@zooique/memora 0.1.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.
Files changed (239) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +148 -0
  3. package/dist/agent/agent.d.ts +343 -0
  4. package/dist/agent/agent.d.ts.map +1 -0
  5. package/dist/agent/agent.js +893 -0
  6. package/dist/agent/agent.js.map +1 -0
  7. package/dist/agent/assembler.d.ts +77 -0
  8. package/dist/agent/assembler.d.ts.map +1 -0
  9. package/dist/agent/assembler.js +115 -0
  10. package/dist/agent/assembler.js.map +1 -0
  11. package/dist/agent/builtinToolHandlers.d.ts +96 -0
  12. package/dist/agent/builtinToolHandlers.d.ts.map +1 -0
  13. package/dist/agent/builtinToolHandlers.js +388 -0
  14. package/dist/agent/builtinToolHandlers.js.map +1 -0
  15. package/dist/agent/builtinTools.d.ts +35 -0
  16. package/dist/agent/builtinTools.d.ts.map +1 -0
  17. package/dist/agent/builtinTools.js +75 -0
  18. package/dist/agent/builtinTools.js.map +1 -0
  19. package/dist/agent/constants.d.ts +67 -0
  20. package/dist/agent/constants.d.ts.map +1 -0
  21. package/dist/agent/constants.js +67 -0
  22. package/dist/agent/constants.js.map +1 -0
  23. package/dist/agent/contextManager.d.ts +130 -0
  24. package/dist/agent/contextManager.d.ts.map +1 -0
  25. package/dist/agent/contextManager.js +287 -0
  26. package/dist/agent/contextManager.js.map +1 -0
  27. package/dist/agent/loop.d.ts +288 -0
  28. package/dist/agent/loop.d.ts.map +1 -0
  29. package/dist/agent/loop.js +756 -0
  30. package/dist/agent/loop.js.map +1 -0
  31. package/dist/agent/managers/autoConfigRefiner.d.ts +39 -0
  32. package/dist/agent/managers/autoConfigRefiner.d.ts.map +1 -0
  33. package/dist/agent/managers/autoConfigRefiner.js +150 -0
  34. package/dist/agent/managers/autoConfigRefiner.js.map +1 -0
  35. package/dist/agent/managers/configManager.d.ts +114 -0
  36. package/dist/agent/managers/configManager.d.ts.map +1 -0
  37. package/dist/agent/managers/configManager.js +186 -0
  38. package/dist/agent/managers/configManager.js.map +1 -0
  39. package/dist/agent/managers/insightExtractor.d.ts +141 -0
  40. package/dist/agent/managers/insightExtractor.d.ts.map +1 -0
  41. package/dist/agent/managers/insightExtractor.js +420 -0
  42. package/dist/agent/managers/insightExtractor.js.map +1 -0
  43. package/dist/agent/managers/memoryAdvisor.d.ts +96 -0
  44. package/dist/agent/managers/memoryAdvisor.d.ts.map +1 -0
  45. package/dist/agent/managers/memoryAdvisor.js +198 -0
  46. package/dist/agent/managers/memoryAdvisor.js.map +1 -0
  47. package/dist/agent/managers/memoryInspector.d.ts +231 -0
  48. package/dist/agent/managers/memoryInspector.d.ts.map +1 -0
  49. package/dist/agent/managers/memoryInspector.js +327 -0
  50. package/dist/agent/managers/memoryInspector.js.map +1 -0
  51. package/dist/agent/managers/sessionManager.d.ts +89 -0
  52. package/dist/agent/managers/sessionManager.d.ts.map +1 -0
  53. package/dist/agent/managers/sessionManager.js +178 -0
  54. package/dist/agent/managers/sessionManager.js.map +1 -0
  55. package/dist/agent/managers/userFactExtractor.d.ts +25 -0
  56. package/dist/agent/managers/userFactExtractor.d.ts.map +1 -0
  57. package/dist/agent/managers/userFactExtractor.js +81 -0
  58. package/dist/agent/managers/userFactExtractor.js.map +1 -0
  59. package/dist/agent/managers/workProjection.d.ts +117 -0
  60. package/dist/agent/managers/workProjection.d.ts.map +1 -0
  61. package/dist/agent/managers/workProjection.js +290 -0
  62. package/dist/agent/managers/workProjection.js.map +1 -0
  63. package/dist/agent/messageHistory.d.ts +157 -0
  64. package/dist/agent/messageHistory.d.ts.map +1 -0
  65. package/dist/agent/messageHistory.js +288 -0
  66. package/dist/agent/messageHistory.js.map +1 -0
  67. package/dist/agent/toolExecutor.d.ts +137 -0
  68. package/dist/agent/toolExecutor.d.ts.map +1 -0
  69. package/dist/agent/toolExecutor.js +209 -0
  70. package/dist/agent/toolExecutor.js.map +1 -0
  71. package/dist/agent/tracer.d.ts +122 -0
  72. package/dist/agent/tracer.d.ts.map +1 -0
  73. package/dist/agent/tracer.js +64 -0
  74. package/dist/agent/tracer.js.map +1 -0
  75. package/dist/agent/types.d.ts +98 -0
  76. package/dist/agent/types.d.ts.map +1 -0
  77. package/dist/agent/types.js +19 -0
  78. package/dist/agent/types.js.map +1 -0
  79. package/dist/config/loader.d.ts +229 -0
  80. package/dist/config/loader.d.ts.map +1 -0
  81. package/dist/config/loader.js +194 -0
  82. package/dist/config/loader.js.map +1 -0
  83. package/dist/eval/evalTypes.d.ts +118 -0
  84. package/dist/eval/evalTypes.d.ts.map +1 -0
  85. package/dist/eval/evalTypes.js +102 -0
  86. package/dist/eval/evalTypes.js.map +1 -0
  87. package/dist/index.d.ts +61 -0
  88. package/dist/index.d.ts.map +1 -0
  89. package/dist/index.js +44 -0
  90. package/dist/index.js.map +1 -0
  91. package/dist/llm/embedding.d.ts +62 -0
  92. package/dist/llm/embedding.d.ts.map +1 -0
  93. package/dist/llm/embedding.js +162 -0
  94. package/dist/llm/embedding.js.map +1 -0
  95. package/dist/llm/factory.d.ts +39 -0
  96. package/dist/llm/factory.d.ts.map +1 -0
  97. package/dist/llm/factory.js +108 -0
  98. package/dist/llm/factory.js.map +1 -0
  99. package/dist/llm/openaiCompatible.d.ts +63 -0
  100. package/dist/llm/openaiCompatible.d.ts.map +1 -0
  101. package/dist/llm/openaiCompatible.js +340 -0
  102. package/dist/llm/openaiCompatible.js.map +1 -0
  103. package/dist/llm/provider.d.ts +91 -0
  104. package/dist/llm/provider.d.ts.map +1 -0
  105. package/dist/llm/provider.js +14 -0
  106. package/dist/llm/provider.js.map +1 -0
  107. package/dist/llm/types.d.ts +25 -0
  108. package/dist/llm/types.d.ts.map +1 -0
  109. package/dist/llm/types.js +7 -0
  110. package/dist/llm/types.js.map +1 -0
  111. package/dist/logging/logger.d.ts +39 -0
  112. package/dist/logging/logger.d.ts.map +1 -0
  113. package/dist/logging/logger.js +279 -0
  114. package/dist/logging/logger.js.map +1 -0
  115. package/dist/logging/loggerInterface.d.ts +33 -0
  116. package/dist/logging/loggerInterface.d.ts.map +1 -0
  117. package/dist/logging/loggerInterface.js +2 -0
  118. package/dist/logging/loggerInterface.js.map +1 -0
  119. package/dist/memory/inMemoryRelationStore.d.ts +51 -0
  120. package/dist/memory/inMemoryRelationStore.d.ts.map +1 -0
  121. package/dist/memory/inMemoryRelationStore.js +65 -0
  122. package/dist/memory/inMemoryRelationStore.js.map +1 -0
  123. package/dist/memory/inMemoryStorage.d.ts +97 -0
  124. package/dist/memory/inMemoryStorage.d.ts.map +1 -0
  125. package/dist/memory/inMemoryStorage.js +177 -0
  126. package/dist/memory/inMemoryStorage.js.map +1 -0
  127. package/dist/memory/loader.d.ts +49 -0
  128. package/dist/memory/loader.d.ts.map +1 -0
  129. package/dist/memory/loader.js +93 -0
  130. package/dist/memory/loader.js.map +1 -0
  131. package/dist/memory/projectManager.d.ts +182 -0
  132. package/dist/memory/projectManager.d.ts.map +1 -0
  133. package/dist/memory/projectManager.js +441 -0
  134. package/dist/memory/projectManager.js.map +1 -0
  135. package/dist/memory/recall.d.ts +77 -0
  136. package/dist/memory/recall.d.ts.map +1 -0
  137. package/dist/memory/recall.js +147 -0
  138. package/dist/memory/recall.js.map +1 -0
  139. package/dist/memory/relationStore.d.ts +78 -0
  140. package/dist/memory/relationStore.d.ts.map +1 -0
  141. package/dist/memory/relationStore.js +2 -0
  142. package/dist/memory/relationStore.js.map +1 -0
  143. package/dist/memory/sessionStore.d.ts +84 -0
  144. package/dist/memory/sessionStore.d.ts.map +1 -0
  145. package/dist/memory/sessionStore.js +2 -0
  146. package/dist/memory/sessionStore.js.map +1 -0
  147. package/dist/memory/storageInterface.d.ts +107 -0
  148. package/dist/memory/storageInterface.d.ts.map +1 -0
  149. package/dist/memory/storageInterface.js +2 -0
  150. package/dist/memory/storageInterface.js.map +1 -0
  151. package/dist/memory/store.d.ts +50 -0
  152. package/dist/memory/store.d.ts.map +1 -0
  153. package/dist/memory/store.js +160 -0
  154. package/dist/memory/store.js.map +1 -0
  155. package/dist/memory/types.d.ts +189 -0
  156. package/dist/memory/types.d.ts.map +1 -0
  157. package/dist/memory/types.js +230 -0
  158. package/dist/memory/types.js.map +1 -0
  159. package/dist/memory/userProfile.d.ts +156 -0
  160. package/dist/memory/userProfile.d.ts.map +1 -0
  161. package/dist/memory/userProfile.js +315 -0
  162. package/dist/memory/userProfile.js.map +1 -0
  163. package/dist/memory/vectorStore.d.ts +75 -0
  164. package/dist/memory/vectorStore.d.ts.map +1 -0
  165. package/dist/memory/vectorStore.js +144 -0
  166. package/dist/memory/vectorStore.js.map +1 -0
  167. package/dist/persona/personaManager.d.ts +121 -0
  168. package/dist/persona/personaManager.d.ts.map +1 -0
  169. package/dist/persona/personaManager.js +349 -0
  170. package/dist/persona/personaManager.js.map +1 -0
  171. package/dist/persona/types.d.ts +32 -0
  172. package/dist/persona/types.d.ts.map +1 -0
  173. package/dist/persona/types.js +5 -0
  174. package/dist/persona/types.js.map +1 -0
  175. package/dist/security/pathGuard.d.ts +121 -0
  176. package/dist/security/pathGuard.d.ts.map +1 -0
  177. package/dist/security/pathGuard.js +276 -0
  178. package/dist/security/pathGuard.js.map +1 -0
  179. package/dist/skill/skillManager.d.ts +82 -0
  180. package/dist/skill/skillManager.d.ts.map +1 -0
  181. package/dist/skill/skillManager.js +198 -0
  182. package/dist/skill/skillManager.js.map +1 -0
  183. package/dist/skill/types.d.ts +28 -0
  184. package/dist/skill/types.d.ts.map +1 -0
  185. package/dist/skill/types.js +5 -0
  186. package/dist/skill/types.js.map +1 -0
  187. package/dist/utils/errors.d.ts +86 -0
  188. package/dist/utils/errors.d.ts.map +1 -0
  189. package/dist/utils/errors.js +143 -0
  190. package/dist/utils/errors.js.map +1 -0
  191. package/dist/utils/eventEmitter.d.ts +87 -0
  192. package/dist/utils/eventEmitter.d.ts.map +1 -0
  193. package/dist/utils/eventEmitter.js +79 -0
  194. package/dist/utils/eventEmitter.js.map +1 -0
  195. package/dist/utils/frontmatter.d.ts +24 -0
  196. package/dist/utils/frontmatter.d.ts.map +1 -0
  197. package/dist/utils/frontmatter.js +44 -0
  198. package/dist/utils/frontmatter.js.map +1 -0
  199. package/dist/utils/json.d.ts +20 -0
  200. package/dist/utils/json.d.ts.map +1 -0
  201. package/dist/utils/json.js +65 -0
  202. package/dist/utils/json.js.map +1 -0
  203. package/dist/utils/loggerHolder.d.ts +37 -0
  204. package/dist/utils/loggerHolder.d.ts.map +1 -0
  205. package/dist/utils/loggerHolder.js +49 -0
  206. package/dist/utils/loggerHolder.js.map +1 -0
  207. package/dist/utils/math.d.ts +5 -0
  208. package/dist/utils/math.d.ts.map +1 -0
  209. package/dist/utils/math.js +19 -0
  210. package/dist/utils/math.js.map +1 -0
  211. package/dist/utils/path.d.ts +28 -0
  212. package/dist/utils/path.d.ts.map +1 -0
  213. package/dist/utils/path.js +33 -0
  214. package/dist/utils/path.js.map +1 -0
  215. package/dist/utils/safeTimer.d.ts +26 -0
  216. package/dist/utils/safeTimer.d.ts.map +1 -0
  217. package/dist/utils/safeTimer.js +49 -0
  218. package/dist/utils/safeTimer.js.map +1 -0
  219. package/dist/utils/scanner.d.ts +54 -0
  220. package/dist/utils/scanner.d.ts.map +1 -0
  221. package/dist/utils/scanner.js +115 -0
  222. package/dist/utils/scanner.js.map +1 -0
  223. package/dist/utils/segmenter.d.ts +30 -0
  224. package/dist/utils/segmenter.d.ts.map +1 -0
  225. package/dist/utils/segmenter.js +80 -0
  226. package/dist/utils/segmenter.js.map +1 -0
  227. package/dist/utils/strings.d.ts +18 -0
  228. package/dist/utils/strings.d.ts.map +1 -0
  229. package/dist/utils/strings.js +25 -0
  230. package/dist/utils/strings.js.map +1 -0
  231. package/dist/utils/time.d.ts +23 -0
  232. package/dist/utils/time.d.ts.map +1 -0
  233. package/dist/utils/time.js +31 -0
  234. package/dist/utils/time.js.map +1 -0
  235. package/dist/utils/toError.d.ts +13 -0
  236. package/dist/utils/toError.d.ts.map +1 -0
  237. package/dist/utils/toError.js +22 -0
  238. package/dist/utils/toError.js.map +1 -0
  239. package/package.json +73 -0
@@ -0,0 +1,893 @@
1
+ /**
2
+ * Agent 门面类 — Memora 宿主项目接入入口
3
+ *
4
+ * 设计文档(ADR-010 · Agent 门面类)要求宿主项目通过 `import { Agent } from '@memora/core'`
5
+ * 一行代码接入。本类负责组件组装和核心对话编排,
6
+ * 领域专属操作委托给专职 Manager(PersonaManager / ToolExecutor / SkillManager / ConfigManager / InsightExtractor / MemoryInspector)。
7
+ *
8
+ * 使用方式(最简):
9
+ * const agent = new Agent({ projectPath: './my-project', configDir: './agent-config' });
10
+ * await agent.init();
11
+ * for await (const chunk of agent.chat('你好')) {
12
+ * if (chunk.type === 'text') process.stdout.write(chunk.content);
13
+ * }
14
+ * await agent.close();
15
+ *
16
+ * 使用方式(高级):
17
+ * const agent = new Agent({ projectPath: './my-project', provider: myProvider, configDir: './agent-config' });
18
+ *
19
+ * 2026-06-12 God Object 拆分:
20
+ * - Insight 提取 → InsightExtractor
21
+ * - 配置管理 → ConfigManager
22
+ * - 记忆查看 → MemoryInspector
23
+ * - 薄包装方法移除,调用方改为 agent.<manager>.xxx()
24
+ */
25
+ import { basename } from 'node:path';
26
+ import { AGENT_CONSTANTS } from '../agent/constants.js';
27
+ import { ProjectManager } from '../memory/projectManager.js';
28
+ import { SecurityGuard } from '../security/pathGuard.js';
29
+ import { recall } from '../memory/recall.js';
30
+ import { extractUserFacts } from '../agent/managers/userFactExtractor.js';
31
+ import { assembleComponents } from '../agent/assembler.js';
32
+ import { configError, toError } from '../utils/errors.js';
33
+ import { safeSetTimeout, clearSafeTimeout, safeSetInterval, clearSafeInterval } from '../utils/safeTimer.js';
34
+ import { SessionManager } from '../agent/managers/sessionManager.js';
35
+ import { TypedEventEmitter } from '../utils/eventEmitter.js';
36
+ import { SOURCE_LABELS } from '../memory/types.js';
37
+ import { logger } from '../logging/logger.js';
38
+ import { NOOP_TRACER, TRACE_SPANS } from '../agent/tracer.js';
39
+ // ─── 模块级常量 ─────────────────────────────────────────
40
+ /** Agent 事件名白名单,用于运行时校验 SessionManager 转发的事件类型 */
41
+ const AGENT_EVENT_NAMES = new Set([
42
+ 'memoryAdded', 'personaSwitched', 'decayCompleted',
43
+ 'memoryRecalled', 'sessionForked', 'insightExtracted',
44
+ ]);
45
+ // ─── Agent 门面类 ───────────────────────────────────────
46
+ /**
47
+ * Memora Agent 门面类
48
+ *
49
+ * **设计哲学**:单 Agent,单配置,单记忆。每个 Agent 实例拥有独立的
50
+ * 对话管线(AgentLoop)、独立的消息历史(MessageHistory)和独立的
51
+ * 运行时状态(activeSkill、chatBusy)。
52
+ *
53
+ * **多实例**:宿主可通过 new Agent({ dataDir: './agent2' }) 创建
54
+ * 独立实例。只要 dataDir 不同,它们拥有完全隔离的记忆库和会话。
55
+ * 内核不负责多 Agent 编排——那是宿主层的职责。
56
+ *
57
+ * **fork vs 多实例**:
58
+ * - forkSession() 分叉对话历史,但记忆索引全局共享
59
+ * - new Agent() + 独立 dataDir 实现完全隔离的记忆空间
60
+ *
61
+ * @example
62
+ * ```ts
63
+ * const agent = new Agent({
64
+ * projectPath: '/path/to/project',
65
+ * provider: new OpenAIProvider({ apiKey: '...' }),
66
+ * configDir: '/path/to/config',
67
+ * dataDir: '/path/to/data',
68
+ * permission: 'owner',
69
+ * allowedPaths: ['/path/to/project'],
70
+ * });
71
+ * await agent.init();
72
+ * const iter = agent.chat('你好');
73
+ * for await (const chunk of iter) { ... }
74
+ * ```
75
+ */
76
+ export class Agent extends TypedEventEmitter {
77
+ // 构造参数分组
78
+ #config;
79
+ /** 前台 Provider(独立字段,因 setProvider() 可变) */
80
+ #provider;
81
+ /** 后台 Provider(独立字段,因 setBackgroundProvider() 可变) */
82
+ #backgroundProvider;
83
+ // 运行时组件(init 后填充)
84
+ projectManager = null;
85
+ history = null;
86
+ loop = null;
87
+ toolExec = null;
88
+ // 新模块
89
+ personaManager = null;
90
+ #userProfile = null;
91
+ skillManager = null;
92
+ // 拆分出的专职 Manager
93
+ insightExtractor = null;
94
+ configManager = null;
95
+ memoryInspector = null;
96
+ workProjection = null;
97
+ /** V-201: AutoConfigRefiner(模式 3:Agent 智能总结) */
98
+ autoConfigRefiner = null;
99
+ /** 会话管理器(从 Agent 拆分出的会话管理职责) */
100
+ _sessionManager = null;
101
+ /** 当前激活的技能名(上一轮匹配,本轮注入) */
102
+ activeSkill = null;
103
+ _initialized = false;
104
+ // 项目上下文(AgentContext 与 ProjectContext 等价,直接使用后者避免重复字段)
105
+ pctx = null;
106
+ /** chat() 并发锁 */
107
+ _chatBusy = false;
108
+ /** 聊天锁超时计时器(防止 LLM 卡死时锁永久持有) */
109
+ chatLockTimer = null;
110
+ /** chat() 内部 AbortController(超时时中断 generator,防止并发) */
111
+ chatAbortController = null;
112
+ /** 记忆衰减定时器 */
113
+ decayTimer = null;
114
+ /** 最近一次 chat() 调用的时间戳 */
115
+ _lastInteractionAt = null;
116
+ // ─── R-103 衰减指标统计字段 ──────────────────────────
117
+ // 累计值,从 Agent.init() 起累加,close() 后随实例销毁。
118
+ /** 衰减执行次数(每次 runMemoryDecay 实际执行 +1) */
119
+ metricDecayRunCount = 0;
120
+ /** 累计衰减记忆数(score 被调低的记忆条数总和) */
121
+ metricTotalDecayedCount = 0;
122
+ /** 上次衰减时间(ISO 8601,null 表示从未执行过) */
123
+ metricLastDecayAt = null;
124
+ constructor(opts) {
125
+ super();
126
+ this.#config = {
127
+ projectPath: opts.projectPath,
128
+ dataDir: opts.dataDir ?? '~/.memora',
129
+ registryDir: opts.registryDir,
130
+ maxContextTokens: opts.maxContextTokens ?? AGENT_CONSTANTS.DEFAULT_MAX_CONTEXT_TOKENS,
131
+ personaName: opts.persona,
132
+ permission: opts.permission ?? 'owner',
133
+ allowedPaths: opts.allowedPaths ?? [],
134
+ confirmWrites: opts.confirmWrites ?? false,
135
+ vectorStore: opts.vectorStore,
136
+ recallExcludeSources: opts.recallExcludeSources ?? [
137
+ ...AGENT_CONSTANTS.DEFAULT_RECALL_EXCLUDE_SOURCES,
138
+ ],
139
+ storage: opts.storage,
140
+ relationStore: opts.relationStore,
141
+ sessionStore: opts.sessionStore,
142
+ configDir: opts.configDir,
143
+ tracer: opts.tracer,
144
+ messages: opts.messages,
145
+ enableContextSummary: opts.enableContextSummary ?? true,
146
+ };
147
+ this.#provider = opts.provider;
148
+ this.#backgroundProvider = opts.backgroundProvider ?? null;
149
+ // 如需自定义日志,请在创建 Agent 前调用 `import { setLogger } from '@memora/core'` 全局设置
150
+ }
151
+ // ─── 生命周期 ─────────────────────────────────────────
152
+ /**
153
+ * 初始化 Agent:加载索引、组装内部组件
154
+ */
155
+ async init(projectPathOverride) {
156
+ if (this._initialized) {
157
+ // 修复 #3:close() 失败不应阻塞 init() 重建
158
+ // 原 close() 异常(如 awaitPendingArchives 超时)会传播到 init() 调用方,
159
+ // 导致 Agent 处于不可用状态。此处捕获后继续重建。
160
+ try {
161
+ await this.close();
162
+ }
163
+ catch (err) {
164
+ logger.warn({ err }, 'init() 中 close() 旧实例失败,继续重建');
165
+ }
166
+ }
167
+ if (projectPathOverride) {
168
+ this.#config.projectPath = projectPathOverride;
169
+ }
170
+ this.projectManager = new ProjectManager({
171
+ dataDir: this.#config.dataDir,
172
+ storage: this.#config.storage,
173
+ registryDir: this.#config.registryDir,
174
+ // A-004: SecurityGuard 由 Agent 层创建,解除 memory→security 反向依赖
175
+ 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),
176
+ });
177
+ const pctx = await this.projectManager.initProject(this.#config.projectPath, undefined, this.#config.configDir);
178
+ await this.assembleComponents(pctx);
179
+ this.pctx = pctx;
180
+ if (!this.loop || !this.history) {
181
+ throw configError('Agent 初始化失败', '组件组装后 loop 或 history 为空(可能 assembleComponents 抛异常被静默吞掉)', [
182
+ '检查 assembleComponents() 是否有未捕获的异常',
183
+ '检查传入的 provider 参数是否有效',
184
+ '确认 API Key 已配置(环境变量或配置文件)',
185
+ ]);
186
+ }
187
+ this._initialized = true;
188
+ // 启动时记忆衰减(insight/archive 来源)
189
+ this.runMemoryDecay();
190
+ // 定期记忆衰减(每小时)
191
+ this.decayTimer = safeSetInterval(() => this.runMemoryDecay(), AGENT_CONSTANTS.DECAY_INTERVAL_MS);
192
+ return pctx;
193
+ }
194
+ /**
195
+ * 流式对话(核心 API)
196
+ */
197
+ async *chat(input, signal) {
198
+ this.assertInitialized('chat');
199
+ if (input.length > AGENT_CONSTANTS.CHAT_INPUT_MAX_LENGTH) {
200
+ throw configError('输入过长', `输入超过最大长度限制(${AGENT_CONSTANTS.CHAT_INPUT_MAX_LENGTH / 1024}KB)`, ['缩短输入内容', '分多次对话发送']);
201
+ }
202
+ if (this._chatBusy) {
203
+ throw configError('对话繁忙', '上一轮对话尚未完成,请等待其结束后再发起新对话', [
204
+ '等待上一轮 chat() 的 AsyncGenerator 耗尽(收到 done 事件)',
205
+ '宿主程序应确保同一时间只有一个 chat() 调用',
206
+ ]);
207
+ }
208
+ this._chatBusy = true;
209
+ // SEC-02: 内部 AbortController,超时时中断 generator 而非仅释放锁
210
+ const internalAbort = new AbortController();
211
+ this.chatAbortController = internalAbort;
212
+ // 合并外部 signal:外部 abort 时也触发内部
213
+ const onExternalAbort = () => internalAbort.abort();
214
+ signal?.addEventListener('abort', onExternalAbort, { once: true });
215
+ // 修复 #4:addEventListener 对已 aborted 的 signal 不触发回调
216
+ // 需手动检查并触发 internalAbort,否则外部已取消的请求仍会进入主流程
217
+ if (signal?.aborted) {
218
+ internalAbort.abort();
219
+ }
220
+ const combinedSignal = internalAbort.signal;
221
+ // 超时保护:LLM 卡死时中断 generator + 释放锁,防止并发
222
+ this.chatLockTimer = safeSetTimeout(() => {
223
+ logger.warn({ timeoutMs: AGENT_CONSTANTS.CHAT_LOCK_TIMEOUT_MS }, 'chat() 锁超时,中断 generator 并释放锁');
224
+ internalAbort.abort();
225
+ this._chatBusy = false;
226
+ this.chatLockTimer = null;
227
+ this.chatAbortController = null;
228
+ }, AGENT_CONSTANTS.CHAT_LOCK_TIMEOUT_MS);
229
+ try {
230
+ this._lastInteractionAt = new Date();
231
+ // 清理上一轮注入的临时 system 消息(recentConversation/skill/recall/truncation)
232
+ // 防止多轮累积:每轮 chat() 开始前,只保留 messages[0] 和非 system 消息
233
+ this.requireNonNull(this.loop, 'loop').cleanTemporarySystemMessages();
234
+ // 基元驱动召回(双通道:语义 + 关键词)
235
+ yield { type: 'thinking', phase: 'recalling' };
236
+ const recalledMemories = await this.recallAndInject(input);
237
+ if (combinedSignal.aborted) {
238
+ yield {
239
+ type: 'aborted',
240
+ reason: this.#config.messages?.abortedByUser ?? 'User cancelled the conversation',
241
+ };
242
+ return;
243
+ }
244
+ // 注入上一轮匹配的技能 prompt
245
+ yield { type: 'thinking', phase: 'processing' };
246
+ this.injectActiveSkill();
247
+ await this.requireNonNull(this.history, 'history').appendUser(input);
248
+ let assistantContent = '';
249
+ let wasAborted = false;
250
+ // 修复 P1-D:原实现 generator 抛错直接传到宿主,宿主未 catch 会变未处理 rejection
251
+ // (表现为"几个字就卡住、无错误日志"——流式输出已开始但错误没机会展示)
252
+ // 改为 try/catch:把 provider 异常转为 yield error chunk,让宿主能优雅展示并清理 UI
253
+ try {
254
+ for await (const chunk of this.requireNonNull(this.loop, 'loop').processUserInput(input, recalledMemories, combinedSignal)) {
255
+ yield chunk;
256
+ if (chunk.type === 'text') {
257
+ assistantContent += chunk.content;
258
+ }
259
+ else if (chunk.type === 'aborted') {
260
+ wasAborted = true;
261
+ }
262
+ }
263
+ }
264
+ catch (err) {
265
+ // aborted 已由 loop.ts 内部 yield chunk 处理,此处只捕获真正的异常
266
+ // (如 LLM 超时、连接断开、AbortError 未被 loop 拦截等)
267
+ // 转为 error chunk 通知宿主,避免裸 throw 导致 UI 卡死
268
+ if (err instanceof DOMException && err.name === 'AbortError') {
269
+ // 翠幕天罗 P1 修复:必须 yield aborted chunk 让宿主能展示中断标记
270
+ // (原仅设置 wasAborted 会导致 chatHandlers catch 不触发,渲染层收不到 ABORTED)
271
+ yield { type: 'aborted', reason: 'User cancelled the conversation' };
272
+ return;
273
+ }
274
+ else {
275
+ yield { type: 'error', message: err instanceof Error ? err.message : String(err) };
276
+ return;
277
+ }
278
+ }
279
+ if (wasAborted) {
280
+ return;
281
+ }
282
+ // 追加助手消息到历史(best-effort:失败不影响用户已收到的回答)
283
+ try {
284
+ await this.requireNonNull(this.history, 'history').appendAssistant(assistantContent);
285
+ }
286
+ catch (err) {
287
+ logger.warn({ err }, '助手消息历史写入失败');
288
+ }
289
+ // 后处理阶段
290
+ yield { type: 'thinking', phase: 'archiving' };
291
+ await this.postProcess(input, assistantContent);
292
+ }
293
+ finally {
294
+ this._chatBusy = false;
295
+ this.chatAbortController = null;
296
+ if (this.chatLockTimer) {
297
+ clearSafeTimeout(this.chatLockTimer);
298
+ this.chatLockTimer = null;
299
+ }
300
+ signal?.removeEventListener('abort', onExternalAbort);
301
+ }
302
+ }
303
+ /**
304
+ * 召回记忆 + 注入最近对话上下文
305
+ *
306
+ * 从 chat() 中提取,职责:
307
+ * 1. 双通道召回(语义 + 关键词)
308
+ * 2. Layer 5: 最近对话注入
309
+ *
310
+ * @param input 用户输入
311
+ * @returns 召回的记忆列表
312
+ */
313
+ async recallAndInject(input) {
314
+ const recalledMemories = await recall(this.requireNonNull(this.pctx, 'projectContext').index, input, {
315
+ limit: 5,
316
+ vectorStore: this.#config.vectorStore,
317
+ excludeSources: this.#config.recallExcludeSources,
318
+ });
319
+ if (recalledMemories.length > 0) {
320
+ this.emit('memoryRecalled', { count: recalledMemories.length, query: input });
321
+ }
322
+ // Layer 5: 最近对话注入
323
+ const loop = this.requireNonNull(this.loop, 'loop');
324
+ const recentHistory = loop.getRecentHistory(3);
325
+ if (recentHistory.length > 0) {
326
+ const msgs = this.#config.messages;
327
+ const label = msgs?.recentConversationLabel ?? '[Recent conversation]';
328
+ const userLabel = msgs?.userLabel ?? 'User';
329
+ const assistantLabel = msgs?.assistantLabel ?? 'Assistant';
330
+ const recentPrompt = `${label}\n` +
331
+ recentHistory
332
+ .map((m) => `${m.role === 'user' ? userLabel : assistantLabel}:${m.content}`)
333
+ .join('\n');
334
+ loop.injectSystemMessage(recentPrompt);
335
+ logger.debug({ turns: recentHistory.length / 2 }, '最近对话已注入');
336
+ }
337
+ return recalledMemories;
338
+ }
339
+ /**
340
+ * 注入上一轮匹配的技能 prompt
341
+ */
342
+ injectActiveSkill() {
343
+ if (this.activeSkill && this.skillManager && this.loop) {
344
+ const skillPrompt = this.skillManager.buildSystemPrompt(this.activeSkill);
345
+ if (skillPrompt) {
346
+ this.loop.injectSystemMessage(skillPrompt);
347
+ logger.debug({ skill: this.activeSkill }, '技能 prompt 已注入');
348
+ }
349
+ this.activeSkill = null;
350
+ }
351
+ }
352
+ /**
353
+ * 对话后处理:用户画像归档、角色匹配、技能匹配、Insight 提取
354
+ *
355
+ * 所有归档/匹配操作均为 best-effort:任何子步骤失败不应影响用户已收到的回答,
356
+ * 失败仅记录日志,不向上抛出异常。
357
+ */
358
+ async postProcess(input, assistantContent) {
359
+ // 用户画像实时归档(语义解析在 agent/ 层,存储在 memory/ 层)
360
+ if (this.#userProfile) {
361
+ try {
362
+ const turnIndex = `turn-${Date.now()}`;
363
+ const facts = extractUserFacts(input, turnIndex);
364
+ // FD-22: 注册到 pendingArchives,确保 close() 时等待后台归档完成,避免写入已关闭的存储
365
+ const archiveFactsPromise = this.#userProfile.archiveFacts(facts).catch((err) => {
366
+ logger.warn({ err }, '用户画像实时归档失败');
367
+ });
368
+ this.requireNonNull(this.history, 'history').registerPendingArchive(archiveFactsPromise);
369
+ }
370
+ catch (err) {
371
+ logger.warn({ err }, '用户画像归档初始化失败');
372
+ }
373
+ }
374
+ // 角色自动匹配(best-effort:失败不阻塞对话结束)
375
+ if (this.personaManager) {
376
+ try {
377
+ const matchedPersona = this.personaManager.autoMatch(input);
378
+ if (matchedPersona) {
379
+ const prevName = this.personaManager.activeName;
380
+ this.personaManager.switchPersona(matchedPersona);
381
+ this.emit('personaSwitched', { from: prevName, to: matchedPersona });
382
+ if (this.loop) {
383
+ const profilePrompt = this.userProfile?.buildSystemPrompt() ?? '';
384
+ const personaPrompt = this.personaManager.buildSystemPrompt();
385
+ const newPrefix = [personaPrompt, profilePrompt].filter(Boolean).join('\n\n') +
386
+ ([personaPrompt, profilePrompt].some(Boolean) ? '\n\n---\n\n' : '');
387
+ this.loop.refreshPersonaPrefix(newPrefix);
388
+ }
389
+ logger.info({ persona: matchedPersona }, '角色自动切换');
390
+ }
391
+ }
392
+ catch (err) {
393
+ logger.warn({ err }, '角色自动匹配失败');
394
+ }
395
+ }
396
+ // 技能关键词匹配(best-effort:失败不阻塞对话结束)
397
+ if (this.skillManager) {
398
+ try {
399
+ const match = this.skillManager.match(input);
400
+ if (match) {
401
+ this.activeSkill = match.skill.name;
402
+ logger.debug({ skill: match.skill.name, score: match.score }, '技能匹配,下一轮注入');
403
+ }
404
+ }
405
+ catch (err) {
406
+ logger.warn({ err }, '技能匹配失败');
407
+ }
408
+ }
409
+ // 输入分类 → Insight 提取(委托给 InsightExtractor)
410
+ if (this.insightExtractor) {
411
+ try {
412
+ const shouldExtract = this.insightExtractor.classify(input);
413
+ if (shouldExtract === 'extract') {
414
+ const p = this.insightExtractor.extract(input, assistantContent).catch((err) => {
415
+ logger.warn({ err }, 'Insight 提取失败');
416
+ return null;
417
+ });
418
+ this.requireNonNull(this.history, 'history').registerPendingArchive(p);
419
+ }
420
+ }
421
+ catch (err) {
422
+ logger.warn({ err }, 'Insight 提取初始化失败');
423
+ }
424
+ }
425
+ // V-201: AutoConfigRefiner(模式 3:Agent 智能总结)
426
+ if (this.autoConfigRefiner) {
427
+ try {
428
+ // FD-22: 注册到 pendingArchives,确保 close() 时等待后台分析完成,避免写入已关闭的存储
429
+ const analyzePromise = this.autoConfigRefiner.analyze(input, assistantContent).catch((err) => {
430
+ logger.warn({ err }, 'AutoConfigRefiner 分析失败');
431
+ });
432
+ this.requireNonNull(this.history, 'history').registerPendingArchive(analyzePromise);
433
+ }
434
+ catch (err) {
435
+ logger.warn({ err }, 'AutoConfigRefiner 初始化失败');
436
+ }
437
+ }
438
+ }
439
+ /**
440
+ * 发送用户消息,非流式返回完整回复
441
+ */
442
+ async chatSync(input, signal) {
443
+ let result = '';
444
+ for await (const chunk of this.chat(input, signal)) {
445
+ if (chunk.type === 'text') {
446
+ result += chunk.content;
447
+ }
448
+ }
449
+ return result;
450
+ }
451
+ /**
452
+ * 分叉当前会话(委托至 SessionManager)
453
+ *
454
+ * 分叉后原会话完整保留,新分支拥有独立消息历史。
455
+ * 记忆索引(IMemoryStorage)全局共享,不受分叉影响。
456
+ */
457
+ forkSession(targetSession) {
458
+ this.assertInitialized('forkSession');
459
+ return this.requireNonNull(this._sessionManager, 'sessionManager').forkSession(targetSession);
460
+ }
461
+ /**
462
+ * 切换到指定项目
463
+ *
464
+ * 切换后自动 rebuildComponents(),无需手动调用。
465
+ * Agent 级记忆(memora.db)保留,项目级配置(.memora/)重新加载。
466
+ */
467
+ async switchProject(nameOrPath) {
468
+ this.assertInitialized('switchProject', ['projectManager', 'provider']);
469
+ // FD-21: 对话进行中切换项目会导致 loop/history 引用被替换,工作记忆与持久化状态不一致
470
+ if (this._chatBusy) {
471
+ throw configError('对话繁忙', '上一轮对话尚未完成,请等待其结束后再切换项目', [
472
+ '等待上一轮 chat() 的 AsyncGenerator 耗尽',
473
+ ]);
474
+ }
475
+ const pm = this.requireNonNull(this.projectManager, 'projectManager');
476
+ const projects = pm.list;
477
+ let target = projects.find((p) => p.name === nameOrPath || p.path === nameOrPath);
478
+ if (!target) {
479
+ const nameOrPathLower = nameOrPath.toLowerCase();
480
+ target = projects.find((p) => p.name.toLowerCase() === nameOrPathLower || p.path.toLowerCase() === nameOrPathLower);
481
+ }
482
+ const projectPath = target ? target.path : nameOrPath;
483
+ const projectName = target ? target.name : basename(nameOrPath);
484
+ const newPctx = await pm.initProject(projectPath, projectName, this.#config.configDir);
485
+ // A-003: 记录源项目路径(用于事件),切换前 pctx 可能不存在(首次初始化)
486
+ const fromProjectPath = this.pctx?.projectPath ?? null;
487
+ this.pctx = newPctx;
488
+ await this.rebuildComponentsWithCurrentCtx();
489
+ // A-003: 发射项目切换事件(供宿主 UI 刷新项目相关界面)
490
+ this.emit('projectSwitched', {
491
+ from: fromProjectPath,
492
+ to: projectPath,
493
+ projectName,
494
+ });
495
+ return newPctx;
496
+ }
497
+ // ─── 组件组装 ─────────────────────────────────────────
498
+ /**
499
+ * 组装所有运行时组件(委托给 assembler 工厂)
500
+ */
501
+ async assembleComponents(pctx) {
502
+ const result = await assembleComponents(pctx, {
503
+ provider: this.provider,
504
+ backgroundProvider: this.#backgroundProvider,
505
+ projectPath: this.#config.projectPath,
506
+ configDir: this.#config.configDir,
507
+ personaName: this.#config.personaName,
508
+ maxContextTokens: this.#config.maxContextTokens,
509
+ sessionStore: this.#config.sessionStore,
510
+ relationStore: this.#config.relationStore,
511
+ tracer: this.#config.tracer,
512
+ messages: this.#config.messages,
513
+ enableContextSummary: this.#config.enableContextSummary,
514
+ existingSkillManager: this.skillManager,
515
+ });
516
+ this.history = result.history;
517
+ this.loop = result.loop;
518
+ this.toolExec = result.toolExec;
519
+ this.personaManager = result.personaManager;
520
+ this.#userProfile = result.userProfile;
521
+ this.skillManager = result.skillManager;
522
+ this.insightExtractor = result.insightExtractor;
523
+ this.configManager = result.configManager;
524
+ this.memoryInspector = result.memoryInspector;
525
+ this.autoConfigRefiner = result.autoConfigRefiner;
526
+ this.workProjection = result.workProjection;
527
+ // V-101:注入 VectorStore 到 MemoryInspector,启用混合搜索
528
+ if (this.memoryInspector && this.#config.vectorStore) {
529
+ this.memoryInspector.setVectorStore(this.#config.vectorStore);
530
+ }
531
+ // 创建会话管理器(通过回调访问当前组件,支持 rebuildComponents 后自动获取最新引用)
532
+ // 事件转发桥接:SessionManager 使用宽类型 (string, Record<string,unknown>),
533
+ // Agent 内部桥接到 TypedEventEmitter 的强类型 emit
534
+ // 运行时校验事件名是否在 AgentEventMap 中,避免不安全的类型断言
535
+ const forwardEvent = (event, data) => {
536
+ if (AGENT_EVENT_NAMES.has(event)) {
537
+ this.emit(event, data);
538
+ }
539
+ };
540
+ this._sessionManager = new SessionManager(() => this.requireNonNull(this.history, 'history'), () => this.requireNonNull(this.loop, 'loop'), this.#config.sessionStore, () => this._chatBusy, forwardEvent);
541
+ }
542
+ /**
543
+ * 用当前 pctx 重建 history / loop
544
+ */
545
+ async rebuildComponentsWithCurrentCtx() {
546
+ if (!this.pctx)
547
+ return;
548
+ await this.assembleComponents(this.pctx);
549
+ // 重建会话管理器:assembleComponents 创建了新的 history/loop 实例
550
+ // 事件转发桥接(同 assembleComponents 中的逻辑,含运行时校验)
551
+ const forwardEvent = (event, data) => {
552
+ if (AGENT_EVENT_NAMES.has(event)) {
553
+ this.emit(event, data);
554
+ }
555
+ };
556
+ this._sessionManager = new SessionManager(() => this.requireNonNull(this.history, 'history'), () => this.requireNonNull(this.loop, 'loop'), this.#config.sessionStore, () => this._chatBusy, forwardEvent);
557
+ }
558
+ // ─── Provider 管理 ────────────────────────────────────
559
+ setProvider(provider) {
560
+ this.#provider = provider;
561
+ if (this.loop) {
562
+ this.loop.setProvider(provider);
563
+ }
564
+ logger.info({ provider: this.#provider.name }, 'Provider 已切换');
565
+ }
566
+ setBackgroundProvider(provider) {
567
+ this.#backgroundProvider = provider;
568
+ // V-201: 同步更新 AutoConfigRefiner 的后台 Provider
569
+ if (this.autoConfigRefiner) {
570
+ this.autoConfigRefiner.setBackgroundProvider(provider);
571
+ }
572
+ logger.info({ hasBackground: !!provider }, '后台 Provider 已切换');
573
+ }
574
+ // ─── 组件访问 ─────────────────────────────────────────
575
+ /**
576
+ * 重建内部组件(history / loop / managers)
577
+ *
578
+ * 通常不需要手动调用——switchProject() 已自动执行 rebuild。
579
+ * 仅在宿主项目需要强制刷新组件时使用(如热更新配置后)。
580
+ */
581
+ async rebuildComponents() {
582
+ // FD-21: 对话进行中重建组件会导致 loop/history 引用被替换,工作记忆与持久化状态不一致
583
+ if (this._chatBusy) {
584
+ throw configError('对话繁忙', '上一轮对话尚未完成,请等待其结束后再重建组件', [
585
+ '等待上一轮 chat() 的 AsyncGenerator 耗尽',
586
+ ]);
587
+ }
588
+ await this.rebuildComponentsWithCurrentCtx();
589
+ }
590
+ // ─── 守卫方法 ───────────────────────────────────────────
591
+ /**
592
+ * 检查 Agent 是否已初始化
593
+ *
594
+ * @param methodName 调用方法名(用于错误消息)
595
+ * @param requires 需要检查的组件列表
596
+ */
597
+ assertInitialized(methodName, requires = ['history', 'loop']) {
598
+ if (!this._initialized) {
599
+ throw configError('Agent 未初始化', '请先调用 init()', [
600
+ `在 ${methodName}() 前调用 await agent.init()`,
601
+ ]);
602
+ }
603
+ const deps = {
604
+ history: this.history,
605
+ loop: this.loop,
606
+ projectManager: this.projectManager,
607
+ provider: this.provider,
608
+ };
609
+ for (const dep of requires) {
610
+ if (!deps[dep]) {
611
+ throw configError('Agent 未初始化', `${dep} 组件不可用`, [
612
+ `在 ${methodName}() 前调用 await agent.init()`,
613
+ ]);
614
+ }
615
+ }
616
+ }
617
+ /**
618
+ * 非空断言守卫——对 null/undefined 值抛出清晰错误的运行时检查
619
+ *
620
+ * @param value 可能为 null/undefined 的值
621
+ * @param name 组件名称(用于错误消息)
622
+ * @throws MemoraError 如果 value 为 null/undefined
623
+ */
624
+ requireNonNull(value, name) {
625
+ if (value === null || value === undefined) {
626
+ throw configError('Agent 未初始化', `${name} 组件不可用`, ['请先调用 await agent.init()']);
627
+ }
628
+ return value;
629
+ }
630
+ // ─── 记忆生命周期 ───────────────────────────────────────
631
+ /**
632
+ * 对 insight/profile/work-projection 记忆执行 score 衰减
633
+ *
634
+ * 长期未访问的记忆 score 逐渐降低,体现"自然遗忘"
635
+ * 不影响 persona/rule/skill(这些是配置型记忆,不应衰减)
636
+ *
637
+ * 衰减逻辑委派给 IMemoryStorage.decayScores(),
638
+ * 宿主(SqliteStorage)可用一条 SQL UPDATE 批量完成,避免 O(n) 全量加载。
639
+ */
640
+ runMemoryDecay() {
641
+ if (!this.pctx)
642
+ return;
643
+ // R-103 衰减 Span:记录衰减执行过程,补全衰减可观测性缺口
644
+ const decaySpan = this.#config.tracer?.startSpan(TRACE_SPANS.DECAY) ?? NOOP_TRACER.startSpan(TRACE_SPANS.DECAY);
645
+ try {
646
+ const sources = [SOURCE_LABELS.INSIGHT, SOURCE_LABELS.PROFILE, SOURCE_LABELS.WORK_PROJECTION];
647
+ const decayedCount = this.pctx.index.decayScores(sources, new Date());
648
+ logger.debug({ decayedCount }, '记忆衰减完成');
649
+ // R-103 衰减指标统计:累计执行次数和衰减记忆数
650
+ this.metricDecayRunCount++;
651
+ this.metricTotalDecayedCount += decayedCount;
652
+ this.metricLastDecayAt = new Date().toISOString();
653
+ decaySpan.setAttribute('decayedCount', decayedCount);
654
+ decaySpan.setAttribute('totalRuns', this.metricDecayRunCount);
655
+ this.emit('decayCompleted', { decayedCount });
656
+ }
657
+ catch (err) {
658
+ logger.warn({ err }, '记忆衰减异常,跳过本轮');
659
+ decaySpan.recordException(toError(err));
660
+ }
661
+ finally {
662
+ decaySpan.end();
663
+ }
664
+ }
665
+ // ─── 关闭 ─────────────────────────────────────────────
666
+ /**
667
+ * 关闭 Agent,释放 SQLite 连接等资源
668
+ */
669
+ async close() {
670
+ // 清理定时器
671
+ if (this.decayTimer) {
672
+ clearSafeInterval(this.decayTimer);
673
+ this.decayTimer = null;
674
+ }
675
+ if (this.chatLockTimer) {
676
+ clearSafeTimeout(this.chatLockTimer);
677
+ this.chatLockTimer = null;
678
+ }
679
+ if (this.chatAbortController) {
680
+ this.chatAbortController.abort();
681
+ this.chatAbortController = null;
682
+ }
683
+ // P2-1 清理 PersonaManager 的角色切换防抖锁计时器,防止关闭后回调触发
684
+ if (this.personaManager) {
685
+ this.personaManager.close();
686
+ }
687
+ this.removeAllListeners();
688
+ if (this.history) {
689
+ await this.history.awaitPendingArchives(5000);
690
+ }
691
+ if (this.projectManager) {
692
+ await this.projectManager.shutdown();
693
+ }
694
+ this._initialized = false;
695
+ this.#backgroundProvider = null;
696
+ this._chatBusy = false;
697
+ this.history = null;
698
+ this.loop = null;
699
+ this._sessionManager = null;
700
+ this.projectManager = null;
701
+ this.insightExtractor = null;
702
+ this.configManager = null;
703
+ this.memoryInspector = null;
704
+ this.autoConfigRefiner = null;
705
+ this.pctx = null;
706
+ }
707
+ // ─── 只读访问器 ───────────────────────────────────────
708
+ get initialized() {
709
+ return this._initialized;
710
+ }
711
+ get context() {
712
+ return this.pctx;
713
+ }
714
+ get agentLoop() {
715
+ return this.loop;
716
+ }
717
+ /**
718
+ * 注入情感基调到 system prompt(Phase 2.1:AffectController)
719
+ *
720
+ * 委托至 AgentLoop.injectAffect(),在角色前缀和 bootstrap 记忆之间插入情感描述。
721
+ * 与角色切换独立——切换角色不会清除情感注入。
722
+ *
723
+ * @param affectString 情感描述文本,传空字符串清除注入
724
+ */
725
+ injectAffect(affectString) {
726
+ this.loop?.injectAffect(affectString);
727
+ }
728
+ get agentHistory() {
729
+ return this.history;
730
+ }
731
+ get provider() {
732
+ return this.#provider;
733
+ }
734
+ get isBusy() {
735
+ return this._chatBusy;
736
+ }
737
+ get lastInteractionAt() {
738
+ return this._lastInteractionAt;
739
+ }
740
+ // ─── R-103 运行时指标 ────────────────────────────────
741
+ /**
742
+ * 获取 Agent 运行时指标快照(R-103 可观测性增强)
743
+ *
744
+ * 聚合 AgentLoop 指标(LLM 调用、记忆召回、工具调用、上下文管理)
745
+ * 与 Agent 层指标(记忆衰减),返回完整的 AgentMetrics 快照。
746
+ *
747
+ * 未初始化时返回全零指标(合理的默认值,不抛异常)。
748
+ * 纯只读、同步、零副作用——适合宿主项目定期轮询构建监控面板。
749
+ *
750
+ * 使用方式:
751
+ * const metrics = agent.getMetrics();
752
+ * console.log(`LLM 调用 ${metrics.llm.callCount} 次,命中率 ${metrics.recall.hitRate}`);
753
+ *
754
+ * @returns AgentMetrics 完整快照(含衰减指标)
755
+ */
756
+ getMetrics() {
757
+ // 未初始化时返回全零指标,避免调用方需要判空
758
+ if (!this.loop) {
759
+ return {
760
+ llm: { callCount: 0, totalInputTokens: 0, totalOutputTokens: 0 },
761
+ recall: { totalCount: 0, hitCount: 0, hitRate: 0 },
762
+ tools: { callCount: 0, failureCount: 0 },
763
+ context: { truncationCount: 0, messageCount: 0, estimatedTokens: 0 },
764
+ decay: {
765
+ runCount: this.metricDecayRunCount,
766
+ totalDecayedCount: this.metricTotalDecayedCount,
767
+ lastRunAt: this.metricLastDecayAt,
768
+ },
769
+ };
770
+ }
771
+ // 获取 AgentLoop 指标快照,填充衰减字段
772
+ const loopMetrics = this.loop.getMetrics();
773
+ return {
774
+ ...loopMetrics,
775
+ decay: {
776
+ runCount: this.metricDecayRunCount,
777
+ totalDecayedCount: this.metricTotalDecayedCount,
778
+ lastRunAt: this.metricLastDecayAt,
779
+ },
780
+ };
781
+ }
782
+ // ─── Manager 暴露(激进拆分:调用方直接操作 Manager)──
783
+ //
784
+ // 设计策略(ADR-010 §Manager 访问器):访问器返回 null,门面方法抛错。
785
+ // - 访问器(getter):返回 Manager | null,供宿主项目链式调用和优雅降级
786
+ // (如 `agent.persona?.activeName`、`if (!agent.memory) return []`)
787
+ // - 门面方法(snapshot/inspect/stats/searchMemories 等):通过
788
+ // assertInitialized + requireNonNull 抛 MemoraError,提供明确错误信息
789
+ // 宿主项目使用访问器时需自行判空,或使用门面方法获得自动错误处理。
790
+ /**
791
+ * 角色管理器(可能为 null)
792
+ *
793
+ * 返回 null 时表示 Agent 未初始化或角色系统未加载。
794
+ * 链式调用建议使用可选链:`agent.persona?.activeName`
795
+ */
796
+ get persona() {
797
+ return this.personaManager;
798
+ }
799
+ /**
800
+ * 工具执行器(可能为 null)
801
+ *
802
+ * 返回 null 时表示 Agent 未初始化。
803
+ */
804
+ get tools() {
805
+ return this.toolExec;
806
+ }
807
+ /**
808
+ * 技能管理器(可能为 null)
809
+ *
810
+ * 返回 null 时表示 Agent 未初始化或技能系统未加载。
811
+ */
812
+ get skills() {
813
+ return this.skillManager;
814
+ }
815
+ /**
816
+ * 配置管理器(可能为 null)—— 规则/技能注入 + 配置建议
817
+ *
818
+ * 返回 null 时表示 Agent 未初始化。
819
+ */
820
+ get config() {
821
+ return this.configManager;
822
+ }
823
+ /**
824
+ * Insight 提取器(可能为 null)—— 输入分类 + 记忆提取
825
+ *
826
+ * 返回 null 时表示 Agent 未初始化或 LLM Provider 未配置。
827
+ */
828
+ get insight() {
829
+ return this.insightExtractor;
830
+ }
831
+ /**
832
+ * 记忆查看器(可能为 null)—— 快照 + 搜索 + 统计
833
+ *
834
+ * 返回 null 时表示 Agent 未初始化或存储层未就绪。
835
+ * 宿主项目常用模式:`const mem = agent.memory; if (!mem) return [];`
836
+ */
837
+ get memory() {
838
+ return this.memoryInspector;
839
+ }
840
+ /**
841
+ * 项目管理器(可能为 null)
842
+ *
843
+ * 返回 null 时表示 Agent 未初始化。
844
+ */
845
+ get projects() {
846
+ return this.projectManager;
847
+ }
848
+ /**
849
+ * 用户画像管理器(可能为 null)—— 实时归档 + 确认/拒绝
850
+ *
851
+ * 返回 null 时表示 Agent 未初始化。
852
+ * 宿主常用模式:
853
+ * - `agent.userProfile?.getPending()` 查询待确认条目
854
+ * - `await agent.userProfile?.confirm(id)` 确认条目
855
+ * - `await agent.userProfile?.reject(id)` 拒绝条目
856
+ */
857
+ get userProfile() {
858
+ return this.#userProfile;
859
+ }
860
+ /**
861
+ * 安全守卫(可能为 null)—— 写入确认回调注册
862
+ *
863
+ * 返回 null 时表示 Agent 未初始化或 projectPath 未设置。
864
+ * 宿主常用模式:
865
+ * - `agent.security?.onWriteConfirmation(callback)` 注册写入确认回调
866
+ * - 回调返回 true 允许写入,返回 false 拒绝写入
867
+ *
868
+ * 详见 M1 写入确认 UI 闭环
869
+ */
870
+ get security() {
871
+ return this.pctx?.security ?? null;
872
+ }
873
+ /**
874
+ * 作品投影管理器(可能为 null)—— 文件内容 → 概要+结构+决策
875
+ *
876
+ * 返回 null 时表示 Agent 未初始化。
877
+ * 宿主常用模式:
878
+ * - `agent.works?.ensureProjection(filePath, content)` 读取文件后触发生成
879
+ * - `agent.works?.loadAll()` 获取所有投影
880
+ */
881
+ get works() {
882
+ return this.workProjection;
883
+ }
884
+ /** 会话管理器(宿主可通过此 getter 访问会话恢复/切换/分叉功能) */
885
+ get sessionManager() {
886
+ return this._sessionManager;
887
+ }
888
+ /** 记忆存储(宿主可直接调用 CRUD,如 delete/upsert) */
889
+ get storage() {
890
+ return this.#config.storage ?? null;
891
+ }
892
+ }
893
+ //# sourceMappingURL=agent.js.map