@zooique/memora 2.0.0 → 2.0.3
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.en.md +272 -0
- package/README.md +150 -38
- package/dist/agent/agent.d.ts +233 -58
- package/dist/agent/agent.d.ts.map +1 -1
- package/dist/agent/agent.js +651 -359
- package/dist/agent/agent.js.map +1 -1
- package/dist/agent/assembler.d.ts +29 -13
- package/dist/agent/assembler.d.ts.map +1 -1
- package/dist/agent/assembler.js +79 -44
- package/dist/agent/assembler.js.map +1 -1
- package/dist/agent/builtinToolHandlers.d.ts +26 -1
- package/dist/agent/builtinToolHandlers.d.ts.map +1 -1
- package/dist/agent/builtinToolHandlers.js +73 -1
- package/dist/agent/builtinToolHandlers.js.map +1 -1
- package/dist/agent/constants.d.ts +4 -0
- package/dist/agent/constants.d.ts.map +1 -1
- package/dist/agent/constants.js +4 -0
- package/dist/agent/constants.js.map +1 -1
- package/dist/agent/contextManager.d.ts +8 -0
- package/dist/agent/contextManager.d.ts.map +1 -1
- package/dist/agent/contextManager.js +7 -2
- package/dist/agent/contextManager.js.map +1 -1
- package/dist/agent/guardrail.d.ts +4 -2
- package/dist/agent/guardrail.d.ts.map +1 -1
- package/dist/agent/guardrail.js +13 -5
- package/dist/agent/guardrail.js.map +1 -1
- package/dist/agent/loop.d.ts +58 -14
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +184 -131
- package/dist/agent/loop.js.map +1 -1
- package/dist/agent/managers/archiveCoordinator.d.ts +126 -22
- package/dist/agent/managers/archiveCoordinator.d.ts.map +1 -1
- package/dist/agent/managers/archiveCoordinator.js +239 -44
- package/dist/agent/managers/archiveCoordinator.js.map +1 -1
- package/dist/agent/managers/autoConfigRefiner.d.ts.map +1 -1
- package/dist/agent/managers/autoConfigRefiner.js +4 -8
- package/dist/agent/managers/autoConfigRefiner.js.map +1 -1
- package/dist/agent/managers/configManager.d.ts +87 -3
- package/dist/agent/managers/configManager.d.ts.map +1 -1
- package/dist/agent/managers/configManager.js +139 -2
- package/dist/agent/managers/configManager.js.map +1 -1
- package/dist/agent/managers/dedupManager.d.ts +169 -0
- package/dist/agent/managers/dedupManager.d.ts.map +1 -0
- package/dist/agent/managers/dedupManager.js +336 -0
- package/dist/agent/managers/dedupManager.js.map +1 -0
- package/dist/agent/managers/insightExtractor.d.ts.map +1 -1
- package/dist/agent/managers/insightExtractor.js +11 -11
- package/dist/agent/managers/insightExtractor.js.map +1 -1
- package/dist/agent/managers/llmJudgeHelper.d.ts +38 -0
- package/dist/agent/managers/llmJudgeHelper.d.ts.map +1 -0
- package/dist/agent/managers/llmJudgeHelper.js +49 -0
- package/dist/agent/managers/llmJudgeHelper.js.map +1 -0
- package/dist/agent/managers/memoryAdvisor.d.ts +3 -3
- package/dist/agent/managers/memoryAdvisor.d.ts.map +1 -1
- package/dist/agent/managers/memoryAdvisor.js +53 -67
- package/dist/agent/managers/memoryAdvisor.js.map +1 -1
- package/dist/agent/managers/memoryDecayScheduler.d.ts +46 -5
- package/dist/agent/managers/memoryDecayScheduler.d.ts.map +1 -1
- package/dist/agent/managers/memoryDecayScheduler.js +127 -63
- package/dist/agent/managers/memoryDecayScheduler.js.map +1 -1
- package/dist/agent/managers/memoryGovernance.d.ts +56 -0
- package/dist/agent/managers/memoryGovernance.d.ts.map +1 -0
- package/dist/agent/managers/memoryGovernance.js +63 -0
- package/dist/agent/managers/memoryGovernance.js.map +1 -0
- package/dist/agent/managers/memoryInspector.d.ts +32 -157
- package/dist/agent/managers/memoryInspector.d.ts.map +1 -1
- package/dist/agent/managers/memoryInspector.js +70 -363
- package/dist/agent/managers/memoryInspector.js.map +1 -1
- package/dist/agent/managers/relationBuilder.js +2 -2
- package/dist/agent/managers/relationBuilder.js.map +1 -1
- package/dist/agent/managers/sessionArchiver.d.ts +6 -1
- package/dist/agent/managers/sessionArchiver.d.ts.map +1 -1
- package/dist/agent/managers/sessionArchiver.js +19 -22
- package/dist/agent/managers/sessionArchiver.js.map +1 -1
- package/dist/agent/managers/streamAccumulator.d.ts +45 -0
- package/dist/agent/managers/streamAccumulator.d.ts.map +1 -0
- package/dist/agent/managers/streamAccumulator.js +40 -0
- package/dist/agent/managers/streamAccumulator.js.map +1 -0
- package/dist/agent/managers/textPolishManager.d.ts.map +1 -1
- package/dist/agent/managers/textPolishManager.js +4 -7
- package/dist/agent/managers/textPolishManager.js.map +1 -1
- package/dist/agent/managers/workProjection.d.ts +16 -1
- package/dist/agent/managers/workProjection.d.ts.map +1 -1
- package/dist/agent/managers/workProjection.js +48 -24
- package/dist/agent/managers/workProjection.js.map +1 -1
- package/dist/agent/toolExecutor.d.ts +22 -2
- package/dist/agent/toolExecutor.d.ts.map +1 -1
- package/dist/agent/toolExecutor.js +28 -3
- package/dist/agent/toolExecutor.js.map +1 -1
- package/dist/agent/types.d.ts +10 -1
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/userFactExtractor.d.ts.map +1 -1
- package/dist/agent/userFactExtractor.js +7 -0
- package/dist/agent/userFactExtractor.js.map +1 -1
- package/dist/config/loader.d.ts +124 -236
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +273 -117
- package/dist/config/loader.js.map +1 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/llm/embedding.d.ts +13 -1
- package/dist/llm/embedding.d.ts.map +1 -1
- package/dist/llm/embedding.js.map +1 -1
- package/dist/llm/factory.d.ts +19 -5
- package/dist/llm/factory.d.ts.map +1 -1
- package/dist/llm/factory.js +19 -37
- package/dist/llm/factory.js.map +1 -1
- package/dist/llm/openaiCompatible.d.ts.map +1 -1
- package/dist/llm/openaiCompatible.js +2 -9
- package/dist/llm/openaiCompatible.js.map +1 -1
- package/dist/logging/logger.d.ts +3 -1
- package/dist/logging/logger.d.ts.map +1 -1
- package/dist/logging/logger.js +47 -50
- package/dist/logging/logger.js.map +1 -1
- package/dist/memory/governance.d.ts +50 -0
- package/dist/memory/governance.d.ts.map +1 -0
- package/dist/memory/governance.js +70 -0
- package/dist/memory/governance.js.map +1 -0
- package/dist/memory/hybridMerge.d.ts +0 -4
- package/dist/memory/hybridMerge.d.ts.map +1 -1
- package/dist/memory/hybridMerge.js +19 -4
- package/dist/memory/hybridMerge.js.map +1 -1
- package/dist/memory/inMemoryRelationStore.d.ts +7 -0
- package/dist/memory/inMemoryRelationStore.d.ts.map +1 -1
- package/dist/memory/inMemoryRelationStore.js +15 -0
- package/dist/memory/inMemoryRelationStore.js.map +1 -1
- package/dist/memory/inMemoryStorage.d.ts +36 -1
- package/dist/memory/inMemoryStorage.d.ts.map +1 -1
- package/dist/memory/inMemoryStorage.js +66 -16
- package/dist/memory/inMemoryStorage.js.map +1 -1
- package/dist/memory/loader.d.ts +2 -0
- package/dist/memory/loader.d.ts.map +1 -1
- package/dist/memory/loader.js +8 -1
- package/dist/memory/loader.js.map +1 -1
- package/dist/memory/lockManager.d.ts +7 -2
- package/dist/memory/lockManager.d.ts.map +1 -1
- package/dist/memory/lockManager.js +31 -5
- package/dist/memory/lockManager.js.map +1 -1
- package/dist/memory/projectManager.d.ts +2 -1
- package/dist/memory/projectManager.d.ts.map +1 -1
- package/dist/memory/projectManager.js +21 -3
- package/dist/memory/projectManager.js.map +1 -1
- package/dist/memory/projectRegistry.d.ts +34 -1
- package/dist/memory/projectRegistry.d.ts.map +1 -1
- package/dist/memory/projectRegistry.js +99 -22
- package/dist/memory/projectRegistry.js.map +1 -1
- package/dist/memory/recall.d.ts +16 -0
- package/dist/memory/recall.d.ts.map +1 -1
- package/dist/memory/recall.js +33 -18
- package/dist/memory/recall.js.map +1 -1
- package/dist/memory/relationStore.d.ts +20 -3
- package/dist/memory/relationStore.d.ts.map +1 -1
- package/dist/memory/sourceValidation.d.ts +11 -0
- package/dist/memory/sourceValidation.d.ts.map +1 -1
- package/dist/memory/sourceValidation.js +24 -2
- package/dist/memory/sourceValidation.js.map +1 -1
- package/dist/memory/storageInterface.d.ts +26 -1
- package/dist/memory/storageInterface.d.ts.map +1 -1
- package/dist/memory/store.d.ts +22 -1
- package/dist/memory/store.d.ts.map +1 -1
- package/dist/memory/store.js +58 -20
- package/dist/memory/store.js.map +1 -1
- package/dist/memory/types.d.ts +44 -31
- package/dist/memory/types.d.ts.map +1 -1
- package/dist/memory/types.js +63 -24
- package/dist/memory/types.js.map +1 -1
- package/dist/memory/userProfile.d.ts +32 -8
- package/dist/memory/userProfile.d.ts.map +1 -1
- package/dist/memory/userProfile.js +105 -44
- package/dist/memory/userProfile.js.map +1 -1
- package/dist/memory/vectorStore.d.ts +16 -22
- package/dist/memory/vectorStore.d.ts.map +1 -1
- package/dist/memory/vectorStore.js +49 -22
- package/dist/memory/vectorStore.js.map +1 -1
- package/dist/persona/personaManager.d.ts +36 -87
- package/dist/persona/personaManager.d.ts.map +1 -1
- package/dist/persona/personaManager.js +131 -230
- package/dist/persona/personaManager.js.map +1 -1
- package/dist/skill/skillManager.d.ts +11 -57
- package/dist/skill/skillManager.d.ts.map +1 -1
- package/dist/skill/skillManager.js +46 -142
- package/dist/skill/skillManager.js.map +1 -1
- package/dist/skill/types.d.ts +2 -3
- package/dist/skill/types.d.ts.map +1 -1
- package/dist/utils/configResourceManager.d.ts +111 -0
- package/dist/utils/configResourceManager.d.ts.map +1 -0
- package/dist/utils/configResourceManager.js +148 -0
- package/dist/utils/configResourceManager.js.map +1 -0
- package/dist/utils/errors.d.ts +17 -0
- package/dist/utils/errors.d.ts.map +1 -1
- package/dist/utils/errors.js +19 -0
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/eventEmitter.d.ts +83 -6
- package/dist/utils/eventEmitter.d.ts.map +1 -1
- package/dist/utils/eventEmitter.js +23 -0
- package/dist/utils/eventEmitter.js.map +1 -1
- package/dist/utils/frontmatter.d.ts.map +1 -1
- package/dist/utils/frontmatter.js +4 -2
- package/dist/utils/frontmatter.js.map +1 -1
- package/dist/utils/math.d.ts +11 -0
- package/dist/utils/math.d.ts.map +1 -1
- package/dist/utils/math.js +17 -0
- package/dist/utils/math.js.map +1 -1
- package/dist/utils/segmenter.d.ts +0 -10
- package/dist/utils/segmenter.d.ts.map +1 -1
- package/dist/utils/segmenter.js +10 -1
- package/dist/utils/segmenter.js.map +1 -1
- package/docs/memora-api-reference.md +1235 -0
- package/docs/memora-/346/216/245/345/205/245/346/214/207/345/215/227.md +808 -0
- package/package.json +39 -22
package/dist/agent/agent.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Agent 门面类 — Memora 宿主项目接入入口
|
|
3
3
|
*
|
|
4
|
-
* 设计文档(ADR-010 · Agent 门面类)要求宿主项目通过 `import { Agent } from '@memora
|
|
4
|
+
* 设计文档(ADR-010 · Agent 门面类)要求宿主项目通过 `import { Agent } from '@zooique/memora'`
|
|
5
5
|
* 一行代码接入。本类负责组件组装和核心对话编排,
|
|
6
6
|
* 领域专属操作委托给专职 Manager(PersonaManager / ToolExecutor / SkillManager / ConfigManager / InsightExtractor / MemoryInspector)。
|
|
7
7
|
*
|
|
@@ -26,34 +26,27 @@ 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
|
-
import { recall } from '../memory/recall.js';
|
|
29
|
+
import { recall, boostScores } from '../memory/recall.js';
|
|
30
30
|
import { assembleComponents } from '../agent/assembler.js';
|
|
31
31
|
import { matchPersonaByLlm } from '../agent/personaMatcher.js';
|
|
32
|
-
import { chatBusyError, configError } from '../utils/errors.js';
|
|
33
|
-
import { clearSafeInterval } from '../utils/safeTimer.js';
|
|
32
|
+
import { chatBusyError, configError, isAbortError, toError } from '../utils/errors.js';
|
|
34
33
|
import { SessionManager } from '../agent/managers/sessionManager.js';
|
|
35
34
|
import { ChatLockManager } from '../agent/managers/chatLockManager.js';
|
|
36
35
|
import { MemoryDecayScheduler } from '../agent/managers/memoryDecayScheduler.js';
|
|
36
|
+
import { MemoryGovernance } from '../agent/managers/memoryGovernance.js';
|
|
37
37
|
import { ArchiveCoordinator } from '../agent/managers/archiveCoordinator.js';
|
|
38
|
-
import { TypedEventEmitter } from '../utils/eventEmitter.js';
|
|
38
|
+
import { TypedEventEmitter, AGENT_EVENTS, AGENT_EVENT_SET } from '../utils/eventEmitter.js';
|
|
39
39
|
import { logger } from '../logging/logger.js';
|
|
40
40
|
import { TRACE_SPANS, NOOP_TRACER } from '../agent/tracer.js';
|
|
41
41
|
// ─── 模块级常量 ─────────────────────────────────────────
|
|
42
|
-
|
|
43
|
-
// 必须与 utils/eventEmitter.ts 的 AgentEventMap 键集保持一致(10 个事件)
|
|
44
|
-
const AGENT_EVENT_NAMES = new Set([
|
|
45
|
-
'memoryAdded', 'personaSwitched', 'decayCompleted',
|
|
46
|
-
'memoryRecalled', 'sessionForked', 'insightExtracted',
|
|
47
|
-
'conflictDetected', 'projectSwitched', 'skillMatched',
|
|
48
|
-
'archiveFailed',
|
|
49
|
-
]);
|
|
42
|
+
// AGENT_EVENTS / AGENT_EVENT_SET 已迁移至 utils/eventEmitter.ts(与 AgentEventMap 同处,单一真理源)
|
|
50
43
|
// ─── Agent 门面类 ───────────────────────────────────────
|
|
51
44
|
/**
|
|
52
45
|
* Memora Agent 门面类
|
|
53
46
|
*
|
|
54
47
|
* **设计哲学**:单 Agent,单配置,单记忆。每个 Agent 实例拥有独立的
|
|
55
48
|
* 对话管线(AgentLoop)、独立的消息历史(MessageHistory)和独立的
|
|
56
|
-
* 运行时状态(
|
|
49
|
+
* 运行时状态(chatBusy)。activeSkill 已移除——技能匹配改为当轮实时注入,无需跨轮状态缓存。
|
|
57
50
|
*
|
|
58
51
|
* **多实例**:宿主可通过 new Agent({ dataDir: './agent2' }) 创建
|
|
59
52
|
* 独立实例。只要 dataDir 不同,它们拥有完全隔离的记忆库和会话。
|
|
@@ -98,6 +91,23 @@ export class Agent extends TypedEventEmitter {
|
|
|
98
91
|
insightExtractor = null;
|
|
99
92
|
configManager = null;
|
|
100
93
|
memoryInspector = null;
|
|
94
|
+
/**
|
|
95
|
+
* 语义去重管理器(L1 LLM 记忆治理)
|
|
96
|
+
*
|
|
97
|
+
* SPLIT-3 闭环(2026-07-21):从 MemoryInspector 拆分出 deduplicateMemories 职责,
|
|
98
|
+
* 让 MemoryInspector 回归纯存储读写。Agent.deduplicateMemories() 委托本对象。
|
|
99
|
+
*/
|
|
100
|
+
dedupManager = null;
|
|
101
|
+
/**
|
|
102
|
+
* 记忆顾问(L3 冲突检测 / sourceHealth / suggest)
|
|
103
|
+
*
|
|
104
|
+
* v2 PROXY-1 闭环:Agent.detectConflicts 直接调用 advisor,
|
|
105
|
+
* 不再经 MemoryInspector 转发,消除 3 层无意义代理。
|
|
106
|
+
* sourceHealth/suggest 仍由 inspector 转发以保持 agent.memory 统一入口语义。
|
|
107
|
+
*/
|
|
108
|
+
memoryAdvisor = null;
|
|
109
|
+
/** 记忆治理统一门面(L0/L1/L2/L3 + 诊断) */
|
|
110
|
+
_governance = null;
|
|
101
111
|
workProjection = null;
|
|
102
112
|
/** AutoConfigRefiner(模式 3:Agent 智能总结) */
|
|
103
113
|
autoConfigRefiner = null;
|
|
@@ -107,8 +117,7 @@ export class Agent extends TypedEventEmitter {
|
|
|
107
117
|
textPolisher = null;
|
|
108
118
|
/** 会话管理器(从 Agent 拆分出的会话管理职责) */
|
|
109
119
|
_sessionManager = null;
|
|
110
|
-
|
|
111
|
-
activeSkill = null;
|
|
120
|
+
// activeSkill 字段已移除:matchAndInjectSkill 改为当轮实时匹配注入,无需跨轮状态缓存
|
|
112
121
|
_initialized = false;
|
|
113
122
|
// 项目上下文(AgentContext 与 ProjectContext 等价,直接使用后者避免重复字段)
|
|
114
123
|
pctx = null;
|
|
@@ -119,10 +128,19 @@ export class Agent extends TypedEventEmitter {
|
|
|
119
128
|
* 详见 ChatLockManager 类注释。
|
|
120
129
|
*/
|
|
121
130
|
chatLockManager = null;
|
|
122
|
-
/** 记忆衰减定时器(已迁移至 MemoryDecayScheduler,此字段保留用于 close 时引用判断) */
|
|
123
|
-
decayTimer = null;
|
|
124
131
|
/** 最近一次 chat() 调用的时间戳 */
|
|
125
132
|
_lastInteractionAt = null;
|
|
133
|
+
/**
|
|
134
|
+
* 对话进行中暂存的配置重载请求集合(chatLock 释放后补执行)
|
|
135
|
+
*
|
|
136
|
+
* 场景:用户在对话中通过 create_persona / create_skill 工具创建配置,
|
|
137
|
+
* reloadConfig 因 chatLock 冲突失败时,将 source 暂存于此。
|
|
138
|
+
* chat() 的 finally 块释放锁后遍历此集合逐个补执行 reloadConfig,
|
|
139
|
+
* 兑现"对话结束后自动加载"的承诺(见 tools.ts createConfigHandler 提示文案)。
|
|
140
|
+
*
|
|
141
|
+
* 用 Set 而非数组:同一 source 多次请求只需补执行一次(去重)。
|
|
142
|
+
*/
|
|
143
|
+
pendingConfigReload = new Set();
|
|
126
144
|
// ─── 衰减职责已拆分至 MemoryDecayScheduler ──────────
|
|
127
145
|
// metricDecayRunCount / metricTotalDecayedCount / metricLastDecayAt 字段
|
|
128
146
|
// 位于 MemoryDecayScheduler 内部,Agent 通过 memoryDecayScheduler.getMetrics() 读取
|
|
@@ -134,7 +152,7 @@ export class Agent extends TypedEventEmitter {
|
|
|
134
152
|
super();
|
|
135
153
|
this.#config = {
|
|
136
154
|
projectPath: opts.projectPath,
|
|
137
|
-
dataDir: opts.dataDir
|
|
155
|
+
dataDir: opts.dataDir,
|
|
138
156
|
registryDir: opts.registryDir,
|
|
139
157
|
maxContextTokens: opts.maxContextTokens ?? AGENT_CONSTANTS.DEFAULT_MAX_CONTEXT_TOKENS,
|
|
140
158
|
personaName: opts.persona,
|
|
@@ -156,27 +174,42 @@ export class Agent extends TypedEventEmitter {
|
|
|
156
174
|
};
|
|
157
175
|
this.#provider = opts.provider;
|
|
158
176
|
this.#backgroundProvider = opts.backgroundProvider ?? null;
|
|
159
|
-
// 如需自定义日志,请在创建 Agent 前调用 `import { setLogger } from '@memora
|
|
177
|
+
// 如需自定义日志,请在创建 Agent 前调用 `import { setLogger } from '@zooique/memora'` 全局设置
|
|
160
178
|
}
|
|
161
179
|
// ─── 生命周期 ─────────────────────────────────────────
|
|
162
180
|
/**
|
|
163
181
|
* 初始化 Agent:加载索引、组装内部组件
|
|
164
182
|
*/
|
|
165
183
|
async init(projectPathOverride) {
|
|
166
|
-
|
|
167
|
-
// close() 失败不应阻塞 init() 重建
|
|
168
|
-
// 原 close() 异常(如 awaitPendingArchives 超时)会传播到 init() 调用方,
|
|
169
|
-
// 导致 Agent 处于不可用状态。此处捕获后继续重建。
|
|
170
|
-
try {
|
|
171
|
-
await this.close();
|
|
172
|
-
}
|
|
173
|
-
catch (err) {
|
|
174
|
-
logger.warn({ err }, 'init() 中 close() 旧实例失败,继续重建');
|
|
175
|
-
}
|
|
176
|
-
}
|
|
184
|
+
await this.disposePreviousInstance();
|
|
177
185
|
if (projectPathOverride) {
|
|
178
186
|
this.#config.projectPath = projectPathOverride;
|
|
179
187
|
}
|
|
188
|
+
const pctx = await this.initializeProject();
|
|
189
|
+
await this.assembleComponents(pctx);
|
|
190
|
+
this.pctx = pctx;
|
|
191
|
+
this.validateCoreComponents();
|
|
192
|
+
this.createPostInitComponents(pctx);
|
|
193
|
+
this._initialized = true;
|
|
194
|
+
return pctx;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* 关闭旧实例(如果已初始化),失败不阻塞重建
|
|
198
|
+
*/
|
|
199
|
+
async disposePreviousInstance() {
|
|
200
|
+
if (!this._initialized)
|
|
201
|
+
return;
|
|
202
|
+
try {
|
|
203
|
+
await this.close();
|
|
204
|
+
}
|
|
205
|
+
catch (err) {
|
|
206
|
+
logger.warn({ err }, 'init() 中 close() 旧实例失败,继续重建');
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* 创建 ProjectManager 并初始化项目上下文
|
|
211
|
+
*/
|
|
212
|
+
async initializeProject() {
|
|
180
213
|
this.projectManager = new ProjectManager({
|
|
181
214
|
dataDir: this.#config.dataDir,
|
|
182
215
|
storage: this.#config.storage,
|
|
@@ -184,9 +217,12 @@ export class Agent extends TypedEventEmitter {
|
|
|
184
217
|
// SecurityGuard 由 Agent 层创建,解除 memory→security 反向依赖
|
|
185
218
|
createSecurityGuard: (projectPath, memoraDir, configDir, agentDataDir) => new SecurityGuard(projectPath, memoraDir, this.#config.allowedPaths, this.#config.confirmWrites, this.#config.permission, configDir ?? this.#config.configDir, agentDataDir ?? this.#config.dataDir),
|
|
186
219
|
});
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
220
|
+
return this.projectManager.initProject(this.#config.projectPath, undefined, this.#config.configDir);
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* 校验核心组件(loop / history)非空
|
|
224
|
+
*/
|
|
225
|
+
validateCoreComponents() {
|
|
190
226
|
if (!this.loop || !this.history) {
|
|
191
227
|
throw configError('Agent 初始化失败', '组件组装后 loop 或 history 为空(可能 assembleComponents 抛异常被静默吞掉)', [
|
|
192
228
|
'检查 assembleComponents() 是否有未捕获的异常',
|
|
@@ -194,29 +230,28 @@ export class Agent extends TypedEventEmitter {
|
|
|
194
230
|
'确认 API Key 已配置(环境变量或配置文件)',
|
|
195
231
|
]);
|
|
196
232
|
}
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* 创建 init() 后期组件(初始化完成后才需要的组件)
|
|
236
|
+
*/
|
|
237
|
+
createPostInitComponents(pctx) {
|
|
197
238
|
// chat() 并发锁管理器(生命周期与 Agent 实例一致)
|
|
198
239
|
this.chatLockManager = new ChatLockManager();
|
|
199
|
-
this._initialized = true;
|
|
200
240
|
// 归档操作委托给 ArchiveCoordinator
|
|
201
|
-
// 使用 getter 回调注入依赖,close 时 null 化字段后 getter 自然返回 null
|
|
202
241
|
this.archiveCoordinator = new ArchiveCoordinator({
|
|
203
242
|
getUserProfile: () => this.#userProfile,
|
|
204
243
|
getInsightExtractor: () => this.insightExtractor,
|
|
205
244
|
getSessionArchiver: () => this.sessionArchiver,
|
|
245
|
+
getArchiveMode: () => this.#config.archiveMode,
|
|
206
246
|
emit: (event, payload) => this.emit(event, payload),
|
|
207
247
|
});
|
|
208
248
|
// 记忆衰减职责委托给 MemoryDecayScheduler
|
|
209
|
-
// L2 时效性评估:注入 backgroundProvider + index(可选,未注入时 evaluateTimeliness 静默跳过)
|
|
210
249
|
this.memoryDecayScheduler = new MemoryDecayScheduler({
|
|
211
250
|
tracer: this.#config.tracer,
|
|
212
251
|
onDecayCompleted: (payload) => {
|
|
213
|
-
this.emit(
|
|
214
|
-
//
|
|
215
|
-
|
|
216
|
-
// 失败静默降级:evaluateTimeliness 内部已捕获 LLM 异常并返回降级报告。
|
|
217
|
-
// 触发频次:每小时 1 次(DECAY_INTERVAL_MS=3_600_000),无频次压力。
|
|
218
|
-
void this.evaluateTimeliness().catch((err) => {
|
|
219
|
-
// 仅记录日志,不抛错(与 onDecayCompleted 回调语义一致:不阻塞衰减循环)
|
|
252
|
+
this.emit(AGENT_EVENTS.decayCompleted, payload);
|
|
253
|
+
// MIND2-D4:从 agent.evaluateTimeliness() 迁移到 governance.evaluateTimeliness()
|
|
254
|
+
void this._governance?.evaluateTimeliness().catch((err) => {
|
|
220
255
|
logger.warn({ err }, 'L2 时效性评估自动触发失败(已降级,不影响衰减循环)');
|
|
221
256
|
});
|
|
222
257
|
},
|
|
@@ -224,38 +259,21 @@ export class Agent extends TypedEventEmitter {
|
|
|
224
259
|
index: pctx.index,
|
|
225
260
|
});
|
|
226
261
|
this.memoryDecayScheduler.start(pctx.index, AGENT_CONSTANTS.DECAY_INTERVAL_MS);
|
|
227
|
-
//
|
|
228
|
-
this.
|
|
229
|
-
return pctx;
|
|
262
|
+
// 记忆治理统一门面(L0/L1/L2/L3 + 诊断)
|
|
263
|
+
this._governance = new MemoryGovernance(this.dedupManager, this.memoryDecayScheduler, this.memoryAdvisor);
|
|
230
264
|
}
|
|
231
265
|
/**
|
|
232
266
|
* 流式对话(核心 API)
|
|
233
267
|
*/
|
|
234
268
|
async *chat(input, signal) {
|
|
235
269
|
this.assertInitialized('chat');
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
239
|
-
if (this.chatLockManager?.isBusy) {
|
|
240
|
-
throw chatBusyError('发起新对话');
|
|
241
|
-
}
|
|
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);
|
|
247
|
-
// 合并外部 signal:外部 abort 时也触发内部
|
|
248
|
-
const cleanupExternalSignal = chatLock.attachExternalSignal(signal, internalAbort);
|
|
249
|
-
const combinedSignal = internalAbort.signal;
|
|
270
|
+
this.validateChatInput(input);
|
|
271
|
+
const lockCtx = this.acquireChatLock(signal);
|
|
272
|
+
const { myToken, combinedSignal, cleanupExternalSignal } = lockCtx;
|
|
250
273
|
try {
|
|
251
274
|
this._lastInteractionAt = new Date();
|
|
252
|
-
//
|
|
253
|
-
|
|
254
|
-
const loop = this.loop;
|
|
255
|
-
loop.cleanTemporarySystemMessages();
|
|
256
|
-
// 基元驱动召回(双通道:语义 + 关键词)
|
|
257
|
-
yield { type: 'thinking', phase: 'recalling' };
|
|
258
|
-
const recalledMemories = await this.recallAndInject(input);
|
|
275
|
+
// 上下文准备:角色匹配 → 记忆召回 → 技能注入 → 历史追加
|
|
276
|
+
const recalledMemories = yield* this.prepareChatContext(input, combinedSignal);
|
|
259
277
|
if (combinedSignal.aborted) {
|
|
260
278
|
yield {
|
|
261
279
|
type: 'aborted',
|
|
@@ -263,72 +281,128 @@ export class Agent extends TypedEventEmitter {
|
|
|
263
281
|
};
|
|
264
282
|
return;
|
|
265
283
|
}
|
|
266
|
-
//
|
|
267
|
-
yield
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
//
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
284
|
+
// LLM 流式调用 → 助手消息写历史 → 后处理
|
|
285
|
+
yield* this.executeChatLoop(input, recalledMemories, combinedSignal);
|
|
286
|
+
}
|
|
287
|
+
finally {
|
|
288
|
+
// 仅当本调用仍是当前锁持有者时才清理资源(token 校验)
|
|
289
|
+
this.chatLockManager?.release(myToken);
|
|
290
|
+
cleanupExternalSignal();
|
|
291
|
+
// 补执行对话期间暂存的配置重载请求
|
|
292
|
+
await this.flushPendingConfigReload();
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* 校验 chat 输入
|
|
297
|
+
*/
|
|
298
|
+
validateChatInput(input) {
|
|
299
|
+
if (input.length > AGENT_CONSTANTS.CHAT_INPUT_MAX_LENGTH) {
|
|
300
|
+
throw configError('输入过长', `输入超过最大长度限制(${AGENT_CONSTANTS.CHAT_INPUT_MAX_LENGTH / 1024}KB)`, ['缩短输入内容', '分多次对话发送']);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* 获取对话锁 — 返回锁上下文
|
|
305
|
+
*/
|
|
306
|
+
acquireChatLock(signal) {
|
|
307
|
+
const chatLock = this.chatLockManager;
|
|
308
|
+
if (!chatLock)
|
|
309
|
+
throw configError('ChatLockManager', 'ChatLockManager 未初始化', []);
|
|
310
|
+
if (chatLock.isBusy) {
|
|
311
|
+
throw chatBusyError('发起新对话');
|
|
312
|
+
}
|
|
313
|
+
const { token: myToken, internalAbort } = chatLock.acquire(AGENT_CONSTANTS.CHAT_LOCK_TIMEOUT_MS);
|
|
314
|
+
const cleanupExternalSignal = chatLock.attachExternalSignal(signal, internalAbort);
|
|
315
|
+
return { myToken, internalAbort, cleanupExternalSignal, combinedSignal: internalAbort.signal };
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* 准备对话上下文:角色匹配 → 记忆召回 → 技能注入 → 用户消息写历史
|
|
319
|
+
* @returns 召回的记忆列表(供 loop.processUserInput 注入为 system 消息)
|
|
320
|
+
*/
|
|
321
|
+
async *prepareChatContext(input, combinedSignal) {
|
|
322
|
+
const loop = this.requireLoop;
|
|
323
|
+
loop.cleanTemporarySystemMessages();
|
|
324
|
+
// 角色自动匹配(回答前执行)
|
|
325
|
+
await this.tryAutoMatchPersona(input);
|
|
326
|
+
// 基元驱动召回(双通道:语义 + 关键词)
|
|
327
|
+
yield { type: 'thinking', phase: 'recalling' };
|
|
328
|
+
const recalledMemories = await this.recallAndInject(input);
|
|
329
|
+
if (combinedSignal.aborted)
|
|
330
|
+
return recalledMemories;
|
|
331
|
+
// 技能关键词/正则匹配(当轮立即注入生效)
|
|
332
|
+
yield { type: 'thinking', phase: 'processing' };
|
|
333
|
+
this.matchAndInjectSkill(input);
|
|
334
|
+
const history = this.requireHistory;
|
|
335
|
+
await history.appendUser(input);
|
|
336
|
+
return recalledMemories;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* 执行 LLM 流式循环 → 追加助手消息 → 触发后处理
|
|
340
|
+
*/
|
|
341
|
+
async *executeChatLoop(input, recalledMemories, combinedSignal) {
|
|
342
|
+
const loop = this.requireLoop;
|
|
343
|
+
const history = this.requireHistory;
|
|
344
|
+
let assistantContent = '';
|
|
345
|
+
let wasAborted = false;
|
|
346
|
+
try {
|
|
347
|
+
for await (const chunk of loop.processUserInput(input, recalledMemories, combinedSignal)) {
|
|
348
|
+
yield chunk;
|
|
349
|
+
if (chunk.type === 'text') {
|
|
350
|
+
assistantContent += chunk.content;
|
|
295
351
|
}
|
|
296
|
-
else {
|
|
297
|
-
|
|
298
|
-
return;
|
|
352
|
+
else if (chunk.type === 'aborted') {
|
|
353
|
+
wasAborted = true;
|
|
299
354
|
}
|
|
300
355
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
if (assistantContent.trim()) {
|
|
306
|
-
const interruptedMark = this.#config.messages?.interrupted ?? '\n\n[已中断]';
|
|
307
|
-
try {
|
|
308
|
-
await history.appendAssistant(assistantContent + interruptedMark);
|
|
309
|
-
}
|
|
310
|
-
catch (err) {
|
|
311
|
-
logger.warn({ err }, '中断消息历史写入失败');
|
|
312
|
-
}
|
|
313
|
-
}
|
|
356
|
+
}
|
|
357
|
+
catch (err) {
|
|
358
|
+
if (isAbortError(err)) {
|
|
359
|
+
yield { type: 'aborted', reason: 'User cancelled the conversation' };
|
|
314
360
|
return;
|
|
315
361
|
}
|
|
316
|
-
|
|
362
|
+
else {
|
|
363
|
+
yield { type: 'error', message: err instanceof Error ? err.message : String(err) };
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
if (wasAborted) {
|
|
368
|
+
if (assistantContent.trim()) {
|
|
369
|
+
const interruptedMark = this.#config.messages?.interrupted ?? '\n\n[已中断]';
|
|
370
|
+
try {
|
|
371
|
+
await history.appendAssistant(assistantContent + interruptedMark);
|
|
372
|
+
}
|
|
373
|
+
catch (err) {
|
|
374
|
+
logger.warn({ err }, '中断消息历史写入失败');
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
// 追加助手消息到历史(best-effort)
|
|
380
|
+
try {
|
|
381
|
+
await history.appendAssistant(assistantContent);
|
|
382
|
+
}
|
|
383
|
+
catch (err) {
|
|
384
|
+
logger.warn({ err }, '助手消息历史写入失败');
|
|
385
|
+
}
|
|
386
|
+
// 后处理阶段
|
|
387
|
+
yield { type: 'thinking', phase: 'archiving' };
|
|
388
|
+
await this.postProcess(input, assistantContent);
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* 补执行对话期间暂存的配置重载请求
|
|
392
|
+
*/
|
|
393
|
+
async flushPendingConfigReload() {
|
|
394
|
+
if (this.pendingConfigReload.size === 0)
|
|
395
|
+
return;
|
|
396
|
+
const pending = Array.from(this.pendingConfigReload);
|
|
397
|
+
this.pendingConfigReload.clear();
|
|
398
|
+
for (const src of pending) {
|
|
317
399
|
try {
|
|
318
|
-
await
|
|
400
|
+
await this.reloadConfig(src);
|
|
401
|
+
this.emit(AGENT_EVENTS.configReloaded, { source: src });
|
|
319
402
|
}
|
|
320
403
|
catch (err) {
|
|
321
|
-
logger.warn({ err }, '
|
|
404
|
+
logger.warn({ err, source: src }, '补执行配置重载失败');
|
|
322
405
|
}
|
|
323
|
-
// 后处理阶段
|
|
324
|
-
yield { type: 'thinking', phase: 'archiving' };
|
|
325
|
-
await this.postProcess(input, assistantContent);
|
|
326
|
-
}
|
|
327
|
-
finally {
|
|
328
|
-
// 仅当本调用仍是当前锁持有者时才清理资源(token 校验)
|
|
329
|
-
// 若 token 已变(超时释放后被新调用者获取),跳过清理避免误清新调用者的状态
|
|
330
|
-
this.chatLockManager?.release(myToken);
|
|
331
|
-
cleanupExternalSignal();
|
|
332
406
|
}
|
|
333
407
|
}
|
|
334
408
|
/**
|
|
@@ -378,8 +452,8 @@ export class Agent extends TypedEventEmitter {
|
|
|
378
452
|
});
|
|
379
453
|
let recalledMemories = [];
|
|
380
454
|
try {
|
|
381
|
-
recalledMemories = await recall(this.
|
|
382
|
-
limit:
|
|
455
|
+
recalledMemories = await recall(this.requirePctx.index, input, {
|
|
456
|
+
limit: AGENT_CONSTANTS.DEFAULT_RECALL_LIMIT,
|
|
383
457
|
vectorStore: this.#config.vectorStore,
|
|
384
458
|
excludeSources: this.#config.recallExcludeSources,
|
|
385
459
|
});
|
|
@@ -393,11 +467,18 @@ export class Agent extends TypedEventEmitter {
|
|
|
393
467
|
recallSpan.end();
|
|
394
468
|
}
|
|
395
469
|
if (recalledMemories.length > 0) {
|
|
396
|
-
this.emit(
|
|
470
|
+
this.emit(AGENT_EVENTS.memoryRecalled, { count: recalledMemories.length, query: input });
|
|
471
|
+
// FIX-P1-2:boost 持久化拆分为 fire-and-forget,不阻塞 chat 读路径
|
|
472
|
+
// boost 是软指标(每次 +0.05,上限 1.0),写入失败仅 log 不影响 chat 流程
|
|
473
|
+
const ids = recalledMemories.map((m) => m.id);
|
|
474
|
+
void boostScores(this.requirePctx.index, ids).catch((err) => {
|
|
475
|
+
logger.warn({ err }, 'boost 持久化失败(不影响 chat 流程)');
|
|
476
|
+
this.emit(AGENT_EVENTS.boostPersistFailed, { memoryId: ids.join(','), message: toError(err).message });
|
|
477
|
+
});
|
|
397
478
|
}
|
|
398
479
|
// Layer 5: 最近对话注入
|
|
399
|
-
const loop = this.
|
|
400
|
-
const recentHistory = loop.getRecentHistory(
|
|
480
|
+
const loop = this.requireLoop;
|
|
481
|
+
const recentHistory = loop.getRecentHistory(AGENT_CONSTANTS.DEFAULT_RECENT_HISTORY_ROUNDS);
|
|
401
482
|
if (recentHistory.length > 0) {
|
|
402
483
|
const msgs = this.#config.messages;
|
|
403
484
|
const label = msgs?.recentConversationLabel ?? '[Recent conversation]';
|
|
@@ -413,16 +494,34 @@ export class Agent extends TypedEventEmitter {
|
|
|
413
494
|
return recalledMemories;
|
|
414
495
|
}
|
|
415
496
|
/**
|
|
416
|
-
*
|
|
497
|
+
* 匹配技能并立即注入本轮对话(当轮生效,不延迟到下一轮)
|
|
498
|
+
*
|
|
499
|
+
* 与 persona 的 tryAutoMatchPersona 同模式:匹配 → 注入 → 本轮 LLM 即生效。
|
|
500
|
+
* 原设计为 injectActiveSkill(注入上一轮匹配结果),导致用户说"写代码"的第一轮
|
|
501
|
+
* 得不到技能增强,需再发一条消息才生效。已改为实时匹配注入。
|
|
502
|
+
*
|
|
503
|
+
* 匹配策略:regex trigger 优先(score=1.0),其次关键词匹配(阈值 0.3)。
|
|
504
|
+
* 清理机制:cleanTemporarySystemMessages() 每轮开头清除临时 system 消息,
|
|
505
|
+
* 技能 prompt 自然不会累积到下一轮。
|
|
506
|
+
*
|
|
507
|
+
* @param input 用户输入文本
|
|
417
508
|
*/
|
|
418
|
-
|
|
419
|
-
if (this.
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
509
|
+
matchAndInjectSkill(input) {
|
|
510
|
+
if (!this.skillManager || !this.loop)
|
|
511
|
+
return;
|
|
512
|
+
try {
|
|
513
|
+
const match = this.skillManager.match(input);
|
|
514
|
+
if (match) {
|
|
515
|
+
const skillPrompt = this.skillManager.buildSystemPrompt(match.skill.name);
|
|
516
|
+
if (skillPrompt) {
|
|
517
|
+
this.loop.injectSystemMessage(skillPrompt);
|
|
518
|
+
logger.debug({ skill: match.skill.name, score: match.score }, '技能 prompt 已注入(当轮生效)');
|
|
519
|
+
}
|
|
520
|
+
this.emit(AGENT_EVENTS.skillMatched, { skill: match.skill.name, score: match.score });
|
|
424
521
|
}
|
|
425
|
-
|
|
522
|
+
}
|
|
523
|
+
catch (err) {
|
|
524
|
+
logger.warn({ err }, '技能匹配失败');
|
|
426
525
|
}
|
|
427
526
|
}
|
|
428
527
|
/**
|
|
@@ -445,7 +544,7 @@ export class Agent extends TypedEventEmitter {
|
|
|
445
544
|
archiveMode: this.#config.archiveMode,
|
|
446
545
|
});
|
|
447
546
|
try {
|
|
448
|
-
await this.
|
|
547
|
+
await this.doPostProcess(input, assistantContent);
|
|
449
548
|
}
|
|
450
549
|
finally {
|
|
451
550
|
span.end();
|
|
@@ -456,95 +555,42 @@ export class Agent extends TypedEventEmitter {
|
|
|
456
555
|
*
|
|
457
556
|
* 原 postProcess 逻辑完整保留于此,由外层 postProcess 负责 span 生命周期管理。
|
|
458
557
|
*/
|
|
459
|
-
async
|
|
460
|
-
//
|
|
461
|
-
//
|
|
462
|
-
//
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
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
|
-
}
|
|
474
|
-
if (matchedPersona) {
|
|
475
|
-
const prevName = this.personaManager.activeName;
|
|
476
|
-
this.personaManager.switchPersona(matchedPersona);
|
|
477
|
-
this.emit('personaSwitched', { from: prevName, to: matchedPersona });
|
|
478
|
-
if (this.loop) {
|
|
479
|
-
const profilePrompt = this.userProfile?.buildSystemPrompt() ?? '';
|
|
480
|
-
const personaPrompt = this.personaManager.buildSystemPrompt();
|
|
481
|
-
const newPrefix = [personaPrompt, profilePrompt].filter(Boolean).join('\n\n') +
|
|
482
|
-
([personaPrompt, profilePrompt].some(Boolean) ? '\n\n---\n\n' : '');
|
|
483
|
-
this.loop.refreshPersonaPrefix(newPrefix);
|
|
484
|
-
}
|
|
485
|
-
logger.info({ persona: matchedPersona }, '角色自动切换');
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
catch (err) {
|
|
489
|
-
logger.warn({ err }, '角色自动匹配失败');
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
// 技能关键词匹配(best-effort:失败不阻塞对话结束,非归档行为不受 archiveMode 影响)
|
|
493
|
-
if (this.skillManager) {
|
|
494
|
-
try {
|
|
495
|
-
const match = this.skillManager.match(input);
|
|
496
|
-
if (match) {
|
|
497
|
-
this.activeSkill = match.skill.name;
|
|
498
|
-
// 发射 skillMatched 事件:宿主 UI 可据此展示当前激活技能
|
|
499
|
-
this.emit('skillMatched', { skill: match.skill.name, score: match.score });
|
|
500
|
-
logger.debug({ skill: match.skill.name, score: match.score }, '技能匹配,下一轮注入');
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
catch (err) {
|
|
504
|
-
logger.warn({ err }, '技能匹配失败');
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
// ADR-015: manual 模式跳过所有自动归档(profile + insight),
|
|
508
|
-
// 需用户手动调用 archiveProfileFacts/archiveInsight 触发。
|
|
558
|
+
async doPostProcess(input, assistantContent) {
|
|
559
|
+
// 技能匹配已迁移到 chat() 开头的 matchAndInjectSkill()(当轮实时生效),
|
|
560
|
+
// 与 persona 的 tryAutoMatchPersona 同模式,消除"第一轮无技能"的一轮延迟问题。
|
|
561
|
+
// ADR-015 + FIX-P1-4: archiveMode 三态控制集中到 ArchiveCoordinator
|
|
562
|
+
// 此处统一传 { autoTriggered: true },由 ArchiveCoordinator 内部按 archiveMode 判断是否跳过:
|
|
563
|
+
// - manual 模式 → 跳过 profile/insight 自动归档(用户需手动调用)
|
|
564
|
+
// - full / insights-only 模式 → 执行
|
|
509
565
|
// 角色匹配/技能匹配/AutoConfigRefiner 属"配置学习"行为,非归档,每轮都执行。
|
|
510
|
-
const
|
|
511
|
-
if (skipAutoArchive) {
|
|
512
|
-
logger.debug({ mode: 'manual' }, '归档模式为 manual,跳过自动归档');
|
|
513
|
-
}
|
|
566
|
+
const history = this.requireHistory;
|
|
514
567
|
// 用户画像实时归档(委托 ArchiveCoordinator,与手动归档路径统一,消除 DRY 违反)
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
}
|
|
528
|
-
catch (err) {
|
|
529
|
-
logger.warn({ err }, '用户画像归档初始化失败');
|
|
530
|
-
}
|
|
568
|
+
try {
|
|
569
|
+
// fire-and-forget 包装:registerPendingArchive 确保 close() 时等待后台归档完成
|
|
570
|
+
// 失败时发射 archiveFailed 事件,让宿主 UI 可通知用户(而非静默吞没)
|
|
571
|
+
const archiveFactsPromise = this.requireArchiveCoordinator.archiveProfileFacts(input, { autoTriggered: true }).then(() => { }, (err) => {
|
|
572
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
573
|
+
logger.warn({ err, stage: 'profile' }, '归档失败');
|
|
574
|
+
this.emit(AGENT_EVENTS.archiveFailed, { stage: 'profile', message: message.slice(0, 200) });
|
|
575
|
+
});
|
|
576
|
+
history.registerPendingArchive(archiveFactsPromise);
|
|
577
|
+
}
|
|
578
|
+
catch (err) {
|
|
579
|
+
logger.warn({ err }, '用户画像归档初始化失败');
|
|
531
580
|
}
|
|
532
581
|
// 输入分类 → Insight 提取(委托 ArchiveCoordinator,与手动归档路径统一)
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
}
|
|
545
|
-
catch (err) {
|
|
546
|
-
logger.warn({ err }, 'Insight 提取初始化失败');
|
|
547
|
-
}
|
|
582
|
+
try {
|
|
583
|
+
// fire-and-forget 包装:classify 判断由 ArchiveCoordinator 内部完成
|
|
584
|
+
// 失败时发射 archiveFailed 事件,让宿主 UI 可通知用户(而非静默吞没)
|
|
585
|
+
const archiveInsightPromise = this.requireArchiveCoordinator.archiveInsight(input, assistantContent, { autoTriggered: true }).then(() => { }, (err) => {
|
|
586
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
587
|
+
logger.warn({ err, stage: 'insight' }, '归档失败');
|
|
588
|
+
this.emit(AGENT_EVENTS.archiveFailed, { stage: 'insight', message: message.slice(0, 200) });
|
|
589
|
+
});
|
|
590
|
+
history.registerPendingArchive(archiveInsightPromise);
|
|
591
|
+
}
|
|
592
|
+
catch (err) {
|
|
593
|
+
logger.warn({ err }, 'Insight 提取初始化失败');
|
|
548
594
|
}
|
|
549
595
|
// AutoConfigRefiner(模式 3:Agent 智能总结)
|
|
550
596
|
if (this.autoConfigRefiner) {
|
|
@@ -580,7 +626,7 @@ export class Agent extends TypedEventEmitter {
|
|
|
580
626
|
*/
|
|
581
627
|
forkSession(targetSession) {
|
|
582
628
|
this.assertInitialized('forkSession');
|
|
583
|
-
return this.
|
|
629
|
+
return this.requireSessionManager.forkSession(targetSession);
|
|
584
630
|
}
|
|
585
631
|
/**
|
|
586
632
|
* 切换到指定项目
|
|
@@ -591,9 +637,7 @@ export class Agent extends TypedEventEmitter {
|
|
|
591
637
|
async switchProject(nameOrPath) {
|
|
592
638
|
this.assertInitialized('switchProject', ['projectManager', 'provider']);
|
|
593
639
|
// 对话进行中切换项目会导致 loop/history 引用被替换,工作记忆与持久化状态不一致
|
|
594
|
-
|
|
595
|
-
throw chatBusyError('切换项目');
|
|
596
|
-
}
|
|
640
|
+
this.assertNotBusy('切换项目');
|
|
597
641
|
const pm = this.projectManager;
|
|
598
642
|
const projects = pm.list;
|
|
599
643
|
let target = projects.find((p) => p.name === nameOrPath || p.path === nameOrPath);
|
|
@@ -606,10 +650,21 @@ export class Agent extends TypedEventEmitter {
|
|
|
606
650
|
const newPctx = await pm.initProject(projectPath, projectName, this.#config.configDir);
|
|
607
651
|
// 记录源项目路径(用于事件),切换前 pctx 可能不存在(首次初始化)
|
|
608
652
|
const fromProjectPath = this.pctx?.projectPath ?? null;
|
|
609
|
-
|
|
610
|
-
|
|
653
|
+
// 先尝试重建组件,失败时回滚 pctx 防止状态不一致
|
|
654
|
+
try {
|
|
655
|
+
this.pctx = newPctx;
|
|
656
|
+
await this.rebuildComponentsWithCurrentCtx();
|
|
657
|
+
}
|
|
658
|
+
catch (err) {
|
|
659
|
+
// 回滚:恢复旧 pctx(若存在)
|
|
660
|
+
if (fromProjectPath) {
|
|
661
|
+
logger.warn({ err: toError(err) }, 'switchProject: 组件重建失败,回滚项目上下文');
|
|
662
|
+
this.pctx = { projectPath: fromProjectPath };
|
|
663
|
+
}
|
|
664
|
+
throw err;
|
|
665
|
+
}
|
|
611
666
|
// 发射项目切换事件(供宿主 UI 刷新项目相关界面)
|
|
612
|
-
this.emit(
|
|
667
|
+
this.emit(AGENT_EVENTS.projectSwitched, {
|
|
613
668
|
from: fromProjectPath,
|
|
614
669
|
to: projectPath,
|
|
615
670
|
projectName,
|
|
@@ -634,6 +689,18 @@ export class Agent extends TypedEventEmitter {
|
|
|
634
689
|
messages: this.#config.messages,
|
|
635
690
|
enableContextSummary: this.#config.enableContextSummary,
|
|
636
691
|
existingSkillManager: this.skillManager,
|
|
692
|
+
onWorkProjectionGenerated: (sourcePath, summary) => {
|
|
693
|
+
this.emit(AGENT_EVENTS.workProjectionGenerated, { sourcePath, summary });
|
|
694
|
+
},
|
|
695
|
+
onContextTruncated: (skippedCount, keptCount) => {
|
|
696
|
+
this.emit(AGENT_EVENTS.contextTruncated, { skippedCount, keptCount });
|
|
697
|
+
},
|
|
698
|
+
onDedupCompleted: (report) => {
|
|
699
|
+
this.emit(AGENT_EVENTS.dedupCompleted, { deduplicatedCount: report.deduplicatedCount, demotedIds: report.demotedIds });
|
|
700
|
+
},
|
|
701
|
+
onGuardrailError: (rule, message) => {
|
|
702
|
+
this.emit(AGENT_EVENTS.guardrailError, { rule, message });
|
|
703
|
+
},
|
|
637
704
|
});
|
|
638
705
|
this.history = result.history;
|
|
639
706
|
this.loop = result.loop;
|
|
@@ -644,6 +711,8 @@ export class Agent extends TypedEventEmitter {
|
|
|
644
711
|
this.insightExtractor = result.insightExtractor;
|
|
645
712
|
this.configManager = result.configManager;
|
|
646
713
|
this.memoryInspector = result.memoryInspector;
|
|
714
|
+
this.dedupManager = result.dedupManager;
|
|
715
|
+
this.memoryAdvisor = result.memoryAdvisor;
|
|
647
716
|
this.autoConfigRefiner = result.autoConfigRefiner;
|
|
648
717
|
this.workProjection = result.workProjection;
|
|
649
718
|
this.sessionArchiver = result.sessionArchiver;
|
|
@@ -651,7 +720,7 @@ export class Agent extends TypedEventEmitter {
|
|
|
651
720
|
// 绑定冲突检测回调,InsightExtractor 检测到 contradicts 时 emit('conflictDetected')
|
|
652
721
|
// 与 bindGetRecentHistory 同模式:解决 Agent 晚于 InsightExtractor 创建的时序循环依赖
|
|
653
722
|
this.insightExtractor.bindOnConflict((info) => {
|
|
654
|
-
this.emit(
|
|
723
|
+
this.emit(AGENT_EVENTS.conflictDetected, info);
|
|
655
724
|
});
|
|
656
725
|
// 注入 VectorStore 到 MemoryInspector,启用混合搜索
|
|
657
726
|
if (this.memoryInspector && this.#config.vectorStore) {
|
|
@@ -683,7 +752,7 @@ export class Agent extends TypedEventEmitter {
|
|
|
683
752
|
*/
|
|
684
753
|
createSessionManager() {
|
|
685
754
|
const forwardEvent = (event, data) => {
|
|
686
|
-
if (
|
|
755
|
+
if (AGENT_EVENT_SET.has(event)) {
|
|
687
756
|
this.emit(event, data);
|
|
688
757
|
}
|
|
689
758
|
};
|
|
@@ -695,9 +764,7 @@ export class Agent extends TypedEventEmitter {
|
|
|
695
764
|
setProvider(provider) {
|
|
696
765
|
// 对话进行中切换 Provider 会导致同一 processUserInput 循环内前后两次 LLM 调用命中不同 Provider
|
|
697
766
|
// (模型上下文窗口假设不一致 → 可能导致上下文截断逻辑误判或 tool_call 格式不兼容)
|
|
698
|
-
|
|
699
|
-
throw chatBusyError('切换 Provider');
|
|
700
|
-
}
|
|
767
|
+
this.assertNotBusy('切换 Provider');
|
|
701
768
|
this.#provider = provider;
|
|
702
769
|
if (this.loop) {
|
|
703
770
|
this.loop.setProvider(provider);
|
|
@@ -706,9 +773,7 @@ export class Agent extends TypedEventEmitter {
|
|
|
706
773
|
}
|
|
707
774
|
setBackgroundProvider(provider) {
|
|
708
775
|
// 与 setProvider 一致,对话进行中禁止切换后台 Provider
|
|
709
|
-
|
|
710
|
-
throw chatBusyError('切换后台 Provider');
|
|
711
|
-
}
|
|
776
|
+
this.assertNotBusy('切换后台 Provider');
|
|
712
777
|
this.#backgroundProvider = provider;
|
|
713
778
|
// 同步更新 AutoConfigRefiner 的后台 Provider
|
|
714
779
|
if (this.autoConfigRefiner) {
|
|
@@ -725,13 +790,12 @@ export class Agent extends TypedEventEmitter {
|
|
|
725
790
|
* @param mode 目标模式
|
|
726
791
|
*/
|
|
727
792
|
setArchiveMode(mode) {
|
|
728
|
-
|
|
729
|
-
throw chatBusyError('切换归档模式');
|
|
730
|
-
}
|
|
793
|
+
this.assertNotBusy('切换归档模式');
|
|
731
794
|
const prev = this.#config.archiveMode;
|
|
732
795
|
if (prev === mode)
|
|
733
796
|
return; // 幂等:无变更直接返回
|
|
734
797
|
this.#config.archiveMode = mode;
|
|
798
|
+
this.emit(AGENT_EVENTS.archiveModeChanged, { from: prev, to: mode });
|
|
735
799
|
logger.info({ from: prev, to: mode }, '归档模式已切换');
|
|
736
800
|
}
|
|
737
801
|
/**
|
|
@@ -742,20 +806,180 @@ export class Agent extends TypedEventEmitter {
|
|
|
742
806
|
getArchiveMode() {
|
|
743
807
|
return this.#config.archiveMode;
|
|
744
808
|
}
|
|
809
|
+
/**
|
|
810
|
+
* 手动切换角色(宿主 UI 角色选择器入口)
|
|
811
|
+
*
|
|
812
|
+
* 与 doPostProcess 中的自动匹配走同一条事件链路,确保:
|
|
813
|
+
* 1. AgentLoop 的 systemPromptPrefix 立即刷新(下一次对话使用新角色 prompt)
|
|
814
|
+
* 2. 发射 personaSwitched 事件,触发 spriteLifecycleManager 的完整副作用:
|
|
815
|
+
* - emit('personaChanged') 通知宿主 UI
|
|
816
|
+
* - proactiveEngine.addNotice('persona', ...) 记录通知
|
|
817
|
+
* - perceptionCoordinator.refreshBeforeChat() 基于新角色 traits 重新推导情感基调
|
|
818
|
+
*
|
|
819
|
+
* 对话进行中切换角色会破坏当前 system prompt,与项目切换一致拒绝。
|
|
820
|
+
*
|
|
821
|
+
* @param name 目标角色名
|
|
822
|
+
* @returns 切换成功返回新角色的 system prompt 段;角色不存在或切换失败返回 null
|
|
823
|
+
*/
|
|
824
|
+
switchPersona(name) {
|
|
825
|
+
this.assertInitialized('switchPersona');
|
|
826
|
+
this.assertNotBusy('切换角色');
|
|
827
|
+
if (!this.personaManager)
|
|
828
|
+
return null;
|
|
829
|
+
// 同名切换幂等:直接返回当前 prompt,不触发事件链路
|
|
830
|
+
if (this.personaManager.activeName === name) {
|
|
831
|
+
return this.personaManager.buildSystemPrompt();
|
|
832
|
+
}
|
|
833
|
+
const prevName = this.personaManager.activeName;
|
|
834
|
+
try {
|
|
835
|
+
this.personaManager.switchPersona(name);
|
|
836
|
+
}
|
|
837
|
+
catch (err) {
|
|
838
|
+
// 角色不存在时 PersonaManager 抛 MemoraError,降级为 null 返回
|
|
839
|
+
logger.warn({ err: toError(err).message, name }, '手动切换角色失败');
|
|
840
|
+
return null;
|
|
841
|
+
}
|
|
842
|
+
// 同步刷新 AgentLoop 的角色前缀(关键:否则下一次对话仍用旧角色 prompt)
|
|
843
|
+
// 与 doPostProcess 自动匹配共用同一段逻辑,ADR-017 枝叶层 2 次提取
|
|
844
|
+
this.refreshPersonaPrefixOnLoop();
|
|
845
|
+
// 发射切换事件,触发宿主 UI 刷新 + 感知重推导 + 通知队列记录
|
|
846
|
+
this.emit(AGENT_EVENTS.personaSwitched, { from: prevName, to: name });
|
|
847
|
+
logger.info({ from: prevName, to: name }, '角色手动切换');
|
|
848
|
+
return this.personaManager.buildSystemPrompt();
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* 获取角色切换锁定状态(透传 PersonaManager,P0-2 用户体验打磨)
|
|
852
|
+
*
|
|
853
|
+
* 与 switchPersona 分离:switchPersona 仍返回 string | null 不变,
|
|
854
|
+
* 锁定原因查询走独立路径,避免破坏既有契约(cli.ts、sprite.test.ts 等消费者无感)。
|
|
855
|
+
*
|
|
856
|
+
* 宿主 IPC 层调用此方法前置判断锁定状态,区分"切换失败"原因
|
|
857
|
+
* (locked / busy / not_found / invalid),让用户知道为什么没反应。
|
|
858
|
+
*
|
|
859
|
+
* @returns locked 是否处于锁定状态;unlockAt 锁定自动恢复时间戳(ms epoch),未锁定时为 null
|
|
860
|
+
*/
|
|
861
|
+
getPersonaSwitchLockStatus() {
|
|
862
|
+
this.assertInitialized('getPersonaSwitchLockStatus');
|
|
863
|
+
if (!this.personaManager)
|
|
864
|
+
return { locked: false, unlockAt: null };
|
|
865
|
+
return this.personaManager.getSwitchLockStatus();
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* 刷新 AgentLoop 的 systemPromptPrefix(角色 prompt + profile prompt)
|
|
869
|
+
*
|
|
870
|
+
* 提取自 doPostProcess 自动匹配 + switchPersona 手动切换两处共用逻辑(ADR-017 枝叶层 2 次提取)。
|
|
871
|
+
* 组装规则:[personaPrompt, profilePrompt].filter(Boolean).join('\n\n') + 末尾分隔符 '---'
|
|
872
|
+
*
|
|
873
|
+
* 调用时机:
|
|
874
|
+
* - tryAutoMatchPersona 中角色自动匹配成功后(chat() 回答前)
|
|
875
|
+
* - switchPersona 手动切换成功后
|
|
876
|
+
* - loop 为 null 时静默跳过(init 前或 close 后的边界场景)
|
|
877
|
+
*/
|
|
878
|
+
refreshPersonaPrefixOnLoop() {
|
|
879
|
+
if (!this.loop)
|
|
880
|
+
return;
|
|
881
|
+
const profilePrompt = this.userProfile?.buildSystemPrompt() ?? '';
|
|
882
|
+
const personaPrompt = this.personaManager?.buildSystemPrompt() ?? '';
|
|
883
|
+
const newPrefix = [personaPrompt, profilePrompt].filter(Boolean).join('\n\n') +
|
|
884
|
+
([personaPrompt, profilePrompt].some(Boolean) ? '\n\n---\n\n' : '');
|
|
885
|
+
this.loop.refreshPersonaPrefix(newPrefix);
|
|
886
|
+
}
|
|
887
|
+
/**
|
|
888
|
+
* 角色自动匹配(best-effort:失败不阻塞对话流程)
|
|
889
|
+
*
|
|
890
|
+
* 在 chat() 回答前执行,确保本轮 LLM 调用就用匹配到的角色 system prompt。
|
|
891
|
+
* 两层匹配策略:关键词高置信度 → LLM 辅助(低置信度且 backgroundProvider 已注入时)。
|
|
892
|
+
* LLM 辅助匹配在 agent 层执行,遵循 backend_layers_rules §分层职责(persona/ 不直接调 LLM)。
|
|
893
|
+
*
|
|
894
|
+
* 匹配成功时:切换角色 + 发射 personaSwitched 事件 + 刷新 AgentLoop 前缀。
|
|
895
|
+
* 匹配失败/异常时:静默降级,保持当前角色。
|
|
896
|
+
*
|
|
897
|
+
* @param input 用户输入文本
|
|
898
|
+
*/
|
|
899
|
+
async tryAutoMatchPersona(input) {
|
|
900
|
+
if (!this.personaManager)
|
|
901
|
+
return;
|
|
902
|
+
try {
|
|
903
|
+
let matchedPersona = null;
|
|
904
|
+
if (this.personaManager.canAutoMatch()) {
|
|
905
|
+
matchedPersona = this.personaManager.autoMatch(input);
|
|
906
|
+
// 关键词低置信度且 backgroundProvider 已注入 → LLM 辅助语义匹配
|
|
907
|
+
const bgProvider = this.#backgroundProvider;
|
|
908
|
+
if (!matchedPersona && bgProvider) {
|
|
909
|
+
matchedPersona = await matchPersonaByLlm(bgProvider, this.personaManager.list, this.personaManager.activeName, input);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
if (matchedPersona) {
|
|
913
|
+
const prevName = this.personaManager.activeName;
|
|
914
|
+
this.personaManager.switchPersona(matchedPersona);
|
|
915
|
+
this.emit(AGENT_EVENTS.personaSwitched, { from: prevName, to: matchedPersona });
|
|
916
|
+
// 刷新 AgentLoop 的角色前缀(与 switchPersona 共用同一段逻辑,ADR-017 枝叶层 2 次提取)
|
|
917
|
+
this.refreshPersonaPrefixOnLoop();
|
|
918
|
+
logger.info({ persona: matchedPersona }, '角色自动切换');
|
|
919
|
+
}
|
|
920
|
+
else {
|
|
921
|
+
// 关键词 + LLM 均未命中,且当前角色不是列表首个角色 → 回退到首个角色
|
|
922
|
+
// "切过去回不来":用户从默认切到散文作者后,输入无关话题应回到默认角色
|
|
923
|
+
// 使用 list[0] 而非硬编码 'default':防止 default 角色被删除后 fallback 抛异常
|
|
924
|
+
const shouldFallback = this.shouldFallbackToDefault();
|
|
925
|
+
if (shouldFallback) {
|
|
926
|
+
const fallbackTarget = this.personaManager.list[0]?.name ?? 'default';
|
|
927
|
+
const prevName = this.personaManager.activeName;
|
|
928
|
+
this.personaManager.switchPersona(fallbackTarget);
|
|
929
|
+
this.emit(AGENT_EVENTS.personaSwitched, { from: prevName, to: fallbackTarget });
|
|
930
|
+
this.refreshPersonaPrefixOnLoop();
|
|
931
|
+
logger.info({ persona: fallbackTarget }, '角色回退默认');
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
catch (err) {
|
|
936
|
+
logger.warn({ err }, '角色自动匹配失败');
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
/**
|
|
940
|
+
* 判断是否应回退到默认角色
|
|
941
|
+
*
|
|
942
|
+
* 回退条件(全部满足):
|
|
943
|
+
* 1. 当前激活角色不是默认角色(已在默认角色则无需回退)
|
|
944
|
+
* 2. 角色管理器存在且非锁定状态(锁定时 switchPersona 也会被拦截,回退无意义)
|
|
945
|
+
*
|
|
946
|
+
* 注意:bgProvider 未配置时也会触发回退——因为关键词 + LLM 均未命中说明当前角色不匹配本轮对话,
|
|
947
|
+
* 回退默认角色比卡在错误角色上更合理。
|
|
948
|
+
*
|
|
949
|
+
* @returns 是否应回退到默认角色
|
|
950
|
+
*/
|
|
951
|
+
shouldFallbackToDefault() {
|
|
952
|
+
if (!this.personaManager)
|
|
953
|
+
return false;
|
|
954
|
+
// manual 模式下不回退:用户明确固定了角色,回退会破坏其意图
|
|
955
|
+
if (this.personaManager.currentMode !== 'auto')
|
|
956
|
+
return false;
|
|
957
|
+
const status = this.personaManager.getSwitchLockStatus();
|
|
958
|
+
if (status.locked)
|
|
959
|
+
return false; // 锁定中不回退,避免无意义调用 switchPersona
|
|
960
|
+
// 当前角色已是���表首个角色("默认"角色)→ 无需回退
|
|
961
|
+
const defaultName = this.personaManager.list[0]?.name;
|
|
962
|
+
return defaultName !== undefined && this.personaManager.activeName !== defaultName;
|
|
963
|
+
}
|
|
745
964
|
/**
|
|
746
965
|
* 手动触发 profile facts 归档(manual 模式下使用)
|
|
747
966
|
*
|
|
748
967
|
* manual 模式下 postProcess 跳过自动归档,用户需通过此 API 主动归档。
|
|
749
968
|
* full / insights-only 模式下也可调用(会重复归档,但不推荐)。
|
|
750
969
|
*
|
|
970
|
+
* FIX-P1-4:新增 options 参数透传给 ArchiveCoordinator。
|
|
971
|
+
* 宿主自动触发时传 `{ autoTriggered: true }`,由 ArchiveCoordinator 内部按模式判断;
|
|
972
|
+
* 用户手动触发时无需传 options(默认 autoTriggered=false,无条件执行)。
|
|
973
|
+
*
|
|
751
974
|
* 归档逻辑已委托给 ArchiveCoordinator
|
|
752
975
|
*
|
|
753
976
|
* @param input 本轮用户输入
|
|
977
|
+
* @param options 触发选项(autoTriggered 默认 false,即手动触发)
|
|
754
978
|
* @returns 写入/更新的 UserProfileEntry 列表
|
|
755
979
|
*/
|
|
756
|
-
async archiveProfileFacts(input) {
|
|
980
|
+
async archiveProfileFacts(input, options) {
|
|
757
981
|
this.assertInitialized('archiveProfileFacts');
|
|
758
|
-
return this.
|
|
982
|
+
return this.requireArchiveCoordinator.archiveProfileFacts(input, options);
|
|
759
983
|
}
|
|
760
984
|
/**
|
|
761
985
|
* 手动触发 insight 提取(manual 模式下使用)
|
|
@@ -763,15 +987,20 @@ export class Agent extends TypedEventEmitter {
|
|
|
763
987
|
* manual 模式下 postProcess 跳过自动归档,用户需通过此 API 主动归档。
|
|
764
988
|
* 内部仍走 classify 判断(避免无价值输入浪费 LLM 调用)。
|
|
765
989
|
*
|
|
990
|
+
* FIX-P1-4:新增 options 参数透传给 ArchiveCoordinator。
|
|
991
|
+
* 宿主自动触发时传 `{ autoTriggered: true }`,由 ArchiveCoordinator 内部按模式判断;
|
|
992
|
+
* 用户手动触发时无需传 options(默认 autoTriggered=false,无条件执行)。
|
|
993
|
+
*
|
|
766
994
|
* 归档逻辑已委托给 ArchiveCoordinator
|
|
767
995
|
*
|
|
768
996
|
* @param input 本轮用户输入
|
|
769
997
|
* @param assistantContent 本轮助手回复内容
|
|
998
|
+
* @param options 触发选项(autoTriggered 默认 false,即手动触发)
|
|
770
999
|
* @returns 写入/更新的 Memory 列表
|
|
771
1000
|
*/
|
|
772
|
-
async archiveInsight(input, assistantContent) {
|
|
1001
|
+
async archiveInsight(input, assistantContent, options) {
|
|
773
1002
|
this.assertInitialized('archiveInsight');
|
|
774
|
-
return this.
|
|
1003
|
+
return this.requireArchiveCoordinator.archiveInsight(input, assistantContent, options);
|
|
775
1004
|
}
|
|
776
1005
|
/**
|
|
777
1006
|
* 手动归档会话内容(content 类记忆)
|
|
@@ -779,15 +1008,32 @@ export class Agent extends TypedEventEmitter {
|
|
|
779
1008
|
* 适用于 `insights-only` / `manual` 模式下用户手动触发会话内容归档。
|
|
780
1009
|
* `full` 模式下由宿主在会话切换前自动调用,无需用户干预。
|
|
781
1010
|
*
|
|
1011
|
+
* FIX-P1-4:新增 options 参数透传给 ArchiveCoordinator。
|
|
1012
|
+
* 宿主自动触发时传 `{ autoTriggered: true }`,由 ArchiveCoordinator 内部按模式判断;
|
|
1013
|
+
* 用户手动触发时无需传 options(默认 autoTriggered=false,无条件执行)。
|
|
1014
|
+
*
|
|
782
1015
|
* 归档逻辑已委托给 ArchiveCoordinator
|
|
783
1016
|
*
|
|
784
1017
|
* @param date 会话日期 YYYY-MM-DD
|
|
785
1018
|
* @param session 会话标识(不含日期前缀)
|
|
1019
|
+
* @param options 触发选项(autoTriggered 默认 false,即手动触发)
|
|
786
1020
|
* @returns 归档结果(memories 可能为空,表示无归档价值或 LLM 失败)
|
|
787
1021
|
*/
|
|
788
|
-
async archiveSessionContent(date, session) {
|
|
1022
|
+
async archiveSessionContent(date, session, options) {
|
|
789
1023
|
this.assertInitialized('archiveSessionContent');
|
|
790
|
-
return this.
|
|
1024
|
+
return this.requireArchiveCoordinator.archiveSessionContent(date, session, options);
|
|
1025
|
+
}
|
|
1026
|
+
/**
|
|
1027
|
+
* 获取 pending 归档队列长度(MIND2-C3)
|
|
1028
|
+
*
|
|
1029
|
+
* 返回待重试的失败归档数。归档失败时输入入队,下次同阶段归档调用时自动重试。
|
|
1030
|
+
* 供宿主 UI 展示"N 条待补归档"提示,或判断是否需要手动触发重试。
|
|
1031
|
+
*
|
|
1032
|
+
* @returns pending 队列长度(0 表示无待补)
|
|
1033
|
+
*/
|
|
1034
|
+
getPendingArchiveCount() {
|
|
1035
|
+
this.assertInitialized('getPendingArchiveCount');
|
|
1036
|
+
return this.requireArchiveCoordinator.getPendingArchiveCount();
|
|
791
1037
|
}
|
|
792
1038
|
// ─── 配置重载(事件驱动) ───────────────────────
|
|
793
1039
|
/**
|
|
@@ -799,7 +1045,7 @@ export class Agent extends TypedEventEmitter {
|
|
|
799
1045
|
* 支持的 source:
|
|
800
1046
|
* - 'skill' → SkillManager.reload() 清空缓存重新扫描 skills/ 目录
|
|
801
1047
|
* - 'persona' → PersonaManager.reload() 清空缓存重新扫描 personas/ 目录(保持激活角色)
|
|
802
|
-
* - 'rule' → 无操作(rule
|
|
1048
|
+
* - 'rule' → 无操作(rule 类型由 ConfigManager CRUD 即时同步 bootstrap 段,详见 deleteRule/updateRule)
|
|
803
1049
|
* - 'guardrail' → 抛错(guardrail 是 AgentLoop 的 readonly 数组,需 rebuildComponents 才能重载)
|
|
804
1050
|
* - undefined → 重载 skill + persona(全量重载,不含 guardrail)
|
|
805
1051
|
*
|
|
@@ -809,33 +1055,57 @@ export class Agent extends TypedEventEmitter {
|
|
|
809
1055
|
async reloadConfig(source) {
|
|
810
1056
|
this.assertInitialized('reloadConfig');
|
|
811
1057
|
if (this.chatLockManager?.isBusy) {
|
|
1058
|
+
// 对话进行中无法热重载:将 source 暂存,待 chat() finally 块释放锁后补执行
|
|
1059
|
+
// source 为 undefined(全量重载)时不暂存——全量重载无具体来源,补执行语义不明
|
|
1060
|
+
if (source) {
|
|
1061
|
+
this.pendingConfigReload.add(source);
|
|
1062
|
+
logger.info({ source }, '对话进行中,配置重载已暂存,将在对话结束后补执行');
|
|
1063
|
+
}
|
|
812
1064
|
throw chatBusyError('重载配置');
|
|
813
1065
|
}
|
|
814
1066
|
// guardrail 需重建 AgentLoop,不属于热重载范畴
|
|
815
1067
|
if (source === 'guardrail') {
|
|
816
1068
|
throw configError('guardrail 不支持热重载', 'guardrail 规则是 AgentLoop 的 readonly 数组,需调用 rebuildComponents() 重建', ['使用 rebuildComponents() 重建组件(代价较高)']);
|
|
817
1069
|
}
|
|
818
|
-
// rule
|
|
1070
|
+
// rule 类型由 ConfigManager.deleteRule/updateRule CRUD 即时同步 bootstrap 段(refreshBootstrapMemories 回调),无需 reloadConfig
|
|
819
1071
|
if (source === 'rule') {
|
|
820
|
-
logger.info('rule
|
|
1072
|
+
logger.info('rule 类型由 ConfigManager CRUD 即时同步 bootstrap 段,reloadConfig 跳过');
|
|
821
1073
|
return { skill: 0, persona: 0 };
|
|
822
1074
|
}
|
|
823
1075
|
const result = { skill: 0, persona: 0 };
|
|
1076
|
+
const errors = [];
|
|
824
1077
|
// 按需重载:source 缺省时全量重载,否则只重载指定类型
|
|
825
1078
|
const shouldReloadSkill = !source || source === 'skill';
|
|
826
1079
|
const shouldReloadPersona = !source || source === 'persona';
|
|
827
1080
|
if (shouldReloadSkill && this.skillManager) {
|
|
828
|
-
|
|
1081
|
+
try {
|
|
1082
|
+
result.skill = await this.skillManager.reload();
|
|
1083
|
+
}
|
|
1084
|
+
catch (err) {
|
|
1085
|
+
errors.push(toError(err));
|
|
1086
|
+
logger.warn({ err: toError(err) }, 'reloadConfig: skillManager.reload 失败');
|
|
1087
|
+
}
|
|
829
1088
|
}
|
|
830
1089
|
if (shouldReloadPersona && this.personaManager) {
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
1090
|
+
try {
|
|
1091
|
+
result.persona = await this.personaManager.reload();
|
|
1092
|
+
// 角色重载后,刷新 AgentLoop 的角色前缀(使新角色内容立即注入 system prompt)
|
|
1093
|
+
if (this.loop) {
|
|
1094
|
+
const newPrefix = this.personaManager.buildSystemPrompt();
|
|
1095
|
+
this.loop.refreshPersonaPrefix(newPrefix);
|
|
1096
|
+
}
|
|
836
1097
|
}
|
|
1098
|
+
catch (err) {
|
|
1099
|
+
errors.push(toError(err));
|
|
1100
|
+
logger.warn({ err: toError(err) }, 'reloadConfig: personaManager.reload 失败');
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
if (errors.length > 0) {
|
|
1104
|
+
logger.warn({ source, ...result, errorCount: errors.length }, '配置热重载部分失败');
|
|
1105
|
+
}
|
|
1106
|
+
else {
|
|
1107
|
+
logger.info({ source, ...result }, '配置已热重载');
|
|
837
1108
|
}
|
|
838
|
-
logger.info({ source, ...result }, '配置已热重载');
|
|
839
1109
|
return result;
|
|
840
1110
|
}
|
|
841
1111
|
// ─── 组件访问 ─────────────────────────────────────────
|
|
@@ -847,9 +1117,7 @@ export class Agent extends TypedEventEmitter {
|
|
|
847
1117
|
*/
|
|
848
1118
|
async rebuildComponents() {
|
|
849
1119
|
// 对话进行中重建组件会导致 loop/history 引用被替换,工作记忆与持久化状态不一致
|
|
850
|
-
|
|
851
|
-
throw chatBusyError('重建组件');
|
|
852
|
-
}
|
|
1120
|
+
this.assertNotBusy('重建组件');
|
|
853
1121
|
await this.rebuildComponentsWithCurrentCtx();
|
|
854
1122
|
}
|
|
855
1123
|
// ─── 守卫方法 ───────────────────────────────────────────
|
|
@@ -879,6 +1147,57 @@ export class Agent extends TypedEventEmitter {
|
|
|
879
1147
|
}
|
|
880
1148
|
}
|
|
881
1149
|
}
|
|
1150
|
+
/**
|
|
1151
|
+
* 断言对话未进行中 — 统一守卫,避免在 chat() 进行中执行会破坏状态一致性的操作
|
|
1152
|
+
*
|
|
1153
|
+
* 提取原因:switchProject / setProvider / setBackgroundProvider / setArchiveMode /
|
|
1154
|
+
* switchPersona / rebuildComponents 等 6 处方法均有相同的 `if (isBusy) throw chatBusyError('XXX')` 守卫,
|
|
1155
|
+
* 违反 DRY 原则(v2 神木回天 REPEAT-4 闭环)。
|
|
1156
|
+
*
|
|
1157
|
+
* 注意:reloadConfig 不使用本方法——它在 isBusy 时需暂存 source 而非直接抛错。
|
|
1158
|
+
*
|
|
1159
|
+
* @param operation 操作名称(用于错误消息,如 "切换项目")
|
|
1160
|
+
*/
|
|
1161
|
+
assertNotBusy(operation) {
|
|
1162
|
+
if (this.chatLockManager?.isBusy) {
|
|
1163
|
+
throw chatBusyError(operation);
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
/**
|
|
1167
|
+
* 断言 getter:获取 loop,若 null 则抛出明确错误
|
|
1168
|
+
* 替代 `this.loop!` 非空断言,提供更好的重构安全性
|
|
1169
|
+
*/
|
|
1170
|
+
get requireLoop() {
|
|
1171
|
+
if (!this.loop)
|
|
1172
|
+
throw configError('AgentLoop 未初始化', undefined, ['在调用此方法前执行 await agent.init()']);
|
|
1173
|
+
return this.loop;
|
|
1174
|
+
}
|
|
1175
|
+
/**
|
|
1176
|
+
* 断言 getter:获取 history,若 null 则抛出明确错误
|
|
1177
|
+
*/
|
|
1178
|
+
get requireHistory() {
|
|
1179
|
+
if (!this.history)
|
|
1180
|
+
throw configError('MessageHistory 未初始化', undefined, ['在调用此方法前执行 await agent.init()']);
|
|
1181
|
+
return this.history;
|
|
1182
|
+
}
|
|
1183
|
+
/**
|
|
1184
|
+
* 断言 getter:获取 pctx,若 null 则抛出明确错误
|
|
1185
|
+
*/
|
|
1186
|
+
get requirePctx() {
|
|
1187
|
+
if (!this.pctx)
|
|
1188
|
+
throw configError('ProjectContext 未初始化', undefined, ['在调用此方法前执行 await agent.init()']);
|
|
1189
|
+
return this.pctx;
|
|
1190
|
+
}
|
|
1191
|
+
get requireArchiveCoordinator() {
|
|
1192
|
+
if (!this.archiveCoordinator)
|
|
1193
|
+
throw configError('ArchiveCoordinator 未初始化', undefined, ['在调用此方法前执行 await agent.init()']);
|
|
1194
|
+
return this.archiveCoordinator;
|
|
1195
|
+
}
|
|
1196
|
+
get requireSessionManager() {
|
|
1197
|
+
if (!this._sessionManager)
|
|
1198
|
+
throw configError('SessionManager 未初始化', undefined, ['在调用此方法前执行 await agent.init()']);
|
|
1199
|
+
return this._sessionManager;
|
|
1200
|
+
}
|
|
882
1201
|
// ─── 记忆生命周期 ───────────────────────────────────────
|
|
883
1202
|
// ─── runMemoryDecay 已迁移至 MemoryDecayScheduler.runOnce ─────
|
|
884
1203
|
// ─── 关闭 ─────────────────────────────────────────────
|
|
@@ -891,17 +1210,29 @@ export class Agent extends TypedEventEmitter {
|
|
|
891
1210
|
this.chatLockManager?.dispose();
|
|
892
1211
|
this.chatLockManager = null;
|
|
893
1212
|
// 清理 MemoryDecayScheduler(含定时器和 storage 引用)
|
|
1213
|
+
// FIX-P0-1:先 stop() abort L2 评估的 LLM 调用,再 awaitInflight() 等待 Promise 完成,
|
|
1214
|
+
// 防止 close 后 LLM 回调 upsert 已关闭的 storage
|
|
894
1215
|
if (this.memoryDecayScheduler) {
|
|
895
1216
|
this.memoryDecayScheduler.stop();
|
|
1217
|
+
try {
|
|
1218
|
+
await this.memoryDecayScheduler.awaitInflight();
|
|
1219
|
+
}
|
|
1220
|
+
catch (err) {
|
|
1221
|
+
logger.warn({ err: toError(err) }, 'close: memoryDecayScheduler.awaitInflight 失败');
|
|
1222
|
+
}
|
|
896
1223
|
this.memoryDecayScheduler = null;
|
|
897
1224
|
}
|
|
1225
|
+
// FIX-P0-1:等待 WorkProjection 的 inflight LLM 生成完成,防止 close 后 upsert 已关闭的 storage
|
|
1226
|
+
if (this.workProjection) {
|
|
1227
|
+
try {
|
|
1228
|
+
await this.workProjection.awaitInflight();
|
|
1229
|
+
}
|
|
1230
|
+
catch (err) {
|
|
1231
|
+
logger.warn({ err: toError(err) }, 'close: workProjection.awaitInflight 失败');
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
898
1234
|
// 清理 ArchiveCoordinator(无定时器,只需释放引用)
|
|
899
1235
|
this.archiveCoordinator = null;
|
|
900
|
-
// 清理定时器
|
|
901
|
-
if (this.decayTimer) {
|
|
902
|
-
clearSafeInterval(this.decayTimer);
|
|
903
|
-
this.decayTimer = null;
|
|
904
|
-
}
|
|
905
1236
|
// 清理 PersonaManager 的角色切换防抖锁计时器,防止关闭后回调触发
|
|
906
1237
|
if (this.personaManager) {
|
|
907
1238
|
this.personaManager.close();
|
|
@@ -910,18 +1241,28 @@ export class Agent extends TypedEventEmitter {
|
|
|
910
1241
|
// 顺序敏感:若先 removeAllListeners,fire-and-forget 的归档 Promise 在 await 期间
|
|
911
1242
|
// reject 时 emit 会变成 no-op(listeners 已清空),archiveFailed 事件丢失
|
|
912
1243
|
if (this.history) {
|
|
913
|
-
|
|
1244
|
+
try {
|
|
1245
|
+
await this.history.awaitPendingArchives(AGENT_CONSTANTS.SHUTDOWN_ARCHIVE_TIMEOUT_MS);
|
|
1246
|
+
}
|
|
1247
|
+
catch (err) {
|
|
1248
|
+
logger.warn({ err: toError(err) }, 'close: awaitPendingArchives 失败');
|
|
1249
|
+
}
|
|
914
1250
|
}
|
|
915
1251
|
// pending archives 完成后再移除监听器,确保归档失败的 emit 能送达
|
|
916
1252
|
this.removeAllListeners();
|
|
917
1253
|
if (this.projectManager) {
|
|
918
|
-
|
|
1254
|
+
try {
|
|
1255
|
+
await this.projectManager.shutdown();
|
|
1256
|
+
}
|
|
1257
|
+
catch (err) {
|
|
1258
|
+
logger.warn({ err: toError(err) }, 'close: projectManager.shutdown 失败');
|
|
1259
|
+
}
|
|
919
1260
|
}
|
|
920
1261
|
this._initialized = false;
|
|
921
1262
|
this.nullifyAllComponents();
|
|
922
1263
|
// 清理次要状态字段,防止 re-init 后残留上一会话状态
|
|
923
|
-
this.activeSkill = null;
|
|
924
1264
|
this._lastInteractionAt = null;
|
|
1265
|
+
this.pendingConfigReload.clear();
|
|
925
1266
|
}
|
|
926
1267
|
/**
|
|
927
1268
|
* 统一 null 化所有组件字段(新增 Manager 时在此处追加一行)
|
|
@@ -943,6 +1284,9 @@ export class Agent extends TypedEventEmitter {
|
|
|
943
1284
|
this.insightExtractor = null;
|
|
944
1285
|
this.configManager = null;
|
|
945
1286
|
this.memoryInspector = null;
|
|
1287
|
+
this.dedupManager = null;
|
|
1288
|
+
this.memoryAdvisor = null;
|
|
1289
|
+
this._governance = null;
|
|
946
1290
|
this.workProjection = null;
|
|
947
1291
|
this.autoConfigRefiner = null;
|
|
948
1292
|
this.sessionArchiver = null;
|
|
@@ -959,9 +1303,30 @@ export class Agent extends TypedEventEmitter {
|
|
|
959
1303
|
get context() {
|
|
960
1304
|
return this.pctx;
|
|
961
1305
|
}
|
|
1306
|
+
/**
|
|
1307
|
+
* @deprecated 直接暴露 AgentLoop 破坏封装,将在下一主版本移除。
|
|
1308
|
+
* 使用 getMessages() / getMessageCount() 等有界接口替代。
|
|
1309
|
+
*/
|
|
962
1310
|
get agentLoop() {
|
|
963
1311
|
return this.loop;
|
|
964
1312
|
}
|
|
1313
|
+
/**
|
|
1314
|
+
* 获取当前消息列表(只读副本,不可绕过 Agent 编排链路直接修改)
|
|
1315
|
+
*/
|
|
1316
|
+
getMessages() {
|
|
1317
|
+
return this.loop?.getMessages() ?? [];
|
|
1318
|
+
}
|
|
1319
|
+
/** 获取当前消息数量 */
|
|
1320
|
+
getMessageCount() {
|
|
1321
|
+
return this.loop?.getMessages().length ?? 0;
|
|
1322
|
+
}
|
|
1323
|
+
/**
|
|
1324
|
+
* @deprecated 直接暴露 MessageHistory 破坏封装,将在下一主版本移除。
|
|
1325
|
+
* 使用 Agent 的对话管理方法(chat/chatSync/forkSession)替代直接操作。
|
|
1326
|
+
*/
|
|
1327
|
+
get agentHistory() {
|
|
1328
|
+
return this.history;
|
|
1329
|
+
}
|
|
965
1330
|
/**
|
|
966
1331
|
* 注入情感基调到 system prompt(Phase 2.1:AffectController)
|
|
967
1332
|
*
|
|
@@ -971,10 +1336,9 @@ export class Agent extends TypedEventEmitter {
|
|
|
971
1336
|
* @param affectString 情感描述文本,传空字符串清除注入
|
|
972
1337
|
*/
|
|
973
1338
|
injectAffect(affectString) {
|
|
974
|
-
this.loop
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
return this.history;
|
|
1339
|
+
if (!this.loop)
|
|
1340
|
+
throw configError('AgentLoop', 'AgentLoop 未初始化,无法注入 affect', []);
|
|
1341
|
+
this.loop.injectAffect(affectString);
|
|
978
1342
|
}
|
|
979
1343
|
get provider() {
|
|
980
1344
|
return this.#provider;
|
|
@@ -985,6 +1349,20 @@ export class Agent extends TypedEventEmitter {
|
|
|
985
1349
|
get lastInteractionAt() {
|
|
986
1350
|
return this._lastInteractionAt;
|
|
987
1351
|
}
|
|
1352
|
+
/**
|
|
1353
|
+
* 记忆治理统一门面(L0 衰减 / L1 去重 / L2 时效性 / L3 冲突 / 诊断 / 推荐)
|
|
1354
|
+
*
|
|
1355
|
+
* 统一入口替代散落在 Agent 上的 6 个独立方法:
|
|
1356
|
+
* agent.governance.decay() → 原 runMemoryDecayOnce()
|
|
1357
|
+
* agent.governance.deduplicate() → 原 deduplicateMemories()
|
|
1358
|
+
* agent.governance.evaluateTimeliness() → 原 evaluateTimeliness()
|
|
1359
|
+
* agent.governance.detectConflicts() → 原 detectConflicts()
|
|
1360
|
+
* agent.governance.sourceHealth() → 原 sourceHealth()
|
|
1361
|
+
* agent.governance.suggest() → 原 suggest()
|
|
1362
|
+
*/
|
|
1363
|
+
get governance() {
|
|
1364
|
+
return this._governance;
|
|
1365
|
+
}
|
|
988
1366
|
// ─── 运行时指标 ────────────────────────────────
|
|
989
1367
|
/**
|
|
990
1368
|
* 获取 Agent 运行时指标快照(可观测性增强)
|
|
@@ -1083,92 +1461,6 @@ export class Agent extends TypedEventEmitter {
|
|
|
1083
1461
|
get memory() {
|
|
1084
1462
|
return this.memoryInspector;
|
|
1085
1463
|
}
|
|
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
|
-
}
|
|
1172
1464
|
/**
|
|
1173
1465
|
* 项目管理器(可能为 null)
|
|
1174
1466
|
*
|