@zooique/memora 1.0.1 → 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 +4 -4
- package/dist/agent/agent.d.ts +66 -99
- package/dist/agent/agent.d.ts.map +1 -1
- package/dist/agent/agent.js +263 -208
- package/dist/agent/agent.js.map +1 -1
- package/dist/agent/assembler.d.ts +3 -3
- package/dist/agent/assembler.d.ts.map +1 -1
- package/dist/agent/assembler.js +10 -9
- 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/loop.d.ts +79 -1
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +315 -189
- 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 +31 -3
- package/dist/agent/managers/insightExtractor.d.ts.map +1 -1
- package/dist/agent/managers/insightExtractor.js +122 -51
- 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 +222 -14
- package/dist/agent/managers/memoryInspector.d.ts.map +1 -1
- package/dist/agent/managers/memoryInspector.js +436 -10
- package/dist/agent/managers/memoryInspector.js.map +1 -1
- package/dist/agent/managers/relationBuilder.d.ts +4 -4
- package/dist/agent/managers/relationBuilder.d.ts.map +1 -1
- package/dist/agent/managers/relationBuilder.js +3 -2
- package/dist/agent/managers/relationBuilder.js.map +1 -1
- 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 +124 -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 +4 -4
- package/dist/config/loader.js +23 -1
- 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 +21 -0
- package/dist/eval/evalTypes.d.ts.map +1 -1
- package/dist/eval/evalTypes.js +15 -0
- 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 +41 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +36 -5
- 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 +1 -12
- package/dist/llm/embedding.d.ts.map +1 -1
- package/dist/llm/embedding.js +5 -17
- package/dist/llm/embedding.js.map +1 -1
- package/dist/llm/openaiCompatible.d.ts.map +1 -1
- package/dist/llm/openaiCompatible.js +15 -25
- package/dist/llm/openaiCompatible.js.map +1 -1
- package/dist/memory/inMemoryStorage.d.ts.map +1 -1
- package/dist/memory/inMemoryStorage.js +7 -6
- package/dist/memory/inMemoryStorage.js.map +1 -1
- package/dist/memory/lockManager.d.ts.map +1 -1
- package/dist/memory/lockManager.js +4 -12
- package/dist/memory/lockManager.js.map +1 -1
- package/dist/memory/projectManager.d.ts +6 -6
- package/dist/memory/projectManager.d.ts.map +1 -1
- package/dist/memory/projectManager.js +12 -12
- package/dist/memory/projectManager.js.map +1 -1
- package/dist/memory/projectRegistry.d.ts.map +1 -1
- package/dist/memory/projectRegistry.js +4 -12
- package/dist/memory/projectRegistry.js.map +1 -1
- package/dist/memory/recall.js +2 -2
- package/dist/memory/recall.js.map +1 -1
- package/dist/memory/sourceValidation.d.ts +6 -0
- package/dist/memory/sourceValidation.d.ts.map +1 -1
- package/dist/memory/sourceValidation.js +3 -1
- package/dist/memory/sourceValidation.js.map +1 -1
- package/dist/memory/vectorStore.d.ts +16 -2
- package/dist/memory/vectorStore.d.ts.map +1 -1
- 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/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 +11 -0
- package/dist/utils/segmenter.d.ts.map +1 -1
- package/dist/utils/segmenter.js +13 -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/managers/memoryMutator.d.ts +0 -92
- package/dist/agent/managers/memoryMutator.d.ts.map +0 -1
- package/dist/agent/managers/memoryMutator.js +0 -94
- package/dist/agent/managers/memoryMutator.js.map +0 -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
|
/**
|
|
@@ -95,13 +98,13 @@ export class Agent extends TypedEventEmitter {
|
|
|
95
98
|
insightExtractor = null;
|
|
96
99
|
configManager = null;
|
|
97
100
|
memoryInspector = null;
|
|
98
|
-
/** 记忆写入器(P1-2 拆分,与 MemoryInspector 严格分工:写操作代理;ES 私有字段避免与 getter 重名递归) */
|
|
99
|
-
#memoryMutator = null;
|
|
100
101
|
workProjection = null;
|
|
101
102
|
/** AutoConfigRefiner(模式 3:Agent 智能总结) */
|
|
102
103
|
autoConfigRefiner = null;
|
|
103
104
|
/** SessionArchiver(会话内容归档器,content 类记忆) */
|
|
104
105
|
sessionArchiver = null;
|
|
106
|
+
/** TextPolishManager(文本润色管理器,LLM 语法修正 + 表达优化) */
|
|
107
|
+
textPolisher = null;
|
|
105
108
|
/** 会话管理器(从 Agent 拆分出的会话管理职责) */
|
|
106
109
|
_sessionManager = null;
|
|
107
110
|
/** 当前激活的技能名(上一轮匹配,本轮注入) */
|
|
@@ -109,29 +112,13 @@ export class Agent extends TypedEventEmitter {
|
|
|
109
112
|
_initialized = false;
|
|
110
113
|
// 项目上下文(AgentContext 与 ProjectContext 等价,直接使用后者避免重复字段)
|
|
111
114
|
pctx = null;
|
|
112
|
-
/** chat() 并发锁 */
|
|
113
|
-
_chatBusy = false;
|
|
114
115
|
/**
|
|
115
|
-
* chat()
|
|
116
|
+
* chat() 并发锁管理器(init 时创建,close 时销毁)
|
|
116
117
|
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
* 导致 race condition:T=0 A 获取锁 → T=180s 超时释放 → T=181s B 获取锁
|
|
120
|
-
* → T=182s A 的 finally 误清 B 的锁/计时器/controller。
|
|
121
|
-
*
|
|
122
|
-
* 防护方案:
|
|
123
|
-
* - 获取锁时 token 递增:`const myToken = ++this._chatLockToken`
|
|
124
|
-
* - 超时回调校验 `this._chatLockToken === myToken` 后才释放
|
|
125
|
-
* - finally 块校验 `this._chatLockToken === myToken` 后才清理
|
|
126
|
-
* - 不匹配时跳过清理,避免误清新调用者的资源
|
|
127
|
-
*
|
|
128
|
-
* 选择 number 而非 Symbol:递增计数器可序列化、零依赖、足够唯一(同一 Agent 实例内)
|
|
118
|
+
* 职责:chat() 并发锁 + token 校验(race condition 防护)+ 超时保护 + 外部 signal 合并。
|
|
119
|
+
* 详见 ChatLockManager 类注释。
|
|
129
120
|
*/
|
|
130
|
-
|
|
131
|
-
/** 聊天锁超时计时器(防止 LLM 卡死时锁永久持有) */
|
|
132
|
-
chatLockTimer = null;
|
|
133
|
-
/** chat() 内部 AbortController(超时时中断 generator,防止并发) */
|
|
134
|
-
chatAbortController = null;
|
|
121
|
+
chatLockManager = null;
|
|
135
122
|
/** 记忆衰减定时器(已迁移至 MemoryDecayScheduler,此字段保留用于 close 时引用判断) */
|
|
136
123
|
decayTimer = null;
|
|
137
124
|
/** 最近一次 chat() 调用的时间戳 */
|
|
@@ -177,7 +164,7 @@ export class Agent extends TypedEventEmitter {
|
|
|
177
164
|
*/
|
|
178
165
|
async init(projectPathOverride) {
|
|
179
166
|
if (this._initialized) {
|
|
180
|
-
//
|
|
167
|
+
// close() 失败不应阻塞 init() 重建
|
|
181
168
|
// 原 close() 异常(如 awaitPendingArchives 超时)会传播到 init() 调用方,
|
|
182
169
|
// 导致 Agent 处于不可用状态。此处捕获后继续重建。
|
|
183
170
|
try {
|
|
@@ -207,6 +194,8 @@ export class Agent extends TypedEventEmitter {
|
|
|
207
194
|
'确认 API Key 已配置(环境变量或配置文件)',
|
|
208
195
|
]);
|
|
209
196
|
}
|
|
197
|
+
// chat() 并发锁管理器(生命周期与 Agent 实例一致)
|
|
198
|
+
this.chatLockManager = new ChatLockManager();
|
|
210
199
|
this._initialized = true;
|
|
211
200
|
// 归档操作委托给 ArchiveCoordinator
|
|
212
201
|
// 使用 getter 回调注入依赖,close 时 null 化字段后 getter 自然返回 null
|
|
@@ -217,9 +206,22 @@ export class Agent extends TypedEventEmitter {
|
|
|
217
206
|
emit: (event, payload) => this.emit(event, payload),
|
|
218
207
|
});
|
|
219
208
|
// 记忆衰减职责委托给 MemoryDecayScheduler
|
|
209
|
+
// L2 时效性评估:注入 backgroundProvider + index(可选,未注入时 evaluateTimeliness 静默跳过)
|
|
220
210
|
this.memoryDecayScheduler = new MemoryDecayScheduler({
|
|
221
211
|
tracer: this.#config.tracer,
|
|
222
|
-
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,
|
|
223
225
|
});
|
|
224
226
|
this.memoryDecayScheduler.start(pctx.index, AGENT_CONSTANTS.DECAY_INTERVAL_MS);
|
|
225
227
|
// 保留 decayTimer 引用用于 close 时序兼容(实际定时器由 MemoryDecayScheduler 管理)
|
|
@@ -234,46 +236,23 @@ export class Agent extends TypedEventEmitter {
|
|
|
234
236
|
if (input.length > AGENT_CONSTANTS.CHAT_INPUT_MAX_LENGTH) {
|
|
235
237
|
throw configError('输入过长', `输入超过最大长度限制(${AGENT_CONSTANTS.CHAT_INPUT_MAX_LENGTH / 1024}KB)`, ['缩短输入内容', '分多次对话发送']);
|
|
236
238
|
}
|
|
237
|
-
if (this.
|
|
238
|
-
throw
|
|
239
|
-
'等待上一轮 chat() 的 AsyncGenerator 耗尽(收到 done 事件)',
|
|
240
|
-
'宿主程序应确保同一时间只有一个 chat() 调用',
|
|
241
|
-
]);
|
|
239
|
+
if (this.chatLockManager?.isBusy) {
|
|
240
|
+
throw chatBusyError('发起新对话');
|
|
242
241
|
}
|
|
243
|
-
|
|
244
|
-
//
|
|
245
|
-
//
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
const internalAbort = new AbortController();
|
|
249
|
-
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);
|
|
250
247
|
// 合并外部 signal:外部 abort 时也触发内部
|
|
251
|
-
const
|
|
252
|
-
signal?.addEventListener('abort', onExternalAbort, { once: true });
|
|
253
|
-
// 修复 #4:addEventListener 对已 aborted 的 signal 不触发回调
|
|
254
|
-
// 需手动检查并触发 internalAbort,否则外部已取消的请求仍会进入主流程
|
|
255
|
-
if (signal?.aborted) {
|
|
256
|
-
internalAbort.abort();
|
|
257
|
-
}
|
|
248
|
+
const cleanupExternalSignal = chatLock.attachExternalSignal(signal, internalAbort);
|
|
258
249
|
const combinedSignal = internalAbort.signal;
|
|
259
|
-
// 超时保护:LLM 卡死时中断 generator + 释放锁,防止并发
|
|
260
|
-
// 超时回调校验 token 后才清理,避免误清新调用者的资源
|
|
261
|
-
this.chatLockTimer = safeSetTimeout(() => {
|
|
262
|
-
// 令牌不匹配:锁已被新调用者获取(或本调用已正常退出),跳过清理
|
|
263
|
-
if (this._chatLockToken !== myToken) {
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
logger.warn({ timeoutMs: AGENT_CONSTANTS.CHAT_LOCK_TIMEOUT_MS }, 'chat() 锁超时,中断 generator 并释放锁');
|
|
267
|
-
internalAbort.abort();
|
|
268
|
-
this._chatBusy = false;
|
|
269
|
-
this.chatLockTimer = null;
|
|
270
|
-
this.chatAbortController = null;
|
|
271
|
-
}, AGENT_CONSTANTS.CHAT_LOCK_TIMEOUT_MS);
|
|
272
250
|
try {
|
|
273
251
|
this._lastInteractionAt = new Date();
|
|
274
252
|
// 清理上一轮注入的临时 system 消息(recentConversation/skill/recall/truncation)
|
|
275
253
|
// 防止多轮累积:每轮 chat() 开始前,只保留 messages[0] 和非 system 消息
|
|
276
|
-
this.
|
|
254
|
+
const loop = this.loop;
|
|
255
|
+
loop.cleanTemporarySystemMessages();
|
|
277
256
|
// 基元驱动召回(双通道:语义 + 关键词)
|
|
278
257
|
yield { type: 'thinking', phase: 'recalling' };
|
|
279
258
|
const recalledMemories = await this.recallAndInject(input);
|
|
@@ -287,13 +266,14 @@ export class Agent extends TypedEventEmitter {
|
|
|
287
266
|
// 注入上一轮匹配的技能 prompt
|
|
288
267
|
yield { type: 'thinking', phase: 'processing' };
|
|
289
268
|
this.injectActiveSkill();
|
|
290
|
-
|
|
269
|
+
const history = this.history;
|
|
270
|
+
await history.appendUser(input);
|
|
291
271
|
let assistantContent = '';
|
|
292
272
|
let wasAborted = false;
|
|
293
273
|
// 把 provider 异常转为 yield error chunk,让宿主能优雅展示并清理 UI
|
|
294
274
|
// (裸 throw 会导致未处理 rejection,UI 收不到错误展示机会)
|
|
295
275
|
try {
|
|
296
|
-
for await (const chunk of
|
|
276
|
+
for await (const chunk of loop.processUserInput(input, recalledMemories, combinedSignal)) {
|
|
297
277
|
yield chunk;
|
|
298
278
|
if (chunk.type === 'text') {
|
|
299
279
|
assistantContent += chunk.content;
|
|
@@ -325,7 +305,7 @@ export class Agent extends TypedEventEmitter {
|
|
|
325
305
|
if (assistantContent.trim()) {
|
|
326
306
|
const interruptedMark = this.#config.messages?.interrupted ?? '\n\n[已中断]';
|
|
327
307
|
try {
|
|
328
|
-
await
|
|
308
|
+
await history.appendAssistant(assistantContent + interruptedMark);
|
|
329
309
|
}
|
|
330
310
|
catch (err) {
|
|
331
311
|
logger.warn({ err }, '中断消息历史写入失败');
|
|
@@ -335,7 +315,7 @@ export class Agent extends TypedEventEmitter {
|
|
|
335
315
|
}
|
|
336
316
|
// 追加助手消息到历史(best-effort:失败不影响用户已收到的回答)
|
|
337
317
|
try {
|
|
338
|
-
await
|
|
318
|
+
await history.appendAssistant(assistantContent);
|
|
339
319
|
}
|
|
340
320
|
catch (err) {
|
|
341
321
|
logger.warn({ err }, '助手消息历史写入失败');
|
|
@@ -345,17 +325,10 @@ export class Agent extends TypedEventEmitter {
|
|
|
345
325
|
await this.postProcess(input, assistantContent);
|
|
346
326
|
}
|
|
347
327
|
finally {
|
|
348
|
-
//
|
|
328
|
+
// 仅当本调用仍是当前锁持有者时才清理资源(token 校验)
|
|
349
329
|
// 若 token 已变(超时释放后被新调用者获取),跳过清理避免误清新调用者的状态
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
this.chatAbortController = null;
|
|
353
|
-
if (this.chatLockTimer) {
|
|
354
|
-
clearSafeTimeout(this.chatLockTimer);
|
|
355
|
-
this.chatLockTimer = null;
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
signal?.removeEventListener('abort', onExternalAbort);
|
|
330
|
+
this.chatLockManager?.release(myToken);
|
|
331
|
+
cleanupExternalSignal();
|
|
359
332
|
}
|
|
360
333
|
}
|
|
361
334
|
/**
|
|
@@ -369,7 +342,7 @@ export class Agent extends TypedEventEmitter {
|
|
|
369
342
|
*
|
|
370
343
|
* 本方法让宿主在确认 generator 挂起后强制释放锁,让用户能立即发起新对话。
|
|
371
344
|
*
|
|
372
|
-
*
|
|
345
|
+
* 安全机制(委托至 ChatLockManager.forceRelease):
|
|
373
346
|
* - 递增 _chatLockToken,让原 chat() 的 finally 块检测到 token 不匹配后
|
|
374
347
|
* 跳过资源清理(避免误清新调用者的 _chatBusy/chatAbortController)
|
|
375
348
|
* - abort chatAbortController,让响应 signal 的 await 点(如 fetch)退出
|
|
@@ -384,22 +357,7 @@ export class Agent extends TypedEventEmitter {
|
|
|
384
357
|
* - 调用后不应再消费原 chat() generator 的后续 chunk(token 已变,chunk 无意义)
|
|
385
358
|
*/
|
|
386
359
|
forceReleaseChatLock() {
|
|
387
|
-
|
|
388
|
-
return;
|
|
389
|
-
// 递增 token 让原 chat() 的 finally 块跳过清理(避免误清新调用者的资源)
|
|
390
|
-
this._chatLockToken++;
|
|
391
|
-
// abort 当前 generator(响应 signal 的 await 点会 throw AbortError 退出)
|
|
392
|
-
if (this.chatAbortController) {
|
|
393
|
-
this.chatAbortController.abort();
|
|
394
|
-
this.chatAbortController = null;
|
|
395
|
-
}
|
|
396
|
-
// 清理锁超时定时器(避免后续触发重复清理)
|
|
397
|
-
if (this.chatLockTimer) {
|
|
398
|
-
clearSafeTimeout(this.chatLockTimer);
|
|
399
|
-
this.chatLockTimer = null;
|
|
400
|
-
}
|
|
401
|
-
this._chatBusy = false;
|
|
402
|
-
logger.warn('对话锁被强制释放(宿主无进展超时兜底)');
|
|
360
|
+
this.chatLockManager?.forceRelease();
|
|
403
361
|
}
|
|
404
362
|
/**
|
|
405
363
|
* 召回记忆 + 注入最近对话上下文
|
|
@@ -412,16 +370,33 @@ export class Agent extends TypedEventEmitter {
|
|
|
412
370
|
* @returns 召回的记忆列表
|
|
413
371
|
*/
|
|
414
372
|
async recallAndInject(input) {
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
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,
|
|
419
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
|
+
}
|
|
420
395
|
if (recalledMemories.length > 0) {
|
|
421
396
|
this.emit('memoryRecalled', { count: recalledMemories.length, query: input });
|
|
422
397
|
}
|
|
423
398
|
// Layer 5: 最近对话注入
|
|
424
|
-
const loop = this.
|
|
399
|
+
const loop = this.loop;
|
|
425
400
|
const recentHistory = loop.getRecentHistory(3);
|
|
426
401
|
if (recentHistory.length > 0) {
|
|
427
402
|
const msgs = this.#config.messages;
|
|
@@ -464,10 +439,38 @@ export class Agent extends TypedEventEmitter {
|
|
|
464
439
|
* (会话归档实现后,`insights-only` 将跳过对话原始内容自动归档)
|
|
465
440
|
*/
|
|
466
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) {
|
|
467
460
|
// 角色自动匹配(best-effort:失败不阻塞对话结束,非归档行为不受 archiveMode 影响)
|
|
461
|
+
// 两层匹配策略:关键词高置信度 → LLM 辅助(低置信度且 backgroundProvider 已注入时)
|
|
462
|
+
// LLM 辅助匹配在 agent 层执行,遵循 backend_layers_rules §分层职责(persona/ 不直接调 LLM)
|
|
468
463
|
if (this.personaManager) {
|
|
469
464
|
try {
|
|
470
|
-
|
|
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
|
+
}
|
|
471
474
|
if (matchedPersona) {
|
|
472
475
|
const prevName = this.personaManager.activeName;
|
|
473
476
|
this.personaManager.switchPersona(matchedPersona);
|
|
@@ -508,46 +511,36 @@ export class Agent extends TypedEventEmitter {
|
|
|
508
511
|
if (skipAutoArchive) {
|
|
509
512
|
logger.debug({ mode: 'manual' }, '归档模式为 manual,跳过自动归档');
|
|
510
513
|
}
|
|
511
|
-
//
|
|
514
|
+
// 用户画像实时归档(委托 ArchiveCoordinator,与手动归档路径统一,消除 DRY 违反)
|
|
512
515
|
// ADR-015: full / insights-only 模式下 profile facts 自动归档
|
|
513
|
-
|
|
516
|
+
const history = this.history;
|
|
517
|
+
if (!skipAutoArchive) {
|
|
514
518
|
try {
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
if (entry.confirmed) {
|
|
522
|
-
this.emit('memoryAdded', { id: entry.id, source: 'profile', name: entry.value });
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
}).catch((err) => {
|
|
526
|
-
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) });
|
|
527
525
|
});
|
|
528
|
-
|
|
526
|
+
history.registerPendingArchive(archiveFactsPromise);
|
|
529
527
|
}
|
|
530
528
|
catch (err) {
|
|
531
529
|
logger.warn({ err }, '用户画像归档初始化失败');
|
|
532
530
|
}
|
|
533
531
|
}
|
|
534
|
-
// 输入分类 → Insight
|
|
532
|
+
// 输入分类 → Insight 提取(委托 ArchiveCoordinator,与手动归档路径统一)
|
|
535
533
|
// ADR-015: full / insights-only 模式下 insight 自动归档
|
|
536
|
-
if (!skipAutoArchive
|
|
534
|
+
if (!skipAutoArchive) {
|
|
537
535
|
try {
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
}).catch((err) => {
|
|
547
|
-
logger.warn({ err }, 'Insight 提取失败');
|
|
548
|
-
});
|
|
549
|
-
this.requireNonNull(this.history, 'history').registerPendingArchive(p);
|
|
550
|
-
}
|
|
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);
|
|
551
544
|
}
|
|
552
545
|
catch (err) {
|
|
553
546
|
logger.warn({ err }, 'Insight 提取初始化失败');
|
|
@@ -560,7 +553,7 @@ export class Agent extends TypedEventEmitter {
|
|
|
560
553
|
const analyzePromise = this.autoConfigRefiner.analyze(input, assistantContent).catch((err) => {
|
|
561
554
|
logger.warn({ err }, 'AutoConfigRefiner 分析失败');
|
|
562
555
|
});
|
|
563
|
-
|
|
556
|
+
history.registerPendingArchive(analyzePromise);
|
|
564
557
|
}
|
|
565
558
|
catch (err) {
|
|
566
559
|
logger.warn({ err }, 'AutoConfigRefiner 初始化失败');
|
|
@@ -587,7 +580,7 @@ export class Agent extends TypedEventEmitter {
|
|
|
587
580
|
*/
|
|
588
581
|
forkSession(targetSession) {
|
|
589
582
|
this.assertInitialized('forkSession');
|
|
590
|
-
return this.
|
|
583
|
+
return this._sessionManager.forkSession(targetSession);
|
|
591
584
|
}
|
|
592
585
|
/**
|
|
593
586
|
* 切换到指定项目
|
|
@@ -598,12 +591,10 @@ export class Agent extends TypedEventEmitter {
|
|
|
598
591
|
async switchProject(nameOrPath) {
|
|
599
592
|
this.assertInitialized('switchProject', ['projectManager', 'provider']);
|
|
600
593
|
// 对话进行中切换项目会导致 loop/history 引用被替换,工作记忆与持久化状态不一致
|
|
601
|
-
if (this.
|
|
602
|
-
throw
|
|
603
|
-
'等待上一轮 chat() 的 AsyncGenerator 耗尽',
|
|
604
|
-
]);
|
|
594
|
+
if (this.chatLockManager?.isBusy) {
|
|
595
|
+
throw chatBusyError('切换项目');
|
|
605
596
|
}
|
|
606
|
-
const pm = this.
|
|
597
|
+
const pm = this.projectManager;
|
|
607
598
|
const projects = pm.list;
|
|
608
599
|
let target = projects.find((p) => p.name === nameOrPath || p.path === nameOrPath);
|
|
609
600
|
if (!target) {
|
|
@@ -611,7 +602,7 @@ export class Agent extends TypedEventEmitter {
|
|
|
611
602
|
target = projects.find((p) => p.name.toLowerCase() === nameOrPathLower || p.path.toLowerCase() === nameOrPathLower);
|
|
612
603
|
}
|
|
613
604
|
const projectPath = target ? target.path : nameOrPath;
|
|
614
|
-
const projectName = target ? target.name :
|
|
605
|
+
const projectName = target ? target.name : getBaseName(nameOrPath);
|
|
615
606
|
const newPctx = await pm.initProject(projectPath, projectName, this.#config.configDir);
|
|
616
607
|
// 记录源项目路径(用于事件),切换前 pctx 可能不存在(首次初始化)
|
|
617
608
|
const fromProjectPath = this.pctx?.projectPath ?? null;
|
|
@@ -653,10 +644,10 @@ export class Agent extends TypedEventEmitter {
|
|
|
653
644
|
this.insightExtractor = result.insightExtractor;
|
|
654
645
|
this.configManager = result.configManager;
|
|
655
646
|
this.memoryInspector = result.memoryInspector;
|
|
656
|
-
this.#memoryMutator = result.memoryMutator;
|
|
657
647
|
this.autoConfigRefiner = result.autoConfigRefiner;
|
|
658
648
|
this.workProjection = result.workProjection;
|
|
659
649
|
this.sessionArchiver = result.sessionArchiver;
|
|
650
|
+
this.textPolisher = result.textPolisher;
|
|
660
651
|
// 绑定冲突检测回调,InsightExtractor 检测到 contradicts 时 emit('conflictDetected')
|
|
661
652
|
// 与 bindGetRecentHistory 同模式:解决 Agent 晚于 InsightExtractor 创建的时序循环依赖
|
|
662
653
|
this.insightExtractor.bindOnConflict((info) => {
|
|
@@ -696,16 +687,16 @@ export class Agent extends TypedEventEmitter {
|
|
|
696
687
|
this.emit(event, data);
|
|
697
688
|
}
|
|
698
689
|
};
|
|
699
|
-
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);
|
|
700
693
|
}
|
|
701
694
|
// ─── Provider 管理 ────────────────────────────────────
|
|
702
695
|
setProvider(provider) {
|
|
703
696
|
// 对话进行中切换 Provider 会导致同一 processUserInput 循环内前后两次 LLM 调用命中不同 Provider
|
|
704
697
|
// (模型上下文窗口假设不一致 → 可能导致上下文截断逻辑误判或 tool_call 格式不兼容)
|
|
705
|
-
if (this.
|
|
706
|
-
throw
|
|
707
|
-
'等待上一轮 chat() 的 AsyncGenerator 耗尽',
|
|
708
|
-
]);
|
|
698
|
+
if (this.chatLockManager?.isBusy) {
|
|
699
|
+
throw chatBusyError('切换 Provider');
|
|
709
700
|
}
|
|
710
701
|
this.#provider = provider;
|
|
711
702
|
if (this.loop) {
|
|
@@ -715,10 +706,8 @@ export class Agent extends TypedEventEmitter {
|
|
|
715
706
|
}
|
|
716
707
|
setBackgroundProvider(provider) {
|
|
717
708
|
// 与 setProvider 一致,对话进行中禁止切换后台 Provider
|
|
718
|
-
if (this.
|
|
719
|
-
throw
|
|
720
|
-
'等待上一轮 chat() 的 AsyncGenerator 耗尽',
|
|
721
|
-
]);
|
|
709
|
+
if (this.chatLockManager?.isBusy) {
|
|
710
|
+
throw chatBusyError('切换后台 Provider');
|
|
722
711
|
}
|
|
723
712
|
this.#backgroundProvider = provider;
|
|
724
713
|
// 同步更新 AutoConfigRefiner 的后台 Provider
|
|
@@ -736,10 +725,8 @@ export class Agent extends TypedEventEmitter {
|
|
|
736
725
|
* @param mode 目标模式
|
|
737
726
|
*/
|
|
738
727
|
setArchiveMode(mode) {
|
|
739
|
-
if (this.
|
|
740
|
-
throw
|
|
741
|
-
'等待上一轮 chat() 的 AsyncGenerator 耗尽',
|
|
742
|
-
]);
|
|
728
|
+
if (this.chatLockManager?.isBusy) {
|
|
729
|
+
throw chatBusyError('切换归档模式');
|
|
743
730
|
}
|
|
744
731
|
const prev = this.#config.archiveMode;
|
|
745
732
|
if (prev === mode)
|
|
@@ -821,10 +808,8 @@ export class Agent extends TypedEventEmitter {
|
|
|
821
808
|
*/
|
|
822
809
|
async reloadConfig(source) {
|
|
823
810
|
this.assertInitialized('reloadConfig');
|
|
824
|
-
if (this.
|
|
825
|
-
throw
|
|
826
|
-
'等待上一轮 chat() 的 AsyncGenerator 耗尽',
|
|
827
|
-
]);
|
|
811
|
+
if (this.chatLockManager?.isBusy) {
|
|
812
|
+
throw chatBusyError('重载配置');
|
|
828
813
|
}
|
|
829
814
|
// guardrail 需重建 AgentLoop,不属于热重载范畴
|
|
830
815
|
if (source === 'guardrail') {
|
|
@@ -862,10 +847,8 @@ export class Agent extends TypedEventEmitter {
|
|
|
862
847
|
*/
|
|
863
848
|
async rebuildComponents() {
|
|
864
849
|
// 对话进行中重建组件会导致 loop/history 引用被替换,工作记忆与持久化状态不一致
|
|
865
|
-
if (this.
|
|
866
|
-
throw
|
|
867
|
-
'等待上一轮 chat() 的 AsyncGenerator 耗尽',
|
|
868
|
-
]);
|
|
850
|
+
if (this.chatLockManager?.isBusy) {
|
|
851
|
+
throw chatBusyError('重建组件');
|
|
869
852
|
}
|
|
870
853
|
await this.rebuildComponentsWithCurrentCtx();
|
|
871
854
|
}
|
|
@@ -896,19 +879,6 @@ export class Agent extends TypedEventEmitter {
|
|
|
896
879
|
}
|
|
897
880
|
}
|
|
898
881
|
}
|
|
899
|
-
/**
|
|
900
|
-
* 非空断言守卫——对 null/undefined 值抛出清晰错误的运行时检查
|
|
901
|
-
*
|
|
902
|
-
* @param value 可能为 null/undefined 的值
|
|
903
|
-
* @param name 组件名称(用于错误消息)
|
|
904
|
-
* @throws MemoraError 如果 value 为 null/undefined
|
|
905
|
-
*/
|
|
906
|
-
requireNonNull(value, name) {
|
|
907
|
-
if (value === null || value === undefined) {
|
|
908
|
-
throw configError('Agent 未初始化', `${name} 组件不可用`, ['请先调用 await agent.init()']);
|
|
909
|
-
}
|
|
910
|
-
return value;
|
|
911
|
-
}
|
|
912
882
|
// ─── 记忆生命周期 ───────────────────────────────────────
|
|
913
883
|
// ─── runMemoryDecay 已迁移至 MemoryDecayScheduler.runOnce ─────
|
|
914
884
|
// ─── 关闭 ─────────────────────────────────────────────
|
|
@@ -916,9 +886,10 @@ export class Agent extends TypedEventEmitter {
|
|
|
916
886
|
* 关闭 Agent,释放 SQLite 连接等资源
|
|
917
887
|
*/
|
|
918
888
|
async close() {
|
|
919
|
-
//
|
|
920
|
-
// 检测到 token
|
|
921
|
-
this.
|
|
889
|
+
// 清理 chat 锁管理器(递增 token 使进行中的 chat() generator 的 finally 块
|
|
890
|
+
// 检测到 token 变化后跳过资源清理,close 已接管清理职责)
|
|
891
|
+
this.chatLockManager?.dispose();
|
|
892
|
+
this.chatLockManager = null;
|
|
922
893
|
// 清理 MemoryDecayScheduler(含定时器和 storage 引用)
|
|
923
894
|
if (this.memoryDecayScheduler) {
|
|
924
895
|
this.memoryDecayScheduler.stop();
|
|
@@ -931,50 +902,55 @@ export class Agent extends TypedEventEmitter {
|
|
|
931
902
|
clearSafeInterval(this.decayTimer);
|
|
932
903
|
this.decayTimer = null;
|
|
933
904
|
}
|
|
934
|
-
if (this.chatLockTimer) {
|
|
935
|
-
clearSafeTimeout(this.chatLockTimer);
|
|
936
|
-
this.chatLockTimer = null;
|
|
937
|
-
}
|
|
938
|
-
if (this.chatAbortController) {
|
|
939
|
-
this.chatAbortController.abort();
|
|
940
|
-
this.chatAbortController = null;
|
|
941
|
-
}
|
|
942
905
|
// 清理 PersonaManager 的角色切换防抖锁计时器,防止关闭后回调触发
|
|
943
906
|
if (this.personaManager) {
|
|
944
907
|
this.personaManager.close();
|
|
945
908
|
}
|
|
946
|
-
|
|
909
|
+
// 先等待后台归档完成,再移除事件监听器
|
|
910
|
+
// 顺序敏感:若先 removeAllListeners,fire-and-forget 的归档 Promise 在 await 期间
|
|
911
|
+
// reject 时 emit 会变成 no-op(listeners 已清空),archiveFailed 事件丢失
|
|
947
912
|
if (this.history) {
|
|
948
913
|
await this.history.awaitPendingArchives(AGENT_CONSTANTS.SHUTDOWN_ARCHIVE_TIMEOUT_MS);
|
|
949
914
|
}
|
|
915
|
+
// pending archives 完成后再移除监听器,确保归档失败的 emit 能送达
|
|
916
|
+
this.removeAllListeners();
|
|
950
917
|
if (this.projectManager) {
|
|
951
918
|
await this.projectManager.shutdown();
|
|
952
919
|
}
|
|
953
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
|
|
954
934
|
this.#backgroundProvider = null;
|
|
955
|
-
|
|
935
|
+
// 核心组件
|
|
956
936
|
this.history = null;
|
|
957
937
|
this.loop = null;
|
|
958
|
-
this.
|
|
959
|
-
|
|
938
|
+
this.toolExec = null;
|
|
939
|
+
// 专职 Manager
|
|
940
|
+
this.personaManager = null;
|
|
941
|
+
this.#userProfile = null;
|
|
942
|
+
this.skillManager = null;
|
|
960
943
|
this.insightExtractor = null;
|
|
961
944
|
this.configManager = null;
|
|
962
945
|
this.memoryInspector = null;
|
|
963
|
-
this
|
|
946
|
+
this.workProjection = null;
|
|
964
947
|
this.autoConfigRefiner = null;
|
|
965
948
|
this.sessionArchiver = null;
|
|
949
|
+
this.textPolisher = null;
|
|
950
|
+
this._sessionManager = null;
|
|
951
|
+
// 项目管理
|
|
952
|
+
this.projectManager = null;
|
|
966
953
|
this.pctx = null;
|
|
967
|
-
// 补全剩余 manager 字段 null 化,与上述字段处理方式一致
|
|
968
|
-
// (原实现仅 null 化部分 manager,toolExec/personaManager/#userProfile/skillManager/workProjection 遗漏)
|
|
969
|
-
this.toolExec = null;
|
|
970
|
-
this.personaManager = null;
|
|
971
|
-
this.#userProfile = null;
|
|
972
|
-
this.skillManager = null;
|
|
973
|
-
this.workProjection = null;
|
|
974
|
-
// 清理次要状态字段,防止 re-init 后残留上一会话状态
|
|
975
|
-
this.activeSkill = null;
|
|
976
|
-
this._lastInteractionAt = null;
|
|
977
|
-
// 衰减指标已迁移至 MemoryDecayScheduler,close 时通过 stop() 销毁实例
|
|
978
954
|
}
|
|
979
955
|
// ─── 只读访问器 ───────────────────────────────────────
|
|
980
956
|
get initialized() {
|
|
@@ -1004,7 +980,7 @@ export class Agent extends TypedEventEmitter {
|
|
|
1004
980
|
return this.#provider;
|
|
1005
981
|
}
|
|
1006
982
|
get isBusy() {
|
|
1007
|
-
return this.
|
|
983
|
+
return this.chatLockManager?.isBusy ?? false;
|
|
1008
984
|
}
|
|
1009
985
|
get lastInteractionAt() {
|
|
1010
986
|
return this._lastInteractionAt;
|
|
@@ -1055,7 +1031,7 @@ export class Agent extends TypedEventEmitter {
|
|
|
1055
1031
|
// - 访问器(getter):返回 Manager | null,供宿主项目链式调用和优雅降级
|
|
1056
1032
|
// (如 `agent.persona?.activeName`、`if (!agent.memory) return []`)
|
|
1057
1033
|
// - 门面方法(snapshot/inspect/stats/searchMemories 等):通过
|
|
1058
|
-
// assertInitialized
|
|
1034
|
+
// assertInitialized 抛 MemoraError,提供明确错误信息
|
|
1059
1035
|
// 宿主项目使用访问器时需自行判空,或使用门面方法获得自动错误处理。
|
|
1060
1036
|
/**
|
|
1061
1037
|
* 角色管理器(可能为 null)
|
|
@@ -1107,16 +1083,91 @@ export class Agent extends TypedEventEmitter {
|
|
|
1107
1083
|
get memory() {
|
|
1108
1084
|
return this.memoryInspector;
|
|
1109
1085
|
}
|
|
1086
|
+
// ─── LLM 记忆治理委托方法(G1:暴露能力但不暴露 manager 实例) ──
|
|
1087
|
+
// 这三个方法是 sprite 层接入 L1~L3 治理能力的唯一入口。
|
|
1088
|
+
// 委托内部 manager(memoryInspector / memoryDecayScheduler / memoryAdvisor),
|
|
1089
|
+
// 不返回 manager 实例本身,保持封装(sprite 层不感知 manager 内部结构)。
|
|
1090
|
+
// manager 未初始化时返回 skippedReason 报告,与 manager 内部"backgroundProvider
|
|
1091
|
+
// 未注入时静默跳过"语义一致(向后兼容)。
|
|
1110
1092
|
/**
|
|
1111
|
-
*
|
|
1112
|
-
* addRelation / removeRelation
|
|
1093
|
+
* L1 语义去重(委托 MemoryInspector.deduplicateMemories)
|
|
1113
1094
|
*
|
|
1114
|
-
*
|
|
1115
|
-
*
|
|
1116
|
-
*
|
|
1095
|
+
* 扫描名称相似的记忆对,调用 LLM 判断语义等价,降级低分记忆(score→0.1,不物理删除)。
|
|
1096
|
+
* backgroundProvider 未注入或 Agent 未初始化时返回 skippedReason 报告,不抛错。
|
|
1097
|
+
*
|
|
1098
|
+
* @param signal 可选的 AbortSignal(取消进行中的 LLM 判断)
|
|
1099
|
+
* @returns 去重报告(扫描数 / 降级 ID 列表 / 跳过原因)
|
|
1117
1100
|
*/
|
|
1118
|
-
|
|
1119
|
-
|
|
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);
|
|
1120
1171
|
}
|
|
1121
1172
|
/**
|
|
1122
1173
|
* 项目管理器(可能为 null)
|
|
@@ -1166,5 +1217,9 @@ export class Agent extends TypedEventEmitter {
|
|
|
1166
1217
|
get sessionManager() {
|
|
1167
1218
|
return this._sessionManager;
|
|
1168
1219
|
}
|
|
1220
|
+
/** 文本润色管理器(宿主可通过此 getter 调用 polish 方法) */
|
|
1221
|
+
get polish() {
|
|
1222
|
+
return this.textPolisher;
|
|
1223
|
+
}
|
|
1169
1224
|
}
|
|
1170
1225
|
//# sourceMappingURL=agent.js.map
|