@zooique/memora 4.0.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (239) hide show
  1. package/CHANGELOG.md +165 -1743
  2. package/README.en.md +43 -36
  3. package/README.md +308 -303
  4. package/dist/agent/agent.d.ts +46 -82
  5. package/dist/agent/agent.d.ts.map +1 -1
  6. package/dist/agent/agent.js +153 -220
  7. package/dist/agent/agent.js.map +1 -1
  8. package/dist/agent/assembler.d.ts +3 -16
  9. package/dist/agent/assembler.d.ts.map +1 -1
  10. package/dist/agent/assembler.js +46 -35
  11. package/dist/agent/assembler.js.map +1 -1
  12. package/dist/agent/backgroundTasks.d.ts +8 -1
  13. package/dist/agent/backgroundTasks.d.ts.map +1 -1
  14. package/dist/agent/backgroundTasks.js +8 -1
  15. package/dist/agent/backgroundTasks.js.map +1 -1
  16. package/dist/agent/budget.d.ts +23 -5
  17. package/dist/agent/budget.d.ts.map +1 -1
  18. package/dist/agent/budget.js +8 -2
  19. package/dist/agent/budget.js.map +1 -1
  20. package/dist/agent/builtinToolHandlers.d.ts +27 -8
  21. package/dist/agent/builtinToolHandlers.d.ts.map +1 -1
  22. package/dist/agent/builtinToolHandlers.js +45 -9
  23. package/dist/agent/builtinToolHandlers.js.map +1 -1
  24. package/dist/agent/builtinTools.d.ts.map +1 -1
  25. package/dist/agent/builtinTools.js +4 -3
  26. package/dist/agent/builtinTools.js.map +1 -1
  27. package/dist/agent/constants.d.ts +0 -6
  28. package/dist/agent/constants.d.ts.map +1 -1
  29. package/dist/agent/constants.js +7 -8
  30. package/dist/agent/constants.js.map +1 -1
  31. package/dist/agent/contextManager.d.ts +8 -0
  32. package/dist/agent/contextManager.d.ts.map +1 -1
  33. package/dist/agent/contextManager.js +45 -3
  34. package/dist/agent/contextManager.js.map +1 -1
  35. package/dist/agent/contextPreparer.d.ts +1 -1
  36. package/dist/agent/contextPreparer.d.ts.map +1 -1
  37. package/dist/agent/contextPreparer.js +15 -10
  38. package/dist/agent/contextPreparer.js.map +1 -1
  39. package/dist/agent/guardRail.d.ts +1 -1
  40. package/dist/agent/guardRail.d.ts.map +1 -1
  41. package/dist/agent/guardRail.js +1 -1
  42. package/dist/agent/guardRail.js.map +1 -1
  43. package/dist/agent/loop.d.ts +46 -13
  44. package/dist/agent/loop.d.ts.map +1 -1
  45. package/dist/agent/loop.js +128 -25
  46. package/dist/agent/loop.js.map +1 -1
  47. package/dist/agent/managers/archiveCoordinator.d.ts +15 -3
  48. package/dist/agent/managers/archiveCoordinator.d.ts.map +1 -1
  49. package/dist/agent/managers/archiveCoordinator.js +13 -7
  50. package/dist/agent/managers/archiveCoordinator.js.map +1 -1
  51. package/dist/agent/managers/chatLockManager.d.ts +3 -9
  52. package/dist/agent/managers/chatLockManager.d.ts.map +1 -1
  53. package/dist/agent/managers/chatLockManager.js +7 -25
  54. package/dist/agent/managers/chatLockManager.js.map +1 -1
  55. package/dist/agent/managers/llmCaller.d.ts.map +1 -1
  56. package/dist/agent/managers/llmCaller.js +27 -1
  57. package/dist/agent/managers/llmCaller.js.map +1 -1
  58. package/dist/agent/managers/memoryInspector.d.ts +12 -7
  59. package/dist/agent/managers/memoryInspector.d.ts.map +1 -1
  60. package/dist/agent/managers/memoryInspector.js +32 -19
  61. package/dist/agent/managers/memoryInspector.js.map +1 -1
  62. package/dist/agent/managers/roundSummaryGenerator.d.ts +3 -1
  63. package/dist/agent/managers/roundSummaryGenerator.d.ts.map +1 -1
  64. package/dist/agent/managers/roundSummaryGenerator.js +14 -4
  65. package/dist/agent/managers/roundSummaryGenerator.js.map +1 -1
  66. package/dist/agent/managers/sessionArchiver.d.ts +20 -6
  67. package/dist/agent/managers/sessionArchiver.d.ts.map +1 -1
  68. package/dist/agent/managers/sessionArchiver.js +19 -5
  69. package/dist/agent/managers/sessionArchiver.js.map +1 -1
  70. package/dist/agent/managers/sessionManager.d.ts +21 -88
  71. package/dist/agent/managers/sessionManager.d.ts.map +1 -1
  72. package/dist/agent/managers/sessionManager.js +77 -278
  73. package/dist/agent/managers/sessionManager.js.map +1 -1
  74. package/dist/agent/seed/orchestrator.d.ts.map +1 -1
  75. package/dist/agent/seed/orchestrator.js +3 -3
  76. package/dist/agent/seed/orchestrator.js.map +1 -1
  77. package/dist/agent/seed/prepare.d.ts.map +1 -1
  78. package/dist/agent/seed/prepare.js +9 -4
  79. package/dist/agent/seed/prepare.js.map +1 -1
  80. package/dist/agent/seed/types.d.ts +11 -4
  81. package/dist/agent/seed/types.d.ts.map +1 -1
  82. package/dist/agent/seed/types.js +2 -2
  83. package/dist/agent/toolExecutor.d.ts +51 -6
  84. package/dist/agent/toolExecutor.d.ts.map +1 -1
  85. package/dist/agent/toolExecutor.js +74 -9
  86. package/dist/agent/toolExecutor.js.map +1 -1
  87. package/dist/agent/toolResultCache.js +1 -1
  88. package/dist/agent/toolRunner.d.ts +4 -2
  89. package/dist/agent/toolRunner.d.ts.map +1 -1
  90. package/dist/agent/toolRunner.js +7 -4
  91. package/dist/agent/toolRunner.js.map +1 -1
  92. package/dist/agent/types.d.ts +8 -25
  93. package/dist/agent/types.d.ts.map +1 -1
  94. package/dist/index.d.ts +9 -20
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/index.js +35 -32
  97. package/dist/index.js.map +1 -1
  98. package/dist/logging/logger.d.ts +4 -1
  99. package/dist/logging/logger.d.ts.map +1 -1
  100. package/dist/logging/logger.js +5 -2
  101. package/dist/logging/logger.js.map +1 -1
  102. package/dist/memory/gcService.d.ts.map +1 -1
  103. package/dist/memory/gcService.js +3 -5
  104. package/dist/memory/gcService.js.map +1 -1
  105. package/dist/memory/inMemorySessionStore.d.ts +2 -2
  106. package/dist/memory/inMemorySessionStore.js +2 -2
  107. package/dist/memory/inMemoryStorage.d.ts +4 -3
  108. package/dist/memory/inMemoryStorage.d.ts.map +1 -1
  109. package/dist/memory/inMemoryStorage.js +2 -2
  110. package/dist/memory/inMemoryStorage.js.map +1 -1
  111. package/dist/memory/nodeSqliteStorage.d.ts +17 -0
  112. package/dist/memory/nodeSqliteStorage.d.ts.map +1 -0
  113. package/dist/memory/nodeSqliteStorage.js +393 -0
  114. package/dist/memory/nodeSqliteStorage.js.map +1 -0
  115. package/dist/memory/projectManager.d.ts +10 -20
  116. package/dist/memory/projectManager.d.ts.map +1 -1
  117. package/dist/memory/projectManager.js +27 -41
  118. package/dist/memory/projectManager.js.map +1 -1
  119. package/dist/memory/roundStore.d.ts +76 -1
  120. package/dist/memory/roundStore.d.ts.map +1 -1
  121. package/dist/memory/roundStore.js.map +1 -1
  122. package/dist/memory/roundSummaryLookup.d.ts +26 -0
  123. package/dist/memory/roundSummaryLookup.d.ts.map +1 -0
  124. package/dist/memory/roundSummaryLookup.js +19 -0
  125. package/dist/memory/roundSummaryLookup.js.map +1 -0
  126. package/dist/memory/sessionStore.d.ts +19 -2
  127. package/dist/memory/sessionStore.d.ts.map +1 -1
  128. package/dist/memory/sessionStore.js.map +1 -1
  129. package/dist/memory/sourceValidation.d.ts +2 -13
  130. package/dist/memory/sourceValidation.d.ts.map +1 -1
  131. package/dist/memory/sourceValidation.js +5 -16
  132. package/dist/memory/sourceValidation.js.map +1 -1
  133. package/dist/memory/storageInterface.d.ts +26 -16
  134. package/dist/memory/storageInterface.d.ts.map +1 -1
  135. package/dist/memory/supersedeResolve.d.ts +39 -0
  136. package/dist/memory/supersedeResolve.d.ts.map +1 -0
  137. package/dist/memory/supersedeResolve.js +16 -0
  138. package/dist/memory/supersedeResolve.js.map +1 -0
  139. package/dist/memory/types.d.ts +3 -2
  140. package/dist/memory/types.d.ts.map +1 -1
  141. package/dist/memory/types.js +2 -1
  142. package/dist/memory/types.js.map +1 -1
  143. package/dist/role-pack/rolePackManager.d.ts +33 -24
  144. package/dist/role-pack/rolePackManager.d.ts.map +1 -1
  145. package/dist/role-pack/rolePackManager.js +48 -42
  146. package/dist/role-pack/rolePackManager.js.map +1 -1
  147. package/dist/role-pack/strategyKeys.d.ts +8 -0
  148. package/dist/role-pack/strategyKeys.d.ts.map +1 -1
  149. package/dist/role-pack/strategyKeys.js +10 -0
  150. package/dist/role-pack/strategyKeys.js.map +1 -1
  151. package/dist/role-pack/strategyResolver.d.ts +6 -1
  152. package/dist/role-pack/strategyResolver.d.ts.map +1 -1
  153. package/dist/role-pack/strategyResolver.js +20 -2
  154. package/dist/role-pack/strategyResolver.js.map +1 -1
  155. package/dist/role-pack/types.d.ts +5 -1
  156. package/dist/role-pack/types.d.ts.map +1 -1
  157. package/dist/security/pathGuard.d.ts +1 -1
  158. package/dist/security/pathGuard.d.ts.map +1 -1
  159. package/dist/security/pathGuard.js +1 -1
  160. package/dist/security/pathGuard.js.map +1 -1
  161. package/dist/utils/backgroundTask.d.ts +56 -34
  162. package/dist/utils/backgroundTask.d.ts.map +1 -1
  163. package/dist/utils/backgroundTask.js +130 -99
  164. package/dist/utils/backgroundTask.js.map +1 -1
  165. package/dist/utils/errors.d.ts +3 -2
  166. package/dist/utils/errors.d.ts.map +1 -1
  167. package/dist/utils/errors.js +3 -2
  168. package/dist/utils/errors.js.map +1 -1
  169. package/dist/utils/eventEmitter.d.ts +20 -22
  170. package/dist/utils/eventEmitter.d.ts.map +1 -1
  171. package/dist/utils/eventEmitter.js +0 -3
  172. package/dist/utils/eventEmitter.js.map +1 -1
  173. package/dist/utils/time.d.ts +3 -3
  174. package/dist/utils/time.d.ts.map +1 -1
  175. package/dist/utils/time.js +3 -3
  176. package/dist/utils/time.js.map +1 -1
  177. package/dist/utils/toError.d.ts +1 -1
  178. package/dist/utils/toError.js +1 -1
  179. package/dist/web-fetch/loginWall.d.ts +39 -0
  180. package/dist/web-fetch/loginWall.d.ts.map +1 -0
  181. package/dist/web-fetch/loginWall.js +78 -0
  182. package/dist/web-fetch/loginWall.js.map +1 -0
  183. package/dist/web-fetch/types.d.ts +8 -0
  184. package/dist/web-fetch/types.d.ts.map +1 -1
  185. package/dist/web-fetch/webFetchProvider.d.ts.map +1 -1
  186. package/dist/web-fetch/webFetchProvider.js +3 -1
  187. package/dist/web-fetch/webFetchProvider.js.map +1 -1
  188. package/dist/web-search/fetchWebSearchProvider.js +28 -23
  189. package/dist/web-search/fetchWebSearchProvider.js.map +1 -1
  190. package/docs/architecture/role-pack-authoring-guide.md +3 -1
  191. package/docs/architecture/role-pack-spec.md +2 -1
  192. package/docs/memora-api-reference.md +1349 -1276
  193. package/docs/memora-/345/215/207/347/272/247/346/214/207/345/215/227-4.x-/345/210/260-5.0.0.md +385 -0
  194. package/docs/memora-/346/216/245/345/205/245/346/214/207/345/215/227.md +87 -49
  195. package/docs/role-pack-/345/274/200/346/224/276/351/224/256/346/214/207/345/215/227.md +148 -148
  196. package/package.json +9 -4
  197. package/role-packs/README.md +41 -40
  198. package/role-packs/memora/345/212/251/346/211/213/rules.md +4 -4
  199. package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/manifest.json +1 -0
  200. package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/persona.md +46 -46
  201. package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/rules.md +16 -16
  202. package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/emotion-wave.md +46 -46
  203. package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/foreshadow.md +29 -29
  204. package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/outline-expand.md +73 -48
  205. package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/project-init/scripts/init-novel-project.mjs +445 -441
  206. package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/story-bible.md +62 -62
  207. package/role-packs//347/231/275/350/257/235/346/226/271/346/241/210/350/256/276/350/256/241/345/270/210/manifest.json +1 -0
  208. package/dist/agent/managers/dedupManager.d.ts +0 -85
  209. package/dist/agent/managers/dedupManager.d.ts.map +0 -1
  210. package/dist/agent/managers/dedupManager.js +0 -248
  211. package/dist/agent/managers/dedupManager.js.map +0 -1
  212. package/dist/agent/managers/goalConsistencyChecker.d.ts +0 -42
  213. package/dist/agent/managers/goalConsistencyChecker.d.ts.map +0 -1
  214. package/dist/agent/managers/goalConsistencyChecker.js +0 -178
  215. package/dist/agent/managers/goalConsistencyChecker.js.map +0 -1
  216. package/dist/agent/managers/llmJudgeHelper.d.ts +0 -21
  217. package/dist/agent/managers/llmJudgeHelper.d.ts.map +0 -1
  218. package/dist/agent/managers/llmJudgeHelper.js +0 -32
  219. package/dist/agent/managers/llmJudgeHelper.js.map +0 -1
  220. package/dist/agent/managers/memoryAdvisor.d.ts +0 -105
  221. package/dist/agent/managers/memoryAdvisor.d.ts.map +0 -1
  222. package/dist/agent/managers/memoryAdvisor.js +0 -322
  223. package/dist/agent/managers/memoryAdvisor.js.map +0 -1
  224. package/dist/agent/managers/memoryGovernance.d.ts +0 -24
  225. package/dist/agent/managers/memoryGovernance.d.ts.map +0 -1
  226. package/dist/agent/managers/memoryGovernance.js +0 -46
  227. package/dist/agent/managers/memoryGovernance.js.map +0 -1
  228. package/dist/memory/governance.d.ts +0 -17
  229. package/dist/memory/governance.d.ts.map +0 -1
  230. package/dist/memory/governance.js +0 -17
  231. package/dist/memory/governance.js.map +0 -1
  232. package/dist/memory/projectRegistry.d.ts +0 -47
  233. package/dist/memory/projectRegistry.d.ts.map +0 -1
  234. package/dist/memory/projectRegistry.js +0 -151
  235. package/dist/memory/projectRegistry.js.map +0 -1
  236. package/dist/utils/recallDefaults.d.ts +0 -16
  237. package/dist/utils/recallDefaults.d.ts.map +0 -1
  238. package/dist/utils/recallDefaults.js +0 -16
  239. package/dist/utils/recallDefaults.js.map +0 -1
@@ -5,8 +5,6 @@
5
5
  * 负责组件组装和核心对话编排,领域专属操作委托给专职 Manager
6
6
  * (RolePackManager / ToolExecutor / SkillManager / MemoryInspector)。
7
7
  */
8
- import { isAbsolute } from 'node:path';
9
- import { getBaseName } from '../utils/path.js';
10
8
  import { AGENT_CONSTANTS } from '../agent/constants.js';
11
9
  import { ProjectManager } from '../memory/projectManager.js';
12
10
  import { SecurityGuard } from '../security/pathGuard.js';
@@ -21,12 +19,10 @@ import { SeedOrchestrator } from '../agent/seed/index.js';
21
19
  import { chatBusyError, configError, isAbortError, isTimeoutAbortSignal, isTimeoutError, } from '../utils/errors.js';
22
20
  import { toError } from '../utils/toError.js';
23
21
  import { ChatLockManager } from '../agent/managers/chatLockManager.js';
24
- import { MemoryGovernance } from '../agent/managers/memoryGovernance.js';
25
22
  import { ArchiveCoordinator, } from '../agent/managers/archiveCoordinator.js';
26
23
  import { TypedEventEmitter, AGENT_EVENTS, AGENT_EVENT_SET, } from '../utils/eventEmitter.js';
27
24
  import { logger } from '../logging/logger.js';
28
25
  import { isValidSessionId, splitSessionId } from '../utils/time.js';
29
- import { getBackgroundTaskStats as readBackgroundTaskStats, awaitBackgroundTasks, } from '../utils/backgroundTask.js';
30
26
  // ─── 模块级常量 ─────────────────────────────────────────
31
27
  // ─── Agent 门面类 ───────────────────────────────────────
32
28
  /**
@@ -45,7 +41,7 @@ export class Agent extends TypedEventEmitter {
45
41
  #backgroundProvider;
46
42
  /** Provider 路由选择器(多模型路由基础,可选) */
47
43
  #providerRouter = null;
48
- // 运行时组件(init 后填充)——宿主 getter 契约字段(projects/memory/works/polish/sessionManager 等)保持独立
44
+ // 运行时组件(init 后填充)——宿主 getter 契约字段(memory/works/polish/sessionManager 等)保持独立
49
45
  projectManager = null;
50
46
  history = null;
51
47
  loop = null;
@@ -56,8 +52,6 @@ export class Agent extends TypedEventEmitter {
56
52
  _rolePackManager = null;
57
53
  // 拆分出的专职 Manager
58
54
  memoryInspector = null;
59
- /** 记忆治理统一门面(L0/L1/L2/L3 + 诊断) */
60
- _governance = null;
61
55
  /** TextPolishManager(文本润色管理器,LLM 语法修正 + 表达优化) */
62
56
  textPolisher = null;
63
57
  /** 会话管理器(从 Agent 拆分出的会话管理职责) */
@@ -70,12 +64,11 @@ export class Agent extends TypedEventEmitter {
70
64
  * 减少门面扁平字段数;宿主 getter 契约字段(memory/works/polish 等)不在此列。
71
65
  */
72
66
  internals = {
73
- dedupManager: null,
74
- memoryAdvisor: null,
75
67
  sessionArchiver: null,
76
68
  sessionNamer: null,
77
69
  roundSummaryGenerator: null,
78
70
  contextPreparer: null,
71
+ backgroundTasks: null,
79
72
  chatLockManager: null,
80
73
  archiveCoordinator: null,
81
74
  seedOrchestrator: null,
@@ -99,12 +92,18 @@ export class Agent extends TypedEventEmitter {
99
92
  * 用 Set 去重——同一 source 只需补执行一次。
100
93
  */
101
94
  pendingConfigReload = new Set();
95
+ /**
96
+ * 对话期间被 chatLock 拒掉的归档模式切换:**末次为准**(不排队——VS Code 设置本身也是后写覆盖)。
97
+ *
98
+ * 与 pendingConfigReload 同族:轮终(chat() / resumeExecution() 的 frame 退出)统一补执行,
99
+ * 语义见 flushPendingTurnEndTasks。null = 无待补执行的切换。
100
+ */
101
+ pendingArchiveMode = null;
102
102
  constructor(opts) {
103
103
  super();
104
104
  this.#config = {
105
105
  projectPath: opts.projectPath,
106
106
  dataDir: opts.dataDir,
107
- registryDir: opts.registryDir,
108
107
  maxContextTokens: opts.maxContextTokens ?? AGENT_CONSTANTS.DEFAULT_MAX_CONTEXT_TOKENS,
109
108
  // per-LLM 输出预算默认值(token):宿主 per-LLM 配置透传;undefined = 不传 max_tokens
110
109
  //(回服务端默认)。与角色包策略 act.outputLimit 的最终关系由 buildChatOptions 取更小值。
@@ -152,12 +151,13 @@ export class Agent extends TypedEventEmitter {
152
151
  // ─── 生命周期 ─────────────────────────────────────────
153
152
  /**
154
153
  * 初始化 Agent:加载索引、组装内部组件
154
+ *
155
+ * ⚠️ **项目在构造时钉死**(`AgentOptions.projectPath`),`init()` **不接受路径覆盖参数**——
156
+ * 形态定案「一个 Agent = 一个项目」:换项目请宿主新建一个 Agent 实例,不要在单实例内改绑
157
+ * (原 `init(projectPathOverride?)` 于 PR-SWITCH-2 删除:它与该原则直接冲突,且全仓零调用)。
155
158
  */
156
- async init(projectPathOverride) {
159
+ async init() {
157
160
  await this.disposePreviousInstance();
158
- if (projectPathOverride) {
159
- this.#config.projectPath = projectPathOverride;
160
- }
161
161
  const pctx = await this.initializeProject();
162
162
  await this.assembleComponents(pctx);
163
163
  this.pctx = pctx;
@@ -190,12 +190,25 @@ export class Agent extends TypedEventEmitter {
190
190
  /**
191
191
  * 注册暂停超时归档处理器(SessionManager 两条超时路径——启动加载与运行时定时器——统一在此消费)
192
192
  * 注册在 init() 内(close() 的 removeAllListeners + init 起始的 disposePreviousInstance 保证实例周期内恰好一个监听器),
193
- * 挂在生命周期稳定的 Agent 上,rebuildComponents() 重建管理器后仍有效。
193
+ * 挂在生命周期稳定的 Agent 上,组件重装配后仍有效。
194
194
  */
195
195
  registerPauseTimeoutArchiver() {
196
196
  this.on(AGENT_EVENTS.sessionPauseTimedOut, (payload) => {
197
197
  // pendingPauseReason 由 resetToRunning() 内部统一清理
198
198
  //(checkPauseTimeout 经 sessionManager resetToRunning 已覆盖,此处无需再补 cancelPendingPause)。
199
+ //
200
+ // 在途提问清理(配对不变量守卫):宿主侧 ASK_TIMEOUT 定时器活在 webview 里,
201
+ // 而本面板未启用 retainContextWhenHidden ⇒ 面板隐藏/销毁即失,提问超时只能由
202
+ // 内核 30min 暂停超时兜底。兜底路径若只清检查点、不清在途提问,messages 尾部会留下
203
+ // assistant(tool_calls) 而 ask_user 无配对 tool 消息——下一次 chat 装配即被
204
+ // OpenAI 兼容端以 400 拒绝。补此调用后与宿主超时路径同构(同一守卫,非新增判据)。
205
+ // cancelAsk 对「无在途提问」天然 no-op,无条件调用安全;失败不阻断归档(隔离而非吞没)。
206
+ try {
207
+ this.cancelAsk();
208
+ }
209
+ catch (err) {
210
+ logger.warn({ err }, '暂停超时清理在途提问失败(不阻断会话归档)');
211
+ }
199
212
  const { sessionId, date, session } = payload;
200
213
  if (!date || !session)
201
214
  return;
@@ -257,7 +270,6 @@ export class Agent extends TypedEventEmitter {
257
270
  this.projectManager = new ProjectManager({
258
271
  dataDir: this.#config.dataDir,
259
272
  storage: this.#config.storage,
260
- registryDir: this.#config.registryDir,
261
273
  // SecurityGuard 由 Agent 层创建,解除 memory→security 反向依赖
262
274
  createSecurityGuard: (projectPath, memoraDir, configDir, agentDataDir) => new SecurityGuard(projectPath, memoraDir, this.#config.allowedPaths, this.#config.confirmWrites, this.#config.permission, this.#config.confirmScripts, configDir ?? this.#config.configDir, agentDataDir ?? this.#config.dataDir),
263
275
  });
@@ -281,7 +293,7 @@ export class Agent extends TypedEventEmitter {
281
293
  createPostInitComponents(_pctx) {
282
294
  // chat() 并发锁管理器(生命周期与 Agent 实例一致)
283
295
  this.internals.chatLockManager = new ChatLockManager();
284
- // 孤儿 Round 垃圾回收服务:roundStore+storage 均为构造期稳定注入(不随 rebuildComponents 变化),
296
+ // 孤儿 Round 垃圾回收服务:roundStore+storage 均为构造期稳定注入(不随组件重装配变化),
285
297
  // 每日周期清理 refCount=0 且超龄的孤立问答闭环(崩溃残留 pending / 已删会话遗留),防止存储膨胀。
286
298
  // shouldSkip:对话进行中跳过本次(minAgeMs 判龄对长任务不可靠,防进行中轮被误清)
287
299
  if (this.#config.roundStore && this.#config.storage) {
@@ -305,11 +317,9 @@ export class Agent extends TypedEventEmitter {
305
317
  this.emit(AGENT_EVENTS.sessionTitleUpdated, { sessionId, title });
306
318
  },
307
319
  });
308
- // 记忆治理统一门面(去重 / 冲突 / 诊断)
309
- this._governance = new MemoryGovernance(this.internals.dedupManager, this.internals.memoryAdvisor);
310
320
  // 种子 turn 编排器(turn 唯一编排真理源):依赖 sessionManager/loop/history 等
311
321
  // 均已就绪(assembleComponents 已完成 + sessionNamer 本方法前段创建),在此构造一次。
312
- // getParts() 惰性取当前组件——rebuildComponents(switchProject)更换组件后仍取到最新引用。
322
+ // getParts() 惰性取当前组件——组件重装配后仍取到最新引用。
313
323
  this.internals.seedOrchestrator = this.createSeedOrchestrator();
314
324
  }
315
325
  /**
@@ -323,7 +333,7 @@ export class Agent extends TypedEventEmitter {
323
333
  */
324
334
  createSeedOrchestrator() {
325
335
  return new SeedOrchestrator({
326
- // 组件快照经 getter 惰性取当前引用:rebuildComponents 更换组件后仍读到最新(关键)
336
+ // 组件快照经 getter 惰性取当前引用:组件重装配后仍读到最新(关键)
327
337
  getParts: () => ({
328
338
  loop: this.loop,
329
339
  history: this.history,
@@ -333,8 +343,11 @@ export class Agent extends TypedEventEmitter {
333
343
  sessionNamer: this.internals.sessionNamer,
334
344
  roundSummaryGenerator: this.internals.roundSummaryGenerator,
335
345
  }),
346
+ // 后台任务槽位调度器(实例级,装配期由 assembler 创建):摘要/会话命名 fire-and-forget 经此
347
+ backgroundTasks: this.internals.backgroundTasks,
336
348
  tracer: this.#config.tracer ?? null,
337
- archiveMode: this.#config.archiveMode,
349
+ // 归档模式经 getter 惰性取:setArchiveMode 轮内/轮终改值后 span 标注仍为真(同 getParts)
350
+ archiveMode: () => this.#config.archiveMode,
338
351
  messages: this.#config.messages,
339
352
  // 门面私有能力经回调注入 seed(物理实现仍在门面)
340
353
  applyRolePackToolExposure: () => this.applyRolePackToolExposure(),
@@ -387,7 +400,7 @@ export class Agent extends TypedEventEmitter {
387
400
  // turn 结束自动收尾:非暂停态无条件清理任务表(turn 内能力,不跨 turn 残留);
388
401
  // 清空前产出 plan_snapshot 快照 chunk(任务项完成态落盘真源)
389
402
  yield* this.clearPlanOnTurnEnd();
390
- await this.flushPendingConfigReload();
403
+ await this.flushPendingTurnEndTasks();
391
404
  }
392
405
  }
393
406
  /**
@@ -413,9 +426,32 @@ export class Agent extends TypedEventEmitter {
413
426
  return { myToken, internalAbort, cleanupExternalSignal, combinedSignal: internalAbort.signal };
414
427
  }
415
428
  /**
416
- * 补执行对话期间暂存的配置重载请求
429
+ * 补执行对话期间暂存的**轮终任务**(当前两类:配置重载 source、归档模式切换)
430
+ *
431
+ * 挂载点 = **两处**都必须挂,缺一即同族缺陷(见 clearPlanOnTurnEnd 的「单一收口点」注释):
432
+ * - chat() 的 finally(本文件 chat() 生成器收尾块)
433
+ * - resumeExecution() 的 finally——缺它则「暂停→续跑」期间暂存的项滞留到下一次 chat(),
434
+ * 用户若不再发问则**永久丢失**(2026-10-16 实证:此前仅 chat() 挂点存在,该缺陷为空头承诺)。
435
+ *
436
+ * 位置约束:必须在 release(myToken) **之后** ⇒ 到达时 chatLock 已释放,此处再申请不会撞 busy。
437
+ * 语义 = 「流退出」(generator frame exit)而非「turn 真正收场(非 paused)」:
438
+ * 暂停边界同样会执行本方法,这与「暂停期间直接改 = 即时生效」的现状一致(ADR-041 §2)。
439
+ *
440
+ * 失败策略:**成功才清槽**。失败保留暂存 + logger.warn,顺延下一个安全点(幂等保证重复应用无害)。
441
+ * ⚠ 下方 reloadConfig 段仍是既有的「先 clear 再尝试」(失败即丢失)——同族债已登记,本批不动其行为。
417
442
  */
418
- async flushPendingConfigReload() {
443
+ async flushPendingTurnEndTasks() {
444
+ if (this.pendingArchiveMode !== null) {
445
+ const mode = this.pendingArchiveMode;
446
+ try {
447
+ // 复用公开入口而非直改字段:幂等判定与 archiveModeChanged 回执走同一条路径
448
+ this.setArchiveMode(mode);
449
+ this.pendingArchiveMode = null; // 仅成功后清槽
450
+ }
451
+ catch (err) {
452
+ logger.warn({ err, mode }, '轮终补执行归档模式切换失败,保留暂存待下一个安全点');
453
+ }
454
+ }
419
455
  if (this.pendingConfigReload.size === 0)
420
456
  return;
421
457
  const pending = Array.from(this.pendingConfigReload);
@@ -430,16 +466,9 @@ export class Agent extends TypedEventEmitter {
430
466
  }
431
467
  }
432
468
  }
433
- /**
434
- * 强制释放对话锁
435
- *
436
- * 宿主在"无进展超时"确认 generator 挂起后调用,使用户能立即发起新对话(否则需等锁超时)。
437
- * 安全机制委托 ChatLockManager.forceRelease:递增 token 让原 chat() 的 finally 跳过清理、abort signal 让响应点退出;
438
- * 原 generator 仍可能后台运行但不影响新调用。幂等(未忙时 no-op)。
439
- */
440
- forceReleaseChatLock() {
441
- this.internals.chatLockManager?.forceRelease();
442
- }
469
+ // 删后注记(2026-10-16 · 5.0.0):曾有公开 forceReleaseChatLock()——注释称宿主在「无进展超时」
470
+ // 后调用以强行释放对话锁,实际宿主全仓零真实调用(仅一句注释提及),承诺的兜底从未接线。
471
+ // 底层 ChatLockManager.forceRelease 随之无调用者,一并拆除(close 路径仍用 dispose 释放锁)。勿加回。
443
472
  /**
444
473
  * 发送用户消息,非流式返回完整回复
445
474
  */
@@ -536,6 +565,9 @@ export class Agent extends TypedEventEmitter {
536
565
  // 两处 turn-end 清理统一为 clearPlanOnTurnEnd(单一收口点),plan 严格 turn 内、不跨 turn 残留;
537
566
  // 清空前产出 plan_snapshot 快照 chunk(任务项完成态落盘真源)
538
567
  yield* this.clearPlanOnTurnEnd();
568
+ // 与 chat() 同构:补执行对话期间暂存的轮终任务(归档模式 / 配置重载)。
569
+ // 缺本行 ⇒ 「暂停→续跑」半程内被拒切的偏好滞留到下一次 chat(),用户不再发问则永久丢失。
570
+ await this.flushPendingTurnEndTasks();
539
571
  }
540
572
  }
541
573
  /**
@@ -555,17 +587,16 @@ export class Agent extends TypedEventEmitter {
555
587
  let paused = false;
556
588
  try {
557
589
  for await (const chunk of source) {
558
- // 内核事实驱动:loop 在 step 边界真正挂起时状态机才翻 PAUSED,非申请即翻转;
559
- // 与 requestPause 空闲分支同源,须传 lowRisk=true 保持同一暂停事件契约(否则流中暂停计入连续暂停配额)
590
+ // 内核事实驱动:loop 在 step 边界真正挂起时状态机才翻 PAUSED,非申请即翻转
560
591
  if (chunk.type === 'paused') {
561
592
  const pendingInfo = this._sessionManager?.consumePendingPause();
562
- // 暂停收口统一写 pauseMeta:reason/source 取自 pendingPause(与状态机一致)。
563
- // ask_user 主动提问等"直接暂停路径"不走 loop.onPaused,在此自然补齐,
564
- // 重启后宿主可展示"为什么暂停 + 问了什么"
593
+ // 原因/来源唯一真源 = 状态机 pendingPause:用户暂停与提问挂起**同源**注册
594
+ // (提问挂起经 assembler 的 onPendingQuestion → requestPause(reason,'agent'),
595
+ // 见 assembler.ts)。此处不另设第二载体,否则同一事实两个真源即漂移。
565
596
  const pauseReason = pendingInfo?.reason ?? '用户主动暂停';
566
597
  const pauseSource = pendingInfo?.source ?? 'user';
567
- this.pause(pauseReason, pauseSource, true);
568
- this._sessionManager?.setPauseMeta({ reason: pauseReason, source: pauseSource });
598
+ // pauseMeta 不在此写——唯一写入点已收口到 SessionManager.pause()(本方法转发调用)。
599
+ this.pause(pauseReason, pauseSource);
569
600
  paused = true;
570
601
  }
571
602
  yield chunk;
@@ -642,11 +673,10 @@ export class Agent extends TypedEventEmitter {
642
673
  }
643
674
  /**
644
675
  * 暂停会话(用户/Agent/系统均可触发,暂停前自动创建检查点)
645
- * @param lowRisk 低风险暂停不计入连续暂停计数(默认 false)
646
676
  */
647
- pause(reason, source = 'user', lowRisk = false) {
677
+ pause(reason, source = 'user') {
648
678
  this.assertInitialized('pause');
649
- return this.requireSessionManager.pause(reason, source, lowRisk);
679
+ return this.requireSessionManager.pause(reason, source);
650
680
  }
651
681
  /**
652
682
  * 恢复会话(从 PAUSED 恢复到 RUNNING)
@@ -669,7 +699,7 @@ export class Agent extends TypedEventEmitter {
669
699
  const status = sm.status;
670
700
  if (status === 'error') {
671
701
  throw configError('会话异常', '会话处于异常状态,无法接收新消息', [
672
- '先标记 error.recovered=true 并调用 agent.recover()',
702
+ '先标记 error.recovered=true 并调用 agent.sessionManager.recover()',
673
703
  ]);
674
704
  }
675
705
  if (status === 'paused') {
@@ -705,7 +735,7 @@ export class Agent extends TypedEventEmitter {
705
735
  // 空闲守卫:无活跃流时暂停无消费方——若此时同步翻状态机并落盘 checkpoint,
706
736
  // 「turn 已完成后的暂停」会把会话钉在 paused,后续新输入被宿主路由成
707
737
  // supplement(新意图被吞成"上一个回答的补充",毒化闭环节点;loop 无流消费的延迟翻转亦无意义)。
708
- // 任务已结束 = 暂停申请作废,返回 false 供宿主明确反馈;系统/Agent 触发(ask_user/drift)均在
738
+ // 任务已结束 = 暂停申请作废,返回 false 供宿主明确反馈;ask_user 提问挂起在
709
739
  // 流中(_flowActive=true)不经过此分支,不受影响。
710
740
  // ⚠ 判据必须用 _flowActive(流活跃)而非 chatLock.isBusy(锁持有)——chatLock 锁
711
741
  // 180s 超时后自动释放(不中断生成流),长任务运行超锁期后 isBusy=false 但流
@@ -850,14 +880,13 @@ export class Agent extends TypedEventEmitter {
850
880
  this.assertInitialized('answerQuestion');
851
881
  return this.requireLoop.answerQuestion(answers);
852
882
  }
853
- /**
854
- * 追加计划任务项(SESSION_APPEND_TASK 落点):在现有 plan 末尾追加新任务项
855
- */
856
- appendPlanItem(description) {
857
- this.assertInitialized('appendPlanItem');
858
- const sm = this.requireSessionManager;
859
- return sm.appendPlanItem(description);
860
- }
883
+ // 删后注记(2026-10-17 · 5.0.0,PUB-API-PRUNE-2):Agent 面类曾有 4 个纯转发门面——
884
+ // appendPlanItem / triggerError / recover / injectSystemMessage——均 3-13 行薄委托
885
+ // (sessionManager / loop 层实现),宿主零调用 + 手册零承诺 + 真身在对应类公开可用
886
+ // (sessionManager.appendPlanItem/triggerError/recover、loop.injectSystemMessage)。
887
+ // 与 loadSessionMessages 同型裁决:转门口不留。getActiveStrategy 经复核**保留**——
888
+ // 它不是转发门面(rolePackManager 无同名方法),是 Agent 级策略读取器且内部自用
889
+ // (buildChatOptionsFromStrategy),「读当前生效策略」属合理外部能力。
861
890
  /**
862
891
  * 卸载运行态挂载物:清空检查点计划与任务项推进日志(宿主任务流结束/停止/异常广播 idle 前调用),
863
892
  * 回到"空闲 = 无挂载物"的资源层常态;会话历史与记忆等资源层内容不受影响。
@@ -906,49 +935,15 @@ export class Agent extends TypedEventEmitter {
906
935
  sm.clearPlan();
907
936
  }
908
937
  }
909
- /**
910
- * 内核→宿主信号:当前会话是否可"无输入续跑"(决定暂停按钮显隐 + 暂停后继续 UI)
911
- *
912
- * 真值条件:① 状态机已 paused(已软暂停必可续跑,最高优先级——pauseRequested 在下一 step 边界才挂起,
913
- * 彼时 inAutonomousStep 已重置为 false,仅看它会误报);② loop 在自主工具步(可暴露暂停按钮在边界挂起);
914
- * ③ 存在未完成的计划任务项(多轮任务可续跑下一轮)。三者皆否(纯单轮、无待续目标)→ 隐藏暂停按钮(仅停止)。
915
- */
916
- canContinueWithoutInput() {
917
- if (this._sessionManager?.status === 'paused')
918
- return true;
919
- // 错误态不展示"继续":error 态由检查点恢复回填进入,须显式处理(重新开始或 recover),避免点了静默无反应
920
- if (this._sessionManager?.status === 'error')
921
- return false;
922
- // 仅 pending/active(可推进)任务项计入"可续跑"——blocked 不续(全 blocked 由 isPlanAllBlocked 拦,
923
- // 避免全 blocked 计划按钮可点但 resumeExecution 早退)。判定走 SessionManager.hasInflightPlan
924
- // 单一真理源(禁内联谓词,SSOT 见 sessionManager.ts hasInflightPlan 注释),与 prepare/loop 同源。
925
- const hasPendingPlan = this._sessionManager?.hasInflightPlan() ?? false;
926
- return this.requireLoop.isInAutonomousStep || hasPendingPlan;
927
- }
928
- /**
929
- * 触发会话异常(公开 API,宿主显式调用):仅 RUNNING 状态可触发,异常时自动创建检查点保存状态。
930
- * 生产内部无调用者——运行时异常(LLM 超时/工具失败)走 yield {type:'error'} 事件流不翻状态机;
931
- * 本 API 供宿主在自定义异常(如外部服务故障)显式触发,或经检查点恢复回填进入 ERROR 态。
932
- */
933
- triggerError(cause) {
934
- this.assertInitialized('triggerError');
935
- return this.requireSessionManager.triggerError(cause);
936
- }
937
- /**
938
- * 从异常恢复:校验恢复条件(error.recovered === true 且 cause 已解除)
939
- */
940
- recover() {
941
- this.assertInitialized('recover');
942
- return this.requireSessionManager.recover();
943
- }
944
938
  /**
945
939
  * 创建会话检查点:快照当前运行时状态(热记忆等),生成纯内存态快照
946
940
  * (不落盘、无序列化/恢复路径——中止/断电走「中断轮补全为完整 turn」)。
947
- * 无 role/standard 会话能力位,createCheckpoint 仅收 mainGoal。
941
+ * 返回值恒非空:未 init 时由 assertInitialized/requireSessionManager 抛错(configError),
942
+ * 不返回 null——null 仅属于 getCheckpoint()(首个检查点尚未创建)。
948
943
  */
949
- createCheckpoint(mainGoal) {
944
+ createCheckpoint() {
950
945
  this.assertInitialized('createCheckpoint');
951
- return this.requireSessionManager.createCheckpoint(mainGoal);
946
+ return this.requireSessionManager.createCheckpoint();
952
947
  }
953
948
  /**
954
949
  * 获取当前检查点
@@ -957,25 +952,10 @@ export class Agent extends TypedEventEmitter {
957
952
  this.assertInitialized('getCheckpoint');
958
953
  return this.requireSessionManager.getCheckpoint();
959
954
  }
960
- /**
961
- * 获取最近一次处理线程的工具执行历史(loop 域聚合出口,补待办 #3)
962
- *
963
- * 数据源自 sessionManager 检查点的 completedToolCalls(已由
964
- * AGENT_CONSTANTS.COMPLETED_TOOL_CALLS_MAX 做 FIFO 封顶)。本方法剥离「恢复/幂等」内部
965
- * 语义,提供稳定的 execute 域只读契约——宿主可在透明面板/观测面消费本轮及历史的工具调用
966
- * 明细(工具名 / 成败 / 结果摘要),与累计 AgentMetrics.tools 互补。
967
- *
968
- * @param limit 返回条数上限(默认取封顶上限,超界由调用方控制,取最近 limit 条)
969
- * @returns 工具执行记录(记录顺序:时间正序即旧→新;无记录返回空数组;返回浅拷贝防宿主改动检查点)
970
- */
971
- getRecentToolExecutions(limit = AGENT_CONSTANTS.COMPLETED_TOOL_CALLS_MAX) {
972
- this.assertInitialized('getRecentToolExecutions');
973
- const calls = this.requireSessionManager.getCheckpoint()?.completedToolCalls;
974
- if (!calls || calls.length === 0)
975
- return [];
976
- // 取最近 limit 条并浅拷贝,避免宿主引用改动污染检查点(FIFO 封顶后 slice 安全)
977
- return calls.slice(-limit).map((r) => ({ ...r }));
978
- }
955
+ // 删后注记(2026-10-16 · 5.0.0,按 SSOT/不带伤裁决提前于 DEPRECATE-1 周期删除):曾有公开
956
+ // getRecentToolExecutions()——聚合检查点 completedToolCalls 的「工具执行历史」出口,
957
+ // 内核+宿主+手册三面零消费(loop 域 tool_start/tool_result 事件流才是宿主可自聚合的真源,
958
+ // 本出口是多余中间层)。勿加回;观测需求经事件流自聚合。
979
959
  /**
980
960
  * 后台任务只读快照(宿主 UI 出口 · 见 `docs/方案-后台任务可见性与kill入口-20261004.md` §2.1)
981
961
  *
@@ -1005,12 +985,6 @@ export class Agent extends TypedEventEmitter {
1005
985
  this.assertInitialized('killBackgroundTask');
1006
986
  return this.requireLoop.killBackgroundTask(taskId);
1007
987
  }
1008
- /**
1009
- * 向 loop 注入 system 消息
1010
- */
1011
- injectSystemMessage(message) {
1012
- this.requireLoop.injectSystemMessage(message);
1013
- }
1014
988
  /**
1015
989
  * 从指定 Round 位置分叉当前会话(委托至 SessionManager):
1016
990
  * round-based 模式唯一分叉方式——创建新会话,复制 Round ID 列表(指针复制),
@@ -1064,58 +1038,6 @@ export class Agent extends TypedEventEmitter {
1064
1038
  }
1065
1039
  await namer.ensureSessionTitle(result.date, result.newSession, firstUserContent);
1066
1040
  }
1067
- /**
1068
- * 切换到指定项目:切换后自动 rebuildComponents();Agent 级记忆存储(宿主注入,实例不随项目切换重建)保留,项目级配置(.memora/)重新加载
1069
- *
1070
- * `nameOrPath` 只接受两种输入:① 项目注册表中的项目名(依赖注册表跨项目共享,见 dataDir/registryDir);
1071
- * ② 项目根目录的**绝对路径**。二者都不满足时抛 configError——否则相对串会以 process.cwd()
1072
- * 为基准解析,静默造出非预期的项目目录并占用其锁。
1073
- */
1074
- async switchProject(nameOrPath) {
1075
- this.assertInitialized('switchProject', ['projectManager', 'provider']);
1076
- // 对话进行中切换项目会导致 loop/history 引用被替换,工作记忆与持久化状态不一致
1077
- this.assertNotBusy('切换项目');
1078
- const pm = this.projectManager;
1079
- const projects = pm.list;
1080
- let target = projects.find((p) => p.name === nameOrPath || p.path === nameOrPath);
1081
- if (!target) {
1082
- const nameOrPathLower = nameOrPath.toLowerCase();
1083
- target = projects.find((p) => p.name.toLowerCase() === nameOrPathLower || p.path.toLowerCase() === nameOrPathLower);
1084
- }
1085
- // 注册表未命中时按路径处理。相对路径会以 process.cwd() 为基准解析——宿主进程的 cwd
1086
- // 不具项目语义,会静默创建出非预期的项目目录并占用其锁,故只接受绝对路径。
1087
- if (!target && !isAbsolute(nameOrPath)) {
1088
- throw configError('项目切换失败', `「${nameOrPath}」既不是已注册的项目名,也不是绝对路径`, [
1089
- '按名切换:先经 agent.projects.registerProject(项目路径, 项目名) 注册,并保证注册表跨项目共享(多项目共用同一 dataDir 或 registryDir)',
1090
- '按路径切换:传入项目根目录的绝对路径',
1091
- ]);
1092
- }
1093
- const projectPath = target ? target.path : nameOrPath;
1094
- const projectName = target ? target.name : getBaseName(nameOrPath);
1095
- const newPctx = await pm.initProject(projectPath, projectName, this.#config.configDir);
1096
- // 记录源项目路径(用于事件),切换前 pctx 可能不存在(首次初始化)
1097
- const fromProjectPath = this.pctx?.projectPath ?? null;
1098
- // 先尝试重建组件,失败时回滚 pctx 防止状态不一致
1099
- try {
1100
- this.pctx = newPctx;
1101
- await this.rebuildComponents();
1102
- }
1103
- catch (err) {
1104
- // 回滚:恢复旧 pctx(若存在)
1105
- if (fromProjectPath) {
1106
- logger.warn({ err: toError(err) }, 'switchProject: 组件重建失败,回滚项目上下文');
1107
- this.pctx = { projectPath: fromProjectPath };
1108
- }
1109
- throw err;
1110
- }
1111
- // 发射项目切换事件(供宿主 UI 刷新项目相关界面)
1112
- this.emit(AGENT_EVENTS.projectSwitched, {
1113
- from: fromProjectPath,
1114
- to: projectPath,
1115
- projectName,
1116
- });
1117
- return newPctx;
1118
- }
1119
1041
  // ─── 组件组装 ─────────────────────────────────────────
1120
1042
  /**
1121
1043
  * 组装所有运行时组件(一行委托给 assembler 工厂)
@@ -1200,8 +1122,6 @@ export class Agent extends TypedEventEmitter {
1200
1122
  this.emit(AGENT_EVENTS.rolePackSwitchLocked, { reason, lockedSeconds });
1201
1123
  });
1202
1124
  this.memoryInspector = result.memoryInspector;
1203
- this.internals.dedupManager = result.dedupManager;
1204
- this.internals.memoryAdvisor = result.memoryAdvisor;
1205
1125
  this.workProjection = result.workProjection;
1206
1126
  this.internals.sessionArchiver = result.sessionArchiver;
1207
1127
  this.textPolisher = result.textPolisher;
@@ -1210,6 +1130,9 @@ export class Agent extends TypedEventEmitter {
1210
1130
  this._sessionManager = result.sessionManager;
1211
1131
  // 输入增强管线由组装器创建,Agent 只保留编排调用点
1212
1132
  this.internals.contextPreparer = result.contextPreparer;
1133
+ // 后台任务槽位调度器(实例级):assembler 创建时已注入 toolExec(命中 touch);
1134
+ // 此处持有 → 下方创建 seedOrchestrator 时注入 SeedDeps(摘要/命名)+ close() 单点等待。
1135
+ this.internals.backgroundTasks = result.backgroundTasks;
1213
1136
  }
1214
1137
  // ─── Provider 管理 ────────────────────────────────────
1215
1138
  setProvider(provider) {
@@ -1284,10 +1207,29 @@ export class Agent extends TypedEventEmitter {
1284
1207
  }
1285
1208
  // ─── 归档模式管理 ───────────────────────────
1286
1209
  /**
1287
- * 运行时切换归档模式:对话进行中禁止切换(避免本轮 postProcess 行为不一致),无变更幂等返回
1210
+ * 运行时切换归档模式:**对话进行中不即时生效** → 暂存期望值 + 抛错,由 flushPendingTurnEndTasks 补执行。
1211
+ *
1212
+ * 契约与 reloadConfig(source) 逐字同构:
1213
+ * - busy 时 **既暂存、又抛错**——抛错是「已排期到轮终」的传输方式,不是「静默接受」。
1214
+ * 宿主据此走 catch 分支向用户如实说明「运行时将在当前对话结束后生效」(settingsPanel.setArchiveMode)。
1215
+ * - 补执行点:chat() / resumeExecution() 的 finally(流退出),见 flushPendingTurnEndTasks。
1216
+ *
1217
+ * ⚠ 守卫用 chatLock.isBusy(`assertNotBusy` 同源),而该锁 180s 超时会**自动释放且不中断生成流**
1218
+ * (见 ChatLockManager.acquire 计时器)⇒ 长轮跑过 180s 后本方法会即时生效而非暂存,
1219
+ * 此时本轮的**后半程**即按新模式归档。这是既有判据的缺陷,非本方法引入;
1220
+ * 判据统一问题已登记专项(本批不动判据),此处如实注明而非回避。
1288
1221
  */
1289
1222
  setArchiveMode(mode) {
1290
- this.assertNotBusy('切换归档模式');
1223
+ if (this.internals.chatLockManager?.isBusy) {
1224
+ // 先暂存后抛:任何一次拒切都排期到轮终,用户偏好既不会丢也不会被伪造为「已生效」
1225
+ // (最坏情况还有「装配期读持久化配置注入」兜底,见 extension.ts getOrCreateAgent)。
1226
+ this.pendingArchiveMode = mode;
1227
+ logger.info({ mode }, '对话进行中,归档模式切换已暂存,将在流退出时补执行');
1228
+ throw chatBusyError('切换归档模式');
1229
+ }
1230
+ // 清槽落点必须在幂等 return **之前**(三条出口全覆盖):
1231
+ // 否则「即时改成当前值」会命中幂等直接 return,旧暂存仍在轮终覆盖用户最后一次表态。
1232
+ this.pendingArchiveMode = null;
1291
1233
  const prev = this.#config.archiveMode;
1292
1234
  if (prev === mode)
1293
1235
  return; // 幂等:无变更直接返回
@@ -1440,7 +1382,9 @@ export class Agent extends TypedEventEmitter {
1440
1382
  }
1441
1383
  // ─── 配置重载(事件驱动) ───────────────────────
1442
1384
  /**
1443
- * 重载配置类记忆:从 configDir 重新扫描指定 source 并更新内存缓存(设定记忆纯文件装载,不写记忆库),使当前会话立即生效(无需重启)。
1385
+ * 从 configDir 重新扫描指定 source 并更新内存缓存(设定记忆纯文件装载,不写记忆库)。
1386
+ * **空闲时**使当前会话立即生效(无需重启);**对话进行中**则暂存 source 并抛错(见下方 busy 分支),
1387
+ * 由 flushPendingTurnEndTasks 在流退出时补执行——故此处不宜读作「无条件立即生效」。
1444
1388
  * - 'skill' → SkillManager.reload() 重扫 skills/
1445
1389
  * - 'rolePack' → RolePackManager.reload() 重扫 role-packs/(保持激活角色,刷新 AgentLoop 前缀)
1446
1390
  * - 'rule' → 经角色包管理机制热更新(即 rolePack)
@@ -1497,17 +1441,6 @@ export class Agent extends TypedEventEmitter {
1497
1441
  return result;
1498
1442
  }
1499
1443
  // ─── 组件访问 ─────────────────────────────────────────
1500
- /**
1501
- * 重建内部组件(history / loop / managers):通常由 switchProject() 自动触发,仅强制刷新时手动调用
1502
- */
1503
- async rebuildComponents() {
1504
- // 对话进行中重建会导致 loop/history 引用被替换,工作记忆与持久化状态不一致
1505
- this.assertNotBusy('重建组件');
1506
- if (!this.pctx)
1507
- return;
1508
- // assembleComponents 内部重建 history/loop/managers,并返回新创建的 sessionManager
1509
- await this.assembleComponents(this.pctx);
1510
- }
1511
1444
  // ─── 守卫方法 ───────────────────────────────────────────
1512
1445
  /**
1513
1446
  * 检查 Agent 是否已初始化(methodName 用于错误消息,requires 为需检查的组件列表)
@@ -1622,20 +1555,25 @@ export class Agent extends TypedEventEmitter {
1622
1555
  this.internals.gcService?.stopPeriodic();
1623
1556
  this.internals.gcService = null;
1624
1557
  // 等待 WorkProjection 无 inflight 概念(作品投影改为用户主动触发同步写,无后台 LLM 任务)
1625
- // 背景任务统一由下方 awaitBackgroundTasks 兜底
1626
- // 关键约束:等待 backgroundTask 全局在途任务完成(如 touchScores)。
1558
+ // 背景任务统一由下方调度器等待兜底
1559
+ // 关键约束:等待**本实例**后台任务在途任务完成(如 touchScores)。
1627
1560
  // 否则 close 后后台任务仍可能 upsert 已关闭的 storage,触发写失效或异常。
1628
- try {
1629
- const awaited = await awaitBackgroundTasks(AGENT_CONSTANTS.SHUTDOWN_ARCHIVE_TIMEOUT_MS);
1630
- if (awaited > 0) {
1631
- logger.debug({ awaited }, 'close: 背景任务等待结束(awaited = 初始在途数,超时放弃时非完成数)');
1561
+ const backgroundTasks = this.internals.backgroundTasks;
1562
+ if (backgroundTasks) {
1563
+ try {
1564
+ const awaited = await backgroundTasks.awaitPending(AGENT_CONSTANTS.SHUTDOWN_ARCHIVE_TIMEOUT_MS);
1565
+ // 超时后丢弃**未启动**的排队任务(只清队列,不中断在途)——防关后从队列启动新任务写已关 storage
1566
+ const discarded = backgroundTasks.cancelPending();
1567
+ if (awaited > 0) {
1568
+ logger.debug({ awaited, discarded }, 'close: 背景任务等待结束(awaited = 初始在途数,超时放弃时非完成数)');
1569
+ }
1570
+ }
1571
+ catch (err) {
1572
+ logger.warn({ err: toError(err) }, 'close: 后台任务等待失败');
1632
1573
  }
1633
1574
  }
1634
- catch (err) {
1635
- logger.warn({ err: toError(err) }, 'close: awaitBackgroundTasks 失败');
1636
- }
1637
- // 后台「命令」任务实例终态收割——与上方 awaitBackgroundTasks 是同名异物:
1638
- // 上方等的是归档/打分类内存任务,此处杀的是注册表持有的 shell 子进程。
1575
+ // 后台「命令」任务实例终态收割——与上方 BackgroundTaskScheduler 是同名异物:
1576
+ // 上方等的是归档/打分/命名类内存副作用,此处杀的是注册表(BackgroundTaskRegistry)持有的 shell 子进程。
1639
1577
  // turn 终态刻意脱管不杀(跨轮存活),但注册表按 Agent 实例隔离,实例销毁后任务
1640
1578
  // 不可寻址,不杀就是 OS 层孤儿(观察点 ⑧ 真机实锤:父进程退出后 ping.exe 仍在)。
1641
1579
  // 本调用同时注销注册表的 exit 兜底钩子(close 内同步杀树若因关窗窗口赶不上,
@@ -1650,17 +1588,14 @@ export class Agent extends TypedEventEmitter {
1650
1588
  if (sm?.isPausePending()) {
1651
1589
  const pendingInfo = sm.pendingPauseInfo;
1652
1590
  logger.warn({ reason: pendingInfo?.reason, source: pendingInfo?.source }, 'close() 时存在未消费的暂停请求,已自动清理');
1653
- // 若状态机仍 running,同步翻 paused 使检查点内存态一致(无需触发挂起,仅修正一致性);
1654
- // 系统清理不消耗连续暂停配额 → lowRisk=true
1591
+ // 若状态机仍 running,同步翻 paused 使检查点内存态一致(无需触发挂起,仅修正一致性)
1655
1592
  if (sm.status === 'running') {
1656
- sm.pause('close 清理残留暂停', 'system', true);
1593
+ sm.pause('close 清理残留暂停', 'system');
1657
1594
  }
1658
1595
  sm.cancelPendingPause();
1659
1596
  }
1660
1597
  // 清理 SessionManager(须先于 nullifyAllComponents,destroy 需访问其内部状态)
1661
1598
  if (this._sessionManager) {
1662
- // 清脏后再 destroy(纯内存,无落盘;检查点不落盘,本调用为保留语义边界的 no-op)
1663
- this._sessionManager.flushOnShutdown();
1664
1599
  this._sessionManager.destroy();
1665
1600
  }
1666
1601
  // ArchiveCoordinator 无定时器,仅释放引用
@@ -1692,6 +1627,7 @@ export class Agent extends TypedEventEmitter {
1692
1627
  // 清理次要状态字段,防止 re-init 后残留上一会话状态
1693
1628
  this._lastInteractionAt = null;
1694
1629
  this.pendingConfigReload.clear();
1630
+ this.pendingArchiveMode = null;
1695
1631
  }
1696
1632
  /**
1697
1633
  * 统一 null 化所有组件字段(新增 Manager 时在此追加一行);带副作用的清理仍由 close() 显式调用,顺序敏感不可合并
@@ -1704,7 +1640,6 @@ export class Agent extends TypedEventEmitter {
1704
1640
  this.skillManager = null;
1705
1641
  this._rolePackManager = null;
1706
1642
  this.memoryInspector = null;
1707
- this._governance = null;
1708
1643
  this.workProjection = null;
1709
1644
  this.textPolisher = null;
1710
1645
  this._sessionManager = null;
@@ -1712,12 +1647,11 @@ export class Agent extends TypedEventEmitter {
1712
1647
  this.pctx = null;
1713
1648
  // 纯内部组件聚合整体置 null(新增内部组件只需在字段声明与 createPostInitComponents 登记)
1714
1649
  this.internals = {
1715
- dedupManager: null,
1716
- memoryAdvisor: null,
1717
1650
  sessionArchiver: null,
1718
1651
  sessionNamer: null,
1719
1652
  roundSummaryGenerator: null,
1720
1653
  contextPreparer: null,
1654
+ backgroundTasks: null,
1721
1655
  chatLockManager: null,
1722
1656
  archiveCoordinator: null,
1723
1657
  seedOrchestrator: null,
@@ -1772,18 +1706,21 @@ export class Agent extends TypedEventEmitter {
1772
1706
  get lastInteractionAt() {
1773
1707
  return this._lastInteractionAt;
1774
1708
  }
1775
- /** 记忆治理统一门面(语义去重 / 来源健康诊断 / 冲突检测 / 建议推荐),Agent 上的记忆管理方法统一收口于此(无显式衰减层面) */
1776
- get governance() {
1777
- return this._governance;
1778
- }
1709
+ // 删后注记(2026-10-18 · 5.0.0,批次 GOVERNANCE-SRC-1 方案②「整链退役」,用户拍板):
1710
+ // 曾有公开 getter `governance`(MemoryGovernance 门面:deduplicate / detectConflicts /
1711
+ // sourceHealth / suggest)。退役理由 = **结构性恒空**,非「暂时没人用」:四个方法里三个
1712
+ // 逐 `GOVERNANCE_SOURCES` 取样,而该常量自「设定记忆归角色包」后固定为空数组 ⇒
1713
+ // deduplicate 恒 0 条 / detectConflicts 恒 skippedReason / suggest(undefined) 恒 [];
1714
+ // 仅 sourceHealth 走 getAllSources() 有效,而其信息已由 `memory.stats().bySource` 覆盖。
1715
+ // 治理模型现为写时取代(roundSummaryGenerator 的 supersedeSimilar + supersededBy)。
1716
+ // 连带拆除:MemoryAdvisor / DedupManager / GOVERNANCE_SOURCES / llmJudgeHelper /
1717
+ // recallDefaults / dedupCompleted 事件 + 宿主 suggestions 通道。勿加回。
1779
1718
  // ─── 运行时指标 ────────────────────────────────
1780
- /**
1781
- * 后台任务统计快照(宿主观测后台副作用健康度,纯只读转发 utils 单例)。
1782
- * @returns 在途 / 成功 / 失败计数
1783
- */
1784
- getBackgroundTaskStats() {
1785
- return readBackgroundTaskStats();
1786
- }
1719
+ // 删后注记(2026-10-16 · 5.0.0,按 SSOT/不带伤裁决提前于 DEPRECATE-1 周期删除):曾有公开
1720
+ // getBackgroundTaskStats()——转发 utils 后台任务统计快照(pending/completed/failed),
1721
+ // 宿主零消费;连带 utils 层 getBackgroundTaskStats 函数、BackgroundTaskStats 接口与
1722
+ // stats 的 completed/failed 只写字段一并拆除(pending 仍服务后台任务等待/收口)。
1723
+ // 勿加回;后台健康度观测需求出现时经事件流或专用诊断面重建。
1787
1724
  /**
1788
1725
  * 获取 Agent 运行时指标快照(可观测性):聚合 AgentLoop 指标与 Agent 层治理指标;
1789
1726
  * 未初始化返回全零默认值(不抛异常),纯只读同步零副作用,适合宿主定期轮询监控面板。
@@ -1851,10 +1788,6 @@ export class Agent extends TypedEventEmitter {
1851
1788
  get memory() {
1852
1789
  return this.memoryInspector;
1853
1790
  }
1854
- /** 项目管理器(返回 null 表示 Agent 未初始化) */
1855
- get projects() {
1856
- return this.projectManager;
1857
- }
1858
1791
  /** 安全守卫(写入确认回调注册:`onWriteConfirmation` 返回 true 放行、false 拒绝;未初始化/projectPath 未设为 null) */
1859
1792
  get security() {
1860
1793
  return this.pctx?.security ?? null;