@zooique/memora 3.0.1 → 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 (282) hide show
  1. package/CHANGELOG.md +161 -1033
  2. package/README.en.md +46 -39
  3. package/README.md +40 -35
  4. package/config.example.json +1 -8
  5. package/dist/agent/agent.d.ts +88 -80
  6. package/dist/agent/agent.d.ts.map +1 -1
  7. package/dist/agent/agent.js +272 -221
  8. package/dist/agent/agent.js.map +1 -1
  9. package/dist/agent/assembler.d.ts +22 -21
  10. package/dist/agent/assembler.d.ts.map +1 -1
  11. package/dist/agent/assembler.js +120 -46
  12. package/dist/agent/assembler.js.map +1 -1
  13. package/dist/agent/backgroundTasks.d.ts +194 -0
  14. package/dist/agent/backgroundTasks.d.ts.map +1 -0
  15. package/dist/agent/backgroundTasks.js +314 -0
  16. package/dist/agent/backgroundTasks.js.map +1 -0
  17. package/dist/agent/budget.d.ts +23 -5
  18. package/dist/agent/budget.d.ts.map +1 -1
  19. package/dist/agent/budget.js +8 -2
  20. package/dist/agent/budget.js.map +1 -1
  21. package/dist/agent/builtinToolHandlers.d.ts +29 -9
  22. package/dist/agent/builtinToolHandlers.d.ts.map +1 -1
  23. package/dist/agent/builtinToolHandlers.js +53 -13
  24. package/dist/agent/builtinToolHandlers.js.map +1 -1
  25. package/dist/agent/builtinTools.d.ts +15 -1
  26. package/dist/agent/builtinTools.d.ts.map +1 -1
  27. package/dist/agent/builtinTools.js +111 -30
  28. package/dist/agent/builtinTools.js.map +1 -1
  29. package/dist/agent/constants.d.ts +31 -6
  30. package/dist/agent/constants.d.ts.map +1 -1
  31. package/dist/agent/constants.js +38 -8
  32. package/dist/agent/constants.js.map +1 -1
  33. package/dist/agent/contextManager.d.ts +8 -0
  34. package/dist/agent/contextManager.d.ts.map +1 -1
  35. package/dist/agent/contextManager.js +45 -3
  36. package/dist/agent/contextManager.js.map +1 -1
  37. package/dist/agent/contextPreparer.d.ts +1 -1
  38. package/dist/agent/contextPreparer.d.ts.map +1 -1
  39. package/dist/agent/contextPreparer.js +15 -10
  40. package/dist/agent/contextPreparer.js.map +1 -1
  41. package/dist/agent/guardRail.d.ts +12 -1
  42. package/dist/agent/guardRail.d.ts.map +1 -1
  43. package/dist/agent/guardRail.js +37 -2
  44. package/dist/agent/guardRail.js.map +1 -1
  45. package/dist/agent/loop.d.ts +319 -34
  46. package/dist/agent/loop.d.ts.map +1 -1
  47. package/dist/agent/loop.js +713 -110
  48. package/dist/agent/loop.js.map +1 -1
  49. package/dist/agent/managers/archiveCoordinator.d.ts +19 -4
  50. package/dist/agent/managers/archiveCoordinator.d.ts.map +1 -1
  51. package/dist/agent/managers/archiveCoordinator.js +25 -4
  52. package/dist/agent/managers/archiveCoordinator.js.map +1 -1
  53. package/dist/agent/managers/chatLockManager.d.ts +3 -9
  54. package/dist/agent/managers/chatLockManager.d.ts.map +1 -1
  55. package/dist/agent/managers/chatLockManager.js +7 -25
  56. package/dist/agent/managers/chatLockManager.js.map +1 -1
  57. package/dist/agent/managers/llmCaller.d.ts.map +1 -1
  58. package/dist/agent/managers/llmCaller.js +27 -1
  59. package/dist/agent/managers/llmCaller.js.map +1 -1
  60. package/dist/agent/managers/loopMetrics.d.ts +14 -0
  61. package/dist/agent/managers/loopMetrics.d.ts.map +1 -1
  62. package/dist/agent/managers/loopMetrics.js +15 -0
  63. package/dist/agent/managers/loopMetrics.js.map +1 -1
  64. package/dist/agent/managers/memoryInspector.d.ts +13 -15
  65. package/dist/agent/managers/memoryInspector.d.ts.map +1 -1
  66. package/dist/agent/managers/memoryInspector.js +38 -49
  67. package/dist/agent/managers/memoryInspector.js.map +1 -1
  68. package/dist/agent/managers/roundSummaryGenerator.d.ts +31 -1
  69. package/dist/agent/managers/roundSummaryGenerator.d.ts.map +1 -1
  70. package/dist/agent/managers/roundSummaryGenerator.js +81 -6
  71. package/dist/agent/managers/roundSummaryGenerator.js.map +1 -1
  72. package/dist/agent/managers/sessionArchiver.d.ts +35 -3
  73. package/dist/agent/managers/sessionArchiver.d.ts.map +1 -1
  74. package/dist/agent/managers/sessionArchiver.js +34 -3
  75. package/dist/agent/managers/sessionArchiver.js.map +1 -1
  76. package/dist/agent/managers/sessionManager.d.ts +21 -88
  77. package/dist/agent/managers/sessionManager.d.ts.map +1 -1
  78. package/dist/agent/managers/sessionManager.js +94 -278
  79. package/dist/agent/managers/sessionManager.js.map +1 -1
  80. package/dist/agent/managers/toolCallHelpers.d.ts +109 -28
  81. package/dist/agent/managers/toolCallHelpers.d.ts.map +1 -1
  82. package/dist/agent/managers/toolCallHelpers.js +93 -37
  83. package/dist/agent/managers/toolCallHelpers.js.map +1 -1
  84. package/dist/agent/messageHistory.d.ts.map +1 -1
  85. package/dist/agent/messageHistory.js +9 -5
  86. package/dist/agent/messageHistory.js.map +1 -1
  87. package/dist/agent/seed/orchestrator.d.ts +12 -1
  88. package/dist/agent/seed/orchestrator.d.ts.map +1 -1
  89. package/dist/agent/seed/orchestrator.js +15 -4
  90. package/dist/agent/seed/orchestrator.js.map +1 -1
  91. package/dist/agent/seed/prepare.d.ts.map +1 -1
  92. package/dist/agent/seed/prepare.js +9 -4
  93. package/dist/agent/seed/prepare.js.map +1 -1
  94. package/dist/agent/seed/types.d.ts +11 -4
  95. package/dist/agent/seed/types.d.ts.map +1 -1
  96. package/dist/agent/seed/types.js +2 -2
  97. package/dist/agent/textSanitize.d.ts +36 -0
  98. package/dist/agent/textSanitize.d.ts.map +1 -0
  99. package/dist/agent/textSanitize.js +55 -0
  100. package/dist/agent/textSanitize.js.map +1 -0
  101. package/dist/agent/toolExecutor.d.ts +97 -26
  102. package/dist/agent/toolExecutor.d.ts.map +1 -1
  103. package/dist/agent/toolExecutor.js +444 -78
  104. package/dist/agent/toolExecutor.js.map +1 -1
  105. package/dist/agent/toolLedger.d.ts +93 -41
  106. package/dist/agent/toolLedger.d.ts.map +1 -1
  107. package/dist/agent/toolLedger.js +114 -42
  108. package/dist/agent/toolLedger.js.map +1 -1
  109. package/dist/agent/toolResultCache.js +1 -1
  110. package/dist/agent/toolResultOffload.d.ts +8 -0
  111. package/dist/agent/toolResultOffload.d.ts.map +1 -1
  112. package/dist/agent/toolResultOffload.js +10 -3
  113. package/dist/agent/toolResultOffload.js.map +1 -1
  114. package/dist/agent/toolRunner.d.ts +16 -7
  115. package/dist/agent/toolRunner.d.ts.map +1 -1
  116. package/dist/agent/toolRunner.js +74 -29
  117. package/dist/agent/toolRunner.js.map +1 -1
  118. package/dist/agent/tracer.d.ts +13 -1
  119. package/dist/agent/tracer.d.ts.map +1 -1
  120. package/dist/agent/types.d.ts +74 -30
  121. package/dist/agent/types.d.ts.map +1 -1
  122. package/dist/code-exec/types.d.ts +9 -0
  123. package/dist/code-exec/types.d.ts.map +1 -1
  124. package/dist/index.d.ts +28 -41
  125. package/dist/index.d.ts.map +1 -1
  126. package/dist/index.js +65 -54
  127. package/dist/index.js.map +1 -1
  128. package/dist/logging/logger.d.ts +4 -1
  129. package/dist/logging/logger.d.ts.map +1 -1
  130. package/dist/logging/logger.js +5 -2
  131. package/dist/logging/logger.js.map +1 -1
  132. package/dist/memory/gcService.d.ts.map +1 -1
  133. package/dist/memory/gcService.js +3 -5
  134. package/dist/memory/gcService.js.map +1 -1
  135. package/dist/memory/inMemorySessionStore.d.ts +2 -2
  136. package/dist/memory/inMemorySessionStore.js +2 -2
  137. package/dist/memory/inMemoryStorage.d.ts +4 -3
  138. package/dist/memory/inMemoryStorage.d.ts.map +1 -1
  139. package/dist/memory/inMemoryStorage.js +2 -2
  140. package/dist/memory/inMemoryStorage.js.map +1 -1
  141. package/dist/memory/nodeSqliteStorage.d.ts +17 -0
  142. package/dist/memory/nodeSqliteStorage.d.ts.map +1 -0
  143. package/dist/memory/nodeSqliteStorage.js +393 -0
  144. package/dist/memory/nodeSqliteStorage.js.map +1 -0
  145. package/dist/memory/projectManager.d.ts +10 -20
  146. package/dist/memory/projectManager.d.ts.map +1 -1
  147. package/dist/memory/projectManager.js +27 -41
  148. package/dist/memory/projectManager.js.map +1 -1
  149. package/dist/memory/roundStore.d.ts +143 -82
  150. package/dist/memory/roundStore.d.ts.map +1 -1
  151. package/dist/memory/roundStore.js +4 -15
  152. package/dist/memory/roundStore.js.map +1 -1
  153. package/dist/memory/roundSummaryLookup.d.ts +26 -0
  154. package/dist/memory/roundSummaryLookup.d.ts.map +1 -0
  155. package/dist/memory/roundSummaryLookup.js +19 -0
  156. package/dist/memory/roundSummaryLookup.js.map +1 -0
  157. package/dist/memory/sessionStore.d.ts +19 -2
  158. package/dist/memory/sessionStore.d.ts.map +1 -1
  159. package/dist/memory/sessionStore.js.map +1 -1
  160. package/dist/memory/sourceValidation.d.ts +2 -13
  161. package/dist/memory/sourceValidation.d.ts.map +1 -1
  162. package/dist/memory/sourceValidation.js +5 -16
  163. package/dist/memory/sourceValidation.js.map +1 -1
  164. package/dist/memory/storageInterface.d.ts +26 -16
  165. package/dist/memory/storageInterface.d.ts.map +1 -1
  166. package/dist/memory/supersedeResolve.d.ts +39 -0
  167. package/dist/memory/supersedeResolve.d.ts.map +1 -0
  168. package/dist/memory/supersedeResolve.js +16 -0
  169. package/dist/memory/supersedeResolve.js.map +1 -0
  170. package/dist/memory/types.d.ts +10 -2
  171. package/dist/memory/types.d.ts.map +1 -1
  172. package/dist/memory/types.js +6 -1
  173. package/dist/memory/types.js.map +1 -1
  174. package/dist/role-pack/rolePackManager.d.ts +74 -33
  175. package/dist/role-pack/rolePackManager.d.ts.map +1 -1
  176. package/dist/role-pack/rolePackManager.js +112 -53
  177. package/dist/role-pack/rolePackManager.js.map +1 -1
  178. package/dist/role-pack/strategyKeys.d.ts +44 -0
  179. package/dist/role-pack/strategyKeys.d.ts.map +1 -1
  180. package/dist/role-pack/strategyKeys.js +55 -0
  181. package/dist/role-pack/strategyKeys.js.map +1 -1
  182. package/dist/role-pack/strategyResolver.d.ts +6 -1
  183. package/dist/role-pack/strategyResolver.d.ts.map +1 -1
  184. package/dist/role-pack/strategyResolver.js +20 -2
  185. package/dist/role-pack/strategyResolver.js.map +1 -1
  186. package/dist/role-pack/types.d.ts +5 -1
  187. package/dist/role-pack/types.d.ts.map +1 -1
  188. package/dist/security/confirmEntries.d.ts +44 -0
  189. package/dist/security/confirmEntries.d.ts.map +1 -0
  190. package/dist/security/confirmEntries.js +59 -0
  191. package/dist/security/confirmEntries.js.map +1 -0
  192. package/dist/security/pathGuard.d.ts +42 -6
  193. package/dist/security/pathGuard.d.ts.map +1 -1
  194. package/dist/security/pathGuard.js +126 -5
  195. package/dist/security/pathGuard.js.map +1 -1
  196. package/dist/skill/skillScriptRunner.d.ts +147 -15
  197. package/dist/skill/skillScriptRunner.d.ts.map +1 -1
  198. package/dist/skill/skillScriptRunner.js +479 -68
  199. package/dist/skill/skillScriptRunner.js.map +1 -1
  200. package/dist/utils/backgroundTask.d.ts +56 -34
  201. package/dist/utils/backgroundTask.d.ts.map +1 -1
  202. package/dist/utils/backgroundTask.js +130 -99
  203. package/dist/utils/backgroundTask.js.map +1 -1
  204. package/dist/utils/configResourceManager.d.ts +6 -2
  205. package/dist/utils/configResourceManager.d.ts.map +1 -1
  206. package/dist/utils/configResourceManager.js +8 -2
  207. package/dist/utils/configResourceManager.js.map +1 -1
  208. package/dist/utils/errors.d.ts +12 -2
  209. package/dist/utils/errors.d.ts.map +1 -1
  210. package/dist/utils/errors.js +12 -2
  211. package/dist/utils/errors.js.map +1 -1
  212. package/dist/utils/eventEmitter.d.ts +76 -23
  213. package/dist/utils/eventEmitter.d.ts.map +1 -1
  214. package/dist/utils/eventEmitter.js +23 -3
  215. package/dist/utils/eventEmitter.js.map +1 -1
  216. package/dist/utils/time.d.ts +8 -5
  217. package/dist/utils/time.d.ts.map +1 -1
  218. package/dist/utils/time.js +8 -5
  219. package/dist/utils/time.js.map +1 -1
  220. package/dist/utils/toError.d.ts +1 -1
  221. package/dist/utils/toError.js +1 -1
  222. package/dist/web-fetch/loginWall.d.ts +39 -0
  223. package/dist/web-fetch/loginWall.d.ts.map +1 -0
  224. package/dist/web-fetch/loginWall.js +78 -0
  225. package/dist/web-fetch/loginWall.js.map +1 -0
  226. package/dist/web-fetch/types.d.ts +8 -0
  227. package/dist/web-fetch/types.d.ts.map +1 -1
  228. package/dist/web-fetch/webFetchProvider.d.ts.map +1 -1
  229. package/dist/web-fetch/webFetchProvider.js +3 -1
  230. package/dist/web-fetch/webFetchProvider.js.map +1 -1
  231. package/dist/web-search/fetchWebSearchProvider.js +28 -23
  232. package/dist/web-search/fetchWebSearchProvider.js.map +1 -1
  233. package/docs/architecture/role-pack-authoring-guide.md +3 -1
  234. package/docs/architecture/role-pack-spec.md +2 -1
  235. package/docs/memora-api-reference.md +1349 -1175
  236. 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
  237. package/docs/memora-/346/216/245/345/205/245/346/214/207/345/215/227.md +91 -52
  238. package/docs/role-pack-/345/274/200/346/224/276/351/224/256/346/214/207/345/215/227.md +148 -148
  239. package/package.json +9 -4
  240. package/role-packs/README.md +41 -40
  241. package/role-packs/memora/345/212/251/346/211/213/rules.md +4 -4
  242. package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/manifest.json +1 -0
  243. package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/persona.md +1 -1
  244. package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/rules.md +1 -0
  245. package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/emotion-wave.md +1 -1
  246. package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/foreshadow.md +1 -1
  247. package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/outline-expand.md +32 -5
  248. 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 +10 -6
  249. package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/story-bible.md +3 -1
  250. 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
  251. package/dist/agent/managers/dedupManager.d.ts +0 -85
  252. package/dist/agent/managers/dedupManager.d.ts.map +0 -1
  253. package/dist/agent/managers/dedupManager.js +0 -248
  254. package/dist/agent/managers/dedupManager.js.map +0 -1
  255. package/dist/agent/managers/goalConsistencyChecker.d.ts +0 -42
  256. package/dist/agent/managers/goalConsistencyChecker.d.ts.map +0 -1
  257. package/dist/agent/managers/goalConsistencyChecker.js +0 -178
  258. package/dist/agent/managers/goalConsistencyChecker.js.map +0 -1
  259. package/dist/agent/managers/llmJudgeHelper.d.ts +0 -21
  260. package/dist/agent/managers/llmJudgeHelper.d.ts.map +0 -1
  261. package/dist/agent/managers/llmJudgeHelper.js +0 -32
  262. package/dist/agent/managers/llmJudgeHelper.js.map +0 -1
  263. package/dist/agent/managers/memoryAdvisor.d.ts +0 -105
  264. package/dist/agent/managers/memoryAdvisor.d.ts.map +0 -1
  265. package/dist/agent/managers/memoryAdvisor.js +0 -322
  266. package/dist/agent/managers/memoryAdvisor.js.map +0 -1
  267. package/dist/agent/managers/memoryGovernance.d.ts +0 -24
  268. package/dist/agent/managers/memoryGovernance.d.ts.map +0 -1
  269. package/dist/agent/managers/memoryGovernance.js +0 -46
  270. package/dist/agent/managers/memoryGovernance.js.map +0 -1
  271. package/dist/memory/governance.d.ts +0 -17
  272. package/dist/memory/governance.d.ts.map +0 -1
  273. package/dist/memory/governance.js +0 -17
  274. package/dist/memory/governance.js.map +0 -1
  275. package/dist/memory/projectRegistry.d.ts +0 -47
  276. package/dist/memory/projectRegistry.d.ts.map +0 -1
  277. package/dist/memory/projectRegistry.js +0 -151
  278. package/dist/memory/projectRegistry.js.map +0 -1
  279. package/dist/utils/recallDefaults.d.ts +0 -16
  280. package/dist/utils/recallDefaults.d.ts.map +0 -1
  281. package/dist/utils/recallDefaults.js +0 -16
  282. package/dist/utils/recallDefaults.js.map +0 -1
@@ -8,7 +8,6 @@ import { chatBusyError, configError } from '../../utils/errors.js';
8
8
  // 会话标识格式契约(SSOT):恢复最近会话时把 sessionId 拆成 (date, session) 供 loadMessages
9
9
  import { isValidSessionId, splitSessionId } from '../../utils/time.js';
10
10
  import { AGENT_CONSTANTS } from '../../agent/constants.js';
11
- import { GoalConsistencyChecker } from '../../agent/managers/goalConsistencyChecker.js';
12
11
  import { SessionStateMachine } from '../../agent/sessionStateMachine.js';
13
12
  /** 会话管理器:经回调访问 Agent 当前组件状态,支持 Agent 重建后自动取最新引用 */
14
13
  export class SessionManager {
@@ -24,17 +23,10 @@ export class SessionManager {
24
23
  stateMachine;
25
24
  /** 当前会话检查点(运行时状态快照) */
26
25
  checkpoint = null;
27
- /** 检查点脏标记(内存态收口):touchCheckpoint 置位、settleCheckpoint 清理(纯内存态,不落盘) */
28
- checkpointDirty = false;
29
- /** 目标一致性校验器(目标版本一致性校验) */
30
- consistencyChecker;
31
- /**
32
- * 连续暂停时间戳数组(防滥用窗口):每次高风险暂停记录时间戳,窗口(1 小时)内连续 2 次则强制降为低风险暂停。
33
- * ⚠ 进程内状态,不入 SessionCheckpoint——重启归零是设计(反滥用卫生状态,非执行状态);1h 窗口自愈防线,勿引入持久化。
34
- */
35
- consecutivePauseTimestamps = [];
36
- /** 连续暂停时间窗口(毫秒),1 小时前的暂停不计入连续计数 */
37
- static CONSECUTIVE_PAUSE_WINDOW_MS = 3_600_000;
26
+ // 删后注记(2026-10-16 · 5.0.0):曾有「连续暂停防滥用」计数器(consecutivePauseTimestamps +
27
+ // CONSECUTIVE_PAUSE_WINDOW_MS)——整条机制从未通电:判据 isPauseLimitReached 零生产消费、
28
+ // 生产路径 pause() 恒传 lowRisk=true 使计数数组恒空,注释却谎称「≥2 次强制降级」。
29
+ // 按 SSOT/不带伤裁决拆除(见 CHANGELOG 5.0.0)。勿加回。
38
30
  // ── 运行时暂停超时检测 ────────────────────────────────
39
31
  /** 暂停超时检测定时器(只在 paused 状态时运行) */
40
32
  _pauseTimeoutTimer = null;
@@ -47,7 +39,6 @@ export class SessionManager {
47
39
  this.isChatBusy = isChatBusy;
48
40
  this.emitEvent = emitEvent;
49
41
  this.stateMachine = new SessionStateMachine('running');
50
- this.consistencyChecker = new GoalConsistencyChecker();
51
42
  }
52
43
  // ─── SSOT 状态机代理(封装 stateMachine,暴露有界接口) ───
53
44
  /** 会话状态(状态机状态) */
@@ -66,14 +57,9 @@ export class SessionManager {
66
57
  get pendingPauseInfo() {
67
58
  return this.stateMachine.pendingPauseInfo;
68
59
  }
69
- /** 检查是否处于暂停态 */
70
- isPaused() {
71
- return this.stateMachine.status === 'paused';
72
- }
73
- /** 检查是否处于运行态 */
74
- isRunning() {
75
- return this.stateMachine.status === 'running';
76
- }
60
+ // 删后注记(2026-10-16 · 5.0.0,按 SSOT/不带伤裁决提前于 DEPRECATE-1 计划删除):曾有
61
+ // isPaused()/isRunning() 两个便捷布尔查询——判据与 status getter 重复(三态两布尔还吞掉 error 态),
62
+ // 内核+宿主+手册零消费的冗余别名。替代物 = `status`(`session.status === 'paused'`)。勿加回(§3.2.1)。
77
63
  /** 待处理暂停请求是否在等待中 */
78
64
  isPausePending() {
79
65
  return this.stateMachine.isPausePending();
@@ -97,29 +83,38 @@ export class SessionManager {
97
83
  }
98
84
  /**
99
85
  * 会话替换 chokepoint:作废所有会话级派生缓存。
100
- * 任何替换 loop 工作记忆(switch/fork/restore)入口都必须经此,新增派生缓存在此注册一处避免"对称的另一半没写完"。
86
+ * 任何替换 loop 工作记忆(switch/fork/load)入口都必须经此,新增派生缓存在此注册一处避免"对称的另一半没写完"。
101
87
  */
102
88
  invalidateSessionDerivedState() {
103
89
  // 真实 loop 必有 resetContextSummary;测试 mock 可能未实现,缺失时 no-op
104
90
  this.getLoop()?.resetContextSummary?.();
105
91
  }
106
- /** 切换当前会话。底层 MessageHistory.switchSession 为纯同步操作,不引入无意义 async 包装。 */
107
- switchSession(newSession) {
108
- // 对话进行中切换会导致消息持久化分散
92
+ /**
93
+ * 会话替换前置闸(SSOT 收口):任何替换 loop 工作记忆(switch/fork/load)的入口都必须经此。
94
+ * 合并「对话进行中」繁忙闸 + 「非 running 态拒绝」状态闸,消除 switch 有状态闸而
95
+ * fork/restore/load 漏闸的对称性缺口(原 switchSession 注释谎称「与 fork/restore 繁忙守卫同构」,
96
+ * 实际 fork/restore/load 无状态闸——属注释与实现不符的带伤,本次修复)。
97
+ * 暂停/错误态替换语义未定义,保守拒绝;宿主未就绪/暂停态触发由宿主 try/catch 兜底转错误通知。
98
+ */
99
+ assertSessionReplaceable(operation) {
100
+ // 对话进行中替换会导致消息持久化分散 / loop 工作记忆被替换
109
101
  if (this.isChatBusy()) {
110
- throw chatBusyError('切换会话');
102
+ throw chatBusyError(operation);
111
103
  }
112
- // 暂停/错误态切换语义未定义,保守拒绝(与 fork/restore 繁忙守卫同构)
104
+ // 暂停/错误态替换语义未定义,保守拒绝(所有替换入口共用此闸,避免状态闸不对称)
113
105
  if (this.stateMachine.status !== 'running') {
114
- throw configError('切换会话', `会话处于 ${this.stateMachine.status} 态,无法切换`, [
115
- '仅 running 态允许 switchSession',
106
+ throw configError(operation, `会话处于 ${this.stateMachine.status} 态,无法替换`, [
107
+ '仅 running 态允许替换工作记忆',
116
108
  ]);
117
109
  }
110
+ }
111
+ /** 切换当前会话。底层 MessageHistory.switchSession 为纯同步操作,不引入无意义 async 包装。 */
112
+ switchSession(newSession) {
113
+ // 替换工作记忆前置闸(SSOT:与 fork/load 共用断言,消除状态闸不对称)
114
+ this.assertSessionReplaceable('切换会话');
118
115
  // 检查点是当前会话的工作状态,切换时清空内存态(检查点纯内存态,不落盘)
119
116
  if (this.checkpoint && this.checkpoint.sessionId !== newSession) {
120
- this.settleCheckpoint(true);
121
117
  this.checkpoint = null;
122
- this.checkpointDirty = false;
123
118
  }
124
119
  const result = this.getHistory().switchSession(newSession);
125
120
  // 会话已替换:作废上下文摘要等派生缓存,避免陈旧摘要注入新会话
@@ -144,11 +139,28 @@ export class SessionManager {
144
139
  ]);
145
140
  }
146
141
  const { date, session } = splitSessionId(sessionId);
142
+ // 切换前记录旧会话身份(getCurrentSessionInfo 的唯一消费方):
143
+ // 旧会话真实存在(history 已初始化)且 ≠ 目标会话时,切换完成后对旧会话触发归档事件——
144
+ // 旧会话由此进入历史列表,其 summary/keyTopics 归档必须在此发生(fire-and-forget,消费方在 Agent 层)
145
+ const previous = this.getCurrentSessionInfo();
146
+ // date/session 任一缺失(history 未初始化/测试 mock 未实现)视为无旧会话,不触发归档事件
147
+ const prevDate = previous?.date;
148
+ const prevSession = previous?.session;
149
+ const previousSessionId = prevDate && prevSession ? `${prevDate}-${prevSession}` : null;
147
150
  // 切换会话身份(内部含 busy/状态守卫 + 作废派生缓存)
148
151
  this.switchSession(session);
149
152
  // 加载/清空工作记忆:空会话 → restoreHistory([]) 清空(缓存已作废,无陈旧注入)
150
153
  const messages = await this.getHistory().loadSessionMessages(date, session);
151
154
  this.applySessionToLoop(messages);
155
+ // 切换成功才广播(失败路径已提前抛出);重复打开同一会话无「切走」语义,不广播
156
+ if (prevDate && prevSession && previousSessionId !== sessionId) {
157
+ this.emitEvent('sessionSwitched', {
158
+ date: prevDate,
159
+ session: prevSession,
160
+ fromSessionId: previousSessionId,
161
+ toSessionId: sessionId,
162
+ });
163
+ }
152
164
  return messages.length;
153
165
  }
154
166
  /** 获取当前会话的日期和会话名(供 switchSession 前触发 content 类归档);history 未初始化返回 null */
@@ -185,9 +197,8 @@ export class SessionManager {
185
197
  * @param targetSession - 可选,自定义新会话名;不传则自动生成
186
198
  */
187
199
  forkSession(roundId, targetSession) {
188
- if (this.isChatBusy()) {
189
- throw chatBusyError('分叉');
190
- }
200
+ // 替换工作记忆前置闸(SSOT:与 switch/load 共用断言)
201
+ this.assertSessionReplaceable('分叉');
191
202
  const history = this.getHistory();
192
203
  // 记录源会话名(含日期前缀完整名,用于事件)
193
204
  const sourceSessionName = history.currentSessionName;
@@ -197,11 +208,10 @@ export class SessionManager {
197
208
  // round-based 模式:通过 roundIds 加载消息并应用到 loop
198
209
  const sessionMessages = history.loadRoundBasedMessages(result.roundIds);
199
210
  this.applySessionToLoop(sessionMessages);
200
- // 关键约束:分叉后清空 checkpoint,防止新分支的 plan/goal 写入源会话检查点。
211
+ // 关键约束:分叉后清空 checkpoint,防止新分支的 plan 等快照内容写入源会话检查点
212
+ // (曾连写 plan/goal,goal 三字段已随 5.0.0 GOAL-DRIFT-1 退役删除)。
201
213
  if (this.checkpoint) {
202
- this.settleCheckpoint(true);
203
214
  this.checkpoint = null;
204
- this.checkpointDirty = false;
205
215
  }
206
216
  this.emitEvent('sessionForked', {
207
217
  from: sourceSessionName,
@@ -216,66 +226,21 @@ export class SessionManager {
216
226
  roundIds: result.roundIds,
217
227
  };
218
228
  }
219
- /**
220
- * 恢复最近活跃会话:经 ISessionStore 加载最近消息到工作记忆;宿主未注入则返回 0。
221
- *
222
- * 会话管理纯度:会话一律手动创建(宿主标题条「+」唯一入口),
223
- * 本方法只做「恢复」、绝不隐式创建会话——无按天归档隐式语义
224
- * (「今天-main 优先」类规则),也无 preferredSession 参数。最近活跃的唯一时间序真理源 = listSessionMetas[0](updatedAt 降序)。
225
- *
226
- * @returns 恢复的消息条数(0 = 无可恢复会话)
227
- */
228
- async restoreMostRecentSession() {
229
- // 对话进行中恢复会导致 loop 工作记忆被替换
230
- if (this.isChatBusy()) {
231
- throw chatBusyError('恢复会话');
232
- }
233
- if (!this.sessionStore) {
234
- logger.debug({ hasSessionStore: false }, '未注入 ISessionStore,无法恢复会话');
235
- return 0;
236
- }
237
- // SSOT:listSessionMetas 依 updatedAt 降序,[0] 即最近活跃会话(唯一时间序真理源)
238
- const metas = this.sessionStore.listSessionMetas();
239
- const target = metas[0]?.sessionId;
240
- if (!target) {
241
- logger.debug({ sessionCount: metas.length }, '没有可恢复的历史会话');
242
- return 0;
243
- }
244
- // 解析 "YYYY-MM-DD-session"(sessionId 格式契约真理源 = splitSessionId,session 名可含连字符)
245
- const parsed = splitSessionId(target);
246
- if (!parsed.session) {
247
- logger.debug({ session: target }, '会话标识格式不匹配');
248
- return 0;
249
- }
250
- const sessionMessages = this.sessionStore.loadMessages(parsed.date, parsed.session);
251
- if (sessionMessages.length === 0) {
252
- logger.debug({ messageCount: 0 }, '没有找到可恢复的历史会话');
253
- return 0;
254
- }
255
- this.applySessionToLoop(sessionMessages);
256
- return sessionMessages.length;
257
- }
258
- /** 恢复指定会话的对话 */
259
- async restoreSession(date, session) {
260
- // 对话进行中恢复会导致 loop 工作记忆被替换
261
- if (this.isChatBusy()) {
262
- throw chatBusyError('恢复会话');
263
- }
264
- const sessionMessages = await this.getHistory().loadSessionMessages(date, session);
265
- if (sessionMessages.length === 0) {
266
- return 0;
267
- }
268
- this.applySessionToLoop(sessionMessages);
269
- return sessionMessages.length;
270
- }
271
- /** 对外暴露的 loadSessionMessages 委托。⚠ 会切换当前会话(更新 currentDate/currentSession),加载后下一条 chat 写入被加载会话 */
272
- async loadSessionMessages(date, session) {
273
- // 对话进行中加载会切换 currentDate/currentSession,导致消息持久化分散
274
- if (this.isChatBusy()) {
275
- throw chatBusyError('加载会话');
276
- }
277
- return this.getHistory().loadSessionMessages(date, session);
278
- }
229
+ // 删后注记(2026-10-16 · 5.0.0,按 SSOT/不带伤裁决提前于 DEPRECATE-1 周期删除):曾有公开
230
+ // restoreMostRecentSession() / restoreSession(date, session) 两个「从 ISessionStore 恢复会话到
231
+ // 工作记忆」入口——宿主零消费(宿主「打开最近/指定会话」走 chatPanel 读自家 HostSessionStore.
232
+ // listSessionMetas + switchToSession 对齐,不经内核恢复入口),且 listSessionMetas 降序契约的
233
+ // 真实消费者在宿主侧。恢复能力仍由 switchToSession(切换+加载,生产路径)与 forkSession 承担。
234
+ // 勿加回;若宿主将来要「重启后恢复内核 session 状态」,应基于 switchToSession 接线而非新开恢复面。
235
+ //
236
+ // 删后注记(2026-10-17 · 5.0.0,用户拍板不走 DEPRECATE-1 周期直删):曾有公开委托
237
+ // loadSessionMessages(date, session)——仅薄委托 MessageHistory.loadSessionMessages(设 currentDate/
238
+ // currentSession + 经 ISessionStore.loadMessages 读消息),是「半个 switchToSession」:改了会话锚点
239
+ // 却不 restore 到 AgentLoop 工作记忆,调用后下一条 chat 写入被加载会话而上下文仍属旧会话 =
240
+ // 语义不完整的陷阱入口(JSDoc ⚠ 即防此)。替代 = switchToSession(切换+加载+应用 loop 的生产路径);
241
+ // 「按 date/session 读消息」的能力真源 = ISessionStore.loadMessages 接口契约(宿主自家 store 已实现
242
+ // 并直读,chatPanel loadMessagesHistory 路径),不随本出口删除而消失。MessageHistory.
243
+ // loadSessionMessages 内部方法保留(switchToSession 内部消费,非公开面)。
279
244
  /** 将会话消息恢复到 AgentLoop 工作记忆 */
280
245
  applySessionToLoop(sessionMessages) {
281
246
  const messages = sessionMessages.map((tm) => ({
@@ -287,25 +252,11 @@ export class SessionManager {
287
252
  this.invalidateSessionDerivedState();
288
253
  }
289
254
  // ─── 不中断工作模型:检查点 + 状态机 ──────────────────
290
- /**
291
- * 检查点必需字段默认值工厂(默认值单一真理源)。
292
- * 仅 createCheckpoint 新建/合并时使用——反序列化采用严格模式,不做静默补齐。
293
- * 必须返回新实例:数组/对象默认值共享引用会让不同检查点互相污染(一个 push 计划任务项另一凭空多出)。
294
- */
295
- static checkpointDefaults() {
296
- return {
297
- mainGoal: '',
298
- currentGoal: '',
299
- goalChangeSeq: 0,
300
- plan: [],
301
- };
302
- }
303
255
  /** 从当前运行时状态创建检查点:快照消息历史与会话标识生成 SessionCheckpoint(纯内存态,
304
256
  * 不落盘——中止/断电走中断轮补全,运行时暂停同 turn 内存续跑) */
305
- createCheckpoint(mainGoal) {
257
+ createCheckpoint() {
306
258
  const history = this.getHistory();
307
259
  const prev = this.checkpoint;
308
- const defaults = SessionManager.checkpointDefaults();
309
260
  // 合并语义:以已有检查点为基底展开,仅覆写本次重算的字段。
310
261
  // 【禁止改回对象字面量整体重建】——整体重建≈隐式字段白名单,任何未显式列出的字段每次 pause 被静默丢弃
311
262
  // (整体重建会使 planItemLog/completedToolCalls/pauseMeta 静默归零,非幂等工具恢复后重复执行)
@@ -323,50 +274,29 @@ export class SessionManager {
323
274
  recovered: false,
324
275
  }
325
276
  : undefined,
326
- mainGoal: mainGoal ?? prev?.mainGoal ?? defaults.mainGoal,
327
- // 不传 mainGoal 时保留已有 currentGoal(防 pause() 内调用覆盖 updateGoal 的更新)
328
- currentGoal: mainGoal ?? prev?.currentGoal ?? prev?.mainGoal ?? defaults.currentGoal,
329
- goalChangeSeq: prev?.goalChangeSeq ?? defaults.goalChangeSeq,
330
- plan: prev?.plan ?? defaults.plan,
277
+ // plan 缺省必须是新数组实例:默认值共享引用会让不同检查点互相污染(一个 push 计划任务项另一凭空多出)
278
+ plan: prev?.plan ?? [],
331
279
  lastHeartbeat: Date.now(),
332
280
  };
333
- // 检查点内容已整体重算,强制清脏(纯内存,无落盘)
334
- this.settleCheckpoint(true);
335
281
  return this.checkpoint;
336
282
  }
337
283
  /**
338
- * 标记检查点已变更(内存写点统一入口)。刷新心跳并置脏标记。
339
- * 所有修改 this.checkpoint 的方法都必须调用本方法,不得直接赋值 lastHeartbeat——否则变更不被后续清脏感知,内存与清脏链路静默分叉。
284
+ * 刷新检查点心跳(内存写点统一入口)。所有修改 `this.checkpoint` 的方法都必须调用本方法,
285
+ * 不得直接赋值 `lastHeartbeat`——心跳是暂停超时判定的兜底依据,绕过本方法会让超时判定读到陈旧值。
286
+ * ⚠️ 检查点不落盘:本方法**只刷心跳,无脏标记**(脏标记随 2026-10-14 CHKPT-3 减法删除——
287
+ * 曾经的 `checkpointDirty` 无外部消费者,属纯内耗)。
340
288
  */
341
289
  touchCheckpoint() {
342
290
  if (!this.checkpoint)
343
291
  return;
344
292
  this.checkpoint.lastHeartbeat = Date.now(); // 心跳唯一写点
345
- this.checkpointDirty = true;
346
- }
347
- /**
348
- * 检查点脏标记清理(**纯内存态**):
349
- * `SessionCheckpoint` 不落盘——中止/断电走「中断轮补全为完整 turn」而非跨重启恢复,
350
- * 运行时暂停是同 turn 内续跑(内存态),无跨进程载体需求。故本方法仅清理脏标记,
351
- * 不再写存储层(保留方法语义边界,避免为纯净化波及 40+ 处调用点)。
352
- */
353
- settleCheckpoint(force = false) {
354
- if (!this.checkpoint)
355
- return;
356
- if (!force && !this.checkpointDirty)
357
- return;
358
- // 纯内存:无落盘动作,仅清脏
359
- this.checkpointDirty = false;
360
293
  }
361
294
  /** 获取当前检查点快照;未创建返回 null */
362
295
  getCheckpoint() {
363
296
  return this.checkpoint;
364
297
  }
365
- /**
366
- * 暂停会话(用户/Agent/系统均可触发)。暂停前自动创建检查点;高风险暂停计入连续计数(防滥用),低风险不计。
367
- * @param lowRisk 低风险暂停不计连续计数(默认 false)
368
- */
369
- pause(reason, source = 'user', lowRisk = false) {
298
+ /** 暂停会话(用户/Agent/系统均可触发)。暂停前自动创建检查点。 */
299
+ pause(reason, source = 'user') {
370
300
  const result = this.stateMachine.pause(reason, source);
371
301
  if (result.allowed) {
372
302
  // 先在 createCheckpoint 前记 pausedAt,检查点经 spread 继承,省去 pause() 内的二次补设分支
@@ -374,19 +304,16 @@ export class SessionManager {
374
304
  this.checkpoint.pausedAt = Date.now();
375
305
  }
376
306
  this.createCheckpoint();
377
- // 首轮暂停无现有检查点,pausedAt 未被 spread 继承,补设并标脏
307
+ // 首轮暂停无现有检查点,pausedAt 未被 spread 继承,补设并刷心跳
378
308
  if (this.checkpoint && !this.checkpoint.pausedAt) {
379
309
  this.checkpoint.pausedAt = Date.now();
380
310
  this.touchCheckpoint();
381
311
  }
382
- // 仅高风险暂停记录时间戳(低风险由 Agent 自动兜底不累积)
383
- if (!lowRisk) {
384
- this.consecutivePauseTimestamps.push(Date.now());
385
- logger.debug({ consecutivePauseCount: this.consecutivePauseTimestamps.length, reason }, '高风险暂停已记录时间戳');
386
- }
387
- else {
388
- logger.debug({ reason }, '低风险暂停不记录时间戳(Agent 自动兜底)');
389
- }
312
+ // pauseMeta 唯一写入点(SSOT):两路暂停——用户 requestPause / ask_user 提问挂起——
313
+ // 全部经本方法翻状态机,故收敛到此处即可全覆盖,各调用方不再各自补写。
314
+ // ⚠ 消费方是**判据**不是展示:clearPlanOnTurnEnd 以 `cp.pauseMeta` 有无决定
315
+ // 「turn 是否真正结束、任务表该不该清」。任一暂停路径漏写,恢复后任务表会被误清。
316
+ this.setPauseMeta({ reason, source });
390
317
  // 启动暂停超时检测,防暂停会话长时间占用资源
391
318
  this.startPauseTimeoutTimer();
392
319
  this.emitEvent('sessionPaused', {
@@ -394,7 +321,7 @@ export class SessionManager {
394
321
  source,
395
322
  sessionId: this.checkpoint?.sessionId,
396
323
  });
397
- logger.info({ reason, source, lowRisk }, '会话已暂停');
324
+ logger.info({ reason, source }, '会话已暂停');
398
325
  }
399
326
  return result.allowed;
400
327
  }
@@ -423,8 +350,6 @@ export class SessionManager {
423
350
  // 走 setPauseMeta 唯一写入口而非内联赋值,避免绕过其兜底链路(其内部已含 touch+settle,此处不再重复调用)
424
351
  this.setPauseMeta(undefined);
425
352
  }
426
- // 连续暂停计数在暂停超时处理(checkPauseTimeout)对称重置,
427
- // 不在 resume() 里清零——避免用户回答前过早清零导致防滥用机制无效
428
353
  this.emitEvent('sessionResumed', {
429
354
  sessionId: this.checkpoint?.sessionId,
430
355
  });
@@ -445,38 +370,12 @@ export class SessionManager {
445
370
  }
446
371
  return result.allowed;
447
372
  }
448
- // ── 防滥用:连续暂停计数 ───────────────────────────────
449
- /** 清理过期暂停时间戳(窗口过滤):移除超过窗口(1 小时)的旧时间戳,仅保留窗口内暂停;每次查询计数时自动调用 */
450
- pruneStalePauseTimestamps() {
451
- const cutoff = Date.now() - SessionManager.CONSECUTIVE_PAUSE_WINDOW_MS;
452
- const before = this.consecutivePauseTimestamps.length;
453
- this.consecutivePauseTimestamps = this.consecutivePauseTimestamps.filter((ts) => ts > cutoff);
454
- const pruned = before - this.consecutivePauseTimestamps.length;
455
- if (pruned > 0) {
456
- logger.debug({ pruned, remaining: this.consecutivePauseTimestamps.length }, '过期暂停时间戳已清理');
457
- }
458
- }
459
- /** 检查连续暂停是否已达上限(防滥用):先清理窗口外时间戳,再查窗口内暂停≥2 次则强制降为低风险暂停。低风险不计数 */
460
- isPauseLimitReached() {
461
- this.pruneStalePauseTimestamps();
462
- return this.consecutivePauseTimestamps.length >= 2;
463
- }
464
- /** 获取当前连续暂停计数:先清理窗口外时间戳,返回窗口内有效暂停数 */
465
- getConsecutivePauseCount() {
466
- this.pruneStalePauseTimestamps();
467
- return this.consecutivePauseTimestamps.length;
468
- }
469
- /** 重置连续暂停计数(防滥用):暂停超时处理后调用。与 resume() 分离,防回答前过早清零致防滥用失效 */
470
- resetConsecutivePauseCount() {
471
- this.consecutivePauseTimestamps = [];
472
- logger.debug('连续暂停时间戳已清空');
473
- }
474
373
  /**
475
374
  * 放弃当前暂停检查点(宿主 stop 语义的内核出口)
476
375
  *
477
376
  * 语义 = 用户决定**彻底放弃**暂停执行,不再 resume。清理内容:
478
377
  * 1. 停暂停超时定时器(防止已放弃的 checkpoint 继续被超时清理)
479
- * 2. 清内存态 checkpoint + checkpointDirty + pauseMeta
378
+ * 2. 清内存态 checkpoint + pauseMeta
480
379
  * 3. 状态机 resetToRunning(回到 idle 之前的 running 基础态,下次 chat() 正常)
481
380
  *
482
381
  * 与 resume() 的区别:resume = 想继续跑暂停点;discardCheckpoint = 不想了,检查点作废。
@@ -494,7 +393,6 @@ export class SessionManager {
494
393
  this.stopPauseTimeoutTimer();
495
394
  // 2. 清内存态(检查点纯内存态不落盘,无存储清理动作)
496
395
  this.checkpoint = null;
497
- this.checkpointDirty = false;
498
396
  this.setPauseMeta(undefined);
499
397
  // 3. 状态机回到 running
500
398
  this.stateMachine.resetToRunning();
@@ -515,8 +413,6 @@ export class SessionManager {
515
413
  this.checkpoint.status = this.stateMachine.status;
516
414
  this.touchCheckpoint();
517
415
  }
518
- // 内存态收口(无落盘:recovered 只活内存,跨进程一律走中断补全)
519
- this.settleCheckpoint();
520
416
  this.emitEvent('sessionRecovered', {
521
417
  sessionId: this.checkpoint?.sessionId,
522
418
  });
@@ -524,52 +420,6 @@ export class SessionManager {
524
420
  }
525
421
  return result.allowed;
526
422
  }
527
- /**
528
- * 更新检查点目标版本:递增 goalChangeSeq 触发漂移检测。漂移处置由内核自兜底不依赖宿主:
529
- * drift 级内核直接低风险暂停强制确认;minor 级仅发射事件+日志不打断执行。
530
- * goalDriftDetected 事件为可观测性出口,宿主可选订阅,不订阅不影响正确性。
531
- */
532
- updateGoal(newGoal) {
533
- if (!this.checkpoint) {
534
- this.createCheckpoint(newGoal);
535
- return null;
536
- }
537
- // 幂等短路:目标值未变直接返回(返回 level='same'),不递增 goalChangeSeq、不触发漂移。
538
- // 续跑/补充输入每轮喂相同 currentGoal,若无短路 goalChangeSeq 会退化为轮次计数器
539
- if (newGoal === this.checkpoint.currentGoal) {
540
- return {
541
- level: 'same',
542
- similarity: 1,
543
- constraints: this.consistencyChecker.extractConstraints(this.checkpoint.mainGoal),
544
- constraintsConsistent: true,
545
- };
546
- }
547
- // 执行一致性校验,与 mainGoal 对比
548
- const mainGoal = this.checkpoint.mainGoal;
549
- const consistencyResult = this.consistencyChecker.checkConsistency(mainGoal, newGoal);
550
- this.checkpoint.currentGoal = newGoal;
551
- this.checkpoint.goalChangeSeq++;
552
- this.touchCheckpoint();
553
- // 目标变更是会话的语义骨架,立即清脏(纯内存,无落盘)
554
- this.settleCheckpoint();
555
- // 检测到漂移则发射 goalDriftDetected 事件;drift 级自动低风险暂停强制用户确认(不计入连续计数)
556
- if (consistencyResult.level !== 'same') {
557
- this.emitEvent('goalDriftDetected', {
558
- sessionId: this.checkpoint.sessionId,
559
- mainGoal,
560
- newGoal,
561
- similarity: consistencyResult.similarity,
562
- level: consistencyResult.level,
563
- constraints: consistencyResult.constraints,
564
- goalChangeSeq: this.checkpoint.goalChangeSeq,
565
- });
566
- if (consistencyResult.level === 'drift') {
567
- const pauseReason = `目标漂移:新目标与原始目标不一致(相似度 ${consistencyResult.similarity.toFixed(2)})`;
568
- this.pause(pauseReason, 'system', true);
569
- }
570
- }
571
- return consistencyResult;
572
- }
573
423
  /** 全量替换计划任务项(与 appendPlanItem 仅追加正交;保留已有任务项 id/status)。唯一生产点:task_table_write mode='replace' */
574
424
  updatePlan(plan) {
575
425
  if (!this.checkpoint)
@@ -638,8 +488,7 @@ export class SessionManager {
638
488
  return cp.plan;
639
489
  }
640
490
  /**
641
- * 更新计划任务项状态(plan 任务项状态的唯一写点)。必须经此写点置 checkpointDirty,
642
- * 否则状态变更可能丢失标脏,计划变更与标脏在此原子完成。
491
+ * 更新计划任务项状态(plan 任务项状态的唯一写点)。
643
492
  *
644
493
  * 写完后自动 ensureActivePlanItem:如果变更导致 active 空缺(如把 active 标记为 done/blocked),
645
494
  * 则推进下一个 pending → active。这保证任何时刻 plan 中恰好有一个 active 任务项。
@@ -680,15 +529,14 @@ export class SessionManager {
680
529
  const firstPending = plan.find((s) => s.status === 'pending');
681
530
  if (firstPending) {
682
531
  firstPending.status = 'active';
683
- // 不 touchCheckpoint——调用方(updatePlanItemStatus / writePlan)会统一标脏
532
+ // 不 touchCheckpoint——调用方(updatePlanItemStatus / writePlan)会统一刷心跳
684
533
  }
685
534
  }
686
535
  /**
687
536
  * 是否存在「在途计划」= 有未完成任务项(pending 或 active)。
688
537
  *
689
- * SSOT:全库该命题唯一实现。消费方三处——会议骨架预置守卫(prepare,判断续会不重开)、
690
- * 任务表 nudge 跳过(loop,已有在途表则不再诱导建表)、canContinueWithoutInput(agent,
691
- * 可续跑信号)。禁任一消费方自行内联谓词。
538
+ * SSOT:全库该命题唯一实现。消费方两处——会议骨架预置守卫(prepare,判断续会不重开)、
539
+ * 任务表 nudge 跳过(loop,已有在途表则不再诱导建表)。禁任一消费方自行内联谓词。
692
540
  *
693
541
  * 口径取 pending||active(非仅 active):与 ensureActivePlanItem 的补偿语义对齐——
694
542
  * plan 非空时恒有一个 active(见 :826 场景 1/2),全 done/blocked 时无 active(场景 3),
@@ -710,9 +558,8 @@ export class SessionManager {
710
558
  this.updatePlanItemStatus(planItemId, 'done');
711
559
  }
712
560
  this.appendPlanItemLog({ planItemId, summary });
713
- // 心跳 + 清脏:step 边界即检查点语义边界(纯内存态;检查点不落盘,崩溃走中断轮补全)
561
+ // 心跳刷新:step 边界即检查点语义边界(纯内存态;检查点不落盘,崩溃走中断轮补全)
714
562
  this.touchCheckpoint();
715
- this.settleCheckpoint();
716
563
  }
717
564
  /** 写 step 边界日志(时间轴投影,不改 plan 状态)。迭代边界
718
565
  * onPlanItemBoundary 只做本写——plan 状态推进唯一写者 = LLM 的 task_table_update。 */
@@ -721,7 +568,6 @@ export class SessionManager {
721
568
  return;
722
569
  this.appendPlanItemLog(options);
723
570
  this.touchCheckpoint();
724
- this.settleCheckpoint();
725
571
  }
726
572
  /**
727
573
  * 兜底收尾(「LLM 未显式 update 即收尾」):turn 正常完成且计划已「全部到达」——
@@ -783,9 +629,8 @@ export class SessionManager {
783
629
  this.checkpoint.plan = [];
784
630
  this.checkpoint.planItemLog = undefined;
785
631
  this.touchCheckpoint();
786
- this.settleCheckpoint();
787
632
  }
788
- /** 设置暂停元数据;传 undefined 清除。写后必须清脏(清除也清脏,纯内存态无磁盘残留) */
633
+ /** 设置暂停元数据;传 undefined 清除。写后刷心跳(纯内存态无磁盘残留) */
789
634
  setPauseMeta(meta) {
790
635
  // 意图:pauseMeta 通常随 pause()(建检查点后)写入,但检查点不一定已存在(首轮/未知状态),
791
636
  // 故保留防御性守卫——无检查点时静默 no-op,不抛弃 pause/resume 链路的其余语义。
@@ -793,7 +638,6 @@ export class SessionManager {
793
638
  return;
794
639
  this.checkpoint.pauseMeta = meta;
795
640
  this.touchCheckpoint();
796
- this.settleCheckpoint();
797
641
  }
798
642
  /**
799
643
  * 计划是否全部阻塞(预判短路收窄专用判定):仅全 blocked 视为真停滞。
@@ -822,7 +666,6 @@ export class SessionManager {
822
666
  // ── 工具执行日志(outbox 模式):补偿管线不启用,以下仅日志 ──
823
667
  /**
824
668
  * 记录工具执行:追加到检查点日志(append-only,内存态)。
825
- * 标脏策略:仅标脏不即时清脏(completePlanItem step 边界统一清脏、createCheckpoint 暂停/异常强制清脏)。
826
669
  * 检查点纯内存态不落盘:completedToolCalls 仅内存态,回合中途崩溃即整体丢弃、走中断轮补全恢复;
827
670
  * 「工具重跑排重」仅在单进程存活期内有效,无跨重启持久化。
828
671
  */
@@ -842,28 +685,15 @@ export class SessionManager {
842
685
  }
843
686
  }
844
687
  this.touchCheckpoint();
845
- // 依赖 completePlanItem / createCheckpoint 在 step 边界统一清脏
846
- }
847
- /** 记录非幂等工具执行(补偿降级后仅日志):不再逐副作用执行补偿,仅记录事实供宿主/人工排查 */
848
- compensateTool(record) {
849
- const msg = `${record.name}(${record.argsSignature.slice(0, 50)}):非幂等工具,需人工确认是否需要补偿`;
850
- logger.warn({ tool: record.name, argsSignature: record.argsSignature.slice(0, 80) }, msg);
851
- return msg;
852
- }
853
- /** 记录所有非幂等工具执行(补偿降级后仅日志):直接记录所有非幂等工具,每条生成一条日志 */
854
- compensateAllNonIdempotent() {
855
- if (!this.checkpoint?.completedToolCalls)
856
- return [];
857
- return this.checkpoint.completedToolCalls
858
- .filter((r) => r.idempotent === 'non-idempotent')
859
- .map((r) => this.compensateTool(r));
860
- }
861
- /** 发送心跳,防僵尸会话 */
862
- heartbeat() {
863
- if (this.checkpoint) {
864
- this.touchCheckpoint();
865
- }
866
688
  }
689
+ // 删后注记(2026-10-16 · 5.0.0,按 SSOT/不带伤裁决提前于 DEPRECATE-1 周期删除):曾有公开
690
+ // compensateTool() / compensateAllNonIdempotent()——「非幂等工具补偿」降级后仅生成文案 +
691
+ // logger.warn,零状态变更、零返回值消费者、生产零调用(仅测试直测),属 no-op 型公开面。
692
+ // 勿加回;真要做人工确认面,应经 tool_start/tool_result 事件流在宿主侧渲染。
693
+ // 删后注记(2026-10-16 · 5.0.0,按 SSOT/不带伤裁决提前于 DEPRECATE-1 计划删除):曾有公开
694
+ // heartbeat()——把私有 touchCheckpoint(心跳唯一写点)包成公开方法,暗示接入者需手动打卡;
695
+ // 实际心跳由每个修改检查点的方法自动刷新,且超时判定时钟钉在 pausedAt(设计已排除手动保活),
696
+ // 生产+宿主零调用。勿加回;真要「暂停保活」需求应开专用 API(见台账 DEPRECATE-1 销项记录)。
867
697
  /**
868
698
  * 标记会话暂停超时(超时事实唯一写点)。广播式:超时信息经事件载荷传递,多监听器并行消费互不干扰。
869
699
  * 由 checkPauseTimeout 调用(唯一调用点,启动路径无跨重启恢复)。
@@ -912,28 +742,14 @@ export class SessionManager {
912
742
  const sessionId = checkpoint.sessionId;
913
743
  const pauseDuration = Date.now() - (checkpoint.pausedAt ?? checkpoint.lastHeartbeat);
914
744
  logger.warn({ sessionId, pauseDuration }, '运行时检测到暂停超时,自动清理检查点');
915
- // 清除检查点 + 状态机回到 running;残留脏标记无对应内存态一并清除
745
+ // 清除检查点 + 状态机回到 running
916
746
  this.checkpoint = null;
917
- this.checkpointDirty = false;
918
747
  this.stateMachine.resetToRunning();
919
- // 暂停超时意味着会话断裂,重置连续暂停计数(递增→衰减→重置闭合)
920
- this.resetConsecutivePauseCount();
921
748
  // 记录超时会话 + 发射事件(唯一入口)
922
749
  this.markSessionTimedOut(sessionId, pauseDuration);
923
750
  // 超时后不再需要继续检测
924
751
  this.stopPauseTimeoutTimer();
925
752
  }
926
- /** 关闭时清脏(纯内存,无落盘):Agent 关闭、destroy 前调用。检查点不落盘,
927
- * 本方法为保留语义边界的 no-op 清脏(无持久化动作) */
928
- flushOnShutdown() {
929
- this.settleCheckpoint(true);
930
- }
931
- /** 立即清脏(纯内存,无落盘):非只读工具完成后调用。⚠️ 检查点不落盘,completedToolCalls 仅内存态——
932
- * 「工具重跑排重」仅在单进程存活期内生效,进程崩溃/重启后失效(走中断轮补全)。无落盘设计,
933
- * 本方法为保留语义边界的 no-op 清脏 */
934
- flushNow() {
935
- this.settleCheckpoint(true);
936
- }
937
753
  /** 销毁 SessionManager,清理所有定时器(防阻止进程退出或悬空回调) */
938
754
  destroy() {
939
755
  this.stopPauseTimeoutTimer();