@orion-agents/orion-code 0.1.7 → 0.1.9

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 (431) hide show
  1. package/CHANGELOG.md +445 -0
  2. package/README.md +60 -41
  3. package/README.zh-CN.md +34 -8
  4. package/dist/agents/coordinator.d.ts.map +1 -1
  5. package/dist/agents/coordinator.js +49 -9
  6. package/dist/agents/coordinator.js.map +1 -1
  7. package/dist/agents/fork.d.ts +5 -1
  8. package/dist/agents/fork.d.ts.map +1 -1
  9. package/dist/agents/fork.js +9 -10
  10. package/dist/agents/fork.js.map +1 -1
  11. package/dist/agents/worker-pool.d.ts.map +1 -1
  12. package/dist/agents/worker-pool.js +8 -3
  13. package/dist/agents/worker-pool.js.map +1 -1
  14. package/dist/cli.js +6 -6
  15. package/dist/cli.js.map +1 -1
  16. package/dist/commands/context-tool-command-handlers.d.ts.map +1 -1
  17. package/dist/commands/context-tool-command-handlers.js +42 -0
  18. package/dist/commands/context-tool-command-handlers.js.map +1 -1
  19. package/dist/commands/core-command-handlers.d.ts.map +1 -1
  20. package/dist/commands/core-command-handlers.js +3 -4
  21. package/dist/commands/core-command-handlers.js.map +1 -1
  22. package/dist/commands/diagnostic-command-handlers.d.ts.map +1 -1
  23. package/dist/commands/diagnostic-command-handlers.js +3 -0
  24. package/dist/commands/diagnostic-command-handlers.js.map +1 -1
  25. package/dist/commands/model-command-handlers.d.ts +1 -1
  26. package/dist/commands/model-command-handlers.d.ts.map +1 -1
  27. package/dist/commands/model-command-handlers.js +166 -1
  28. package/dist/commands/model-command-handlers.js.map +1 -1
  29. package/dist/commands/registry.d.ts.map +1 -1
  30. package/dist/commands/registry.js +7 -3
  31. package/dist/commands/registry.js.map +1 -1
  32. package/dist/commands/session-command-handlers.d.ts.map +1 -1
  33. package/dist/commands/session-command-handlers.js +97 -5
  34. package/dist/commands/session-command-handlers.js.map +1 -1
  35. package/dist/commands/session-commands.js +2 -2
  36. package/dist/commands/session-commands.js.map +1 -1
  37. package/dist/commands/types.d.ts +6 -0
  38. package/dist/commands/types.d.ts.map +1 -1
  39. package/dist/commands/types.js.map +1 -1
  40. package/dist/commands/workflow-command-handlers.d.ts.map +1 -1
  41. package/dist/commands/workflow-command-handlers.js +2 -2
  42. package/dist/commands/workflow-command-handlers.js.map +1 -1
  43. package/dist/commands/workflow-commands.d.ts.map +1 -1
  44. package/dist/commands/workflow-commands.js +1 -1
  45. package/dist/commands/workflow-commands.js.map +1 -1
  46. package/dist/core/checkpoint.d.ts.map +1 -1
  47. package/dist/core/checkpoint.js +9 -8
  48. package/dist/core/checkpoint.js.map +1 -1
  49. package/dist/framework/index.d.ts +5 -3
  50. package/dist/framework/index.d.ts.map +1 -1
  51. package/dist/framework/index.js +9 -1
  52. package/dist/framework/index.js.map +1 -1
  53. package/dist/framework/prompt.d.ts +1 -1
  54. package/dist/framework/prompt.d.ts.map +1 -1
  55. package/dist/framework/prompt.js +5 -4
  56. package/dist/framework/prompt.js.map +1 -1
  57. package/dist/framework/query.d.ts +30 -4
  58. package/dist/framework/query.d.ts.map +1 -1
  59. package/dist/framework/query.js +329 -62
  60. package/dist/framework/query.js.map +1 -1
  61. package/dist/framework/stop-decision.d.ts +56 -0
  62. package/dist/framework/stop-decision.d.ts.map +1 -0
  63. package/dist/framework/stop-decision.js +36 -0
  64. package/dist/framework/stop-decision.js.map +1 -0
  65. package/dist/framework/store.d.ts.map +1 -1
  66. package/dist/framework/store.js +4 -4
  67. package/dist/framework/store.js.map +1 -1
  68. package/dist/framework/tool-scheduler.d.ts +23 -13
  69. package/dist/framework/tool-scheduler.d.ts.map +1 -1
  70. package/dist/framework/tool-scheduler.js +93 -77
  71. package/dist/framework/tool-scheduler.js.map +1 -1
  72. package/dist/framework/tool.d.ts +7 -0
  73. package/dist/framework/tool.d.ts.map +1 -1
  74. package/dist/framework/tool.js +9 -0
  75. package/dist/framework/tool.js.map +1 -1
  76. package/dist/harness/assembler.d.ts.map +1 -1
  77. package/dist/harness/assembler.js +77 -19
  78. package/dist/harness/assembler.js.map +1 -1
  79. package/dist/harness/capability-profile.d.ts +3 -0
  80. package/dist/harness/capability-profile.d.ts.map +1 -0
  81. package/dist/harness/capability-profile.js +44 -0
  82. package/dist/harness/capability-profile.js.map +1 -0
  83. package/dist/harness/capsule.d.ts.map +1 -1
  84. package/dist/harness/capsule.js +22 -3
  85. package/dist/harness/capsule.js.map +1 -1
  86. package/dist/harness/compact-benchmark-corpus.d.ts +10 -0
  87. package/dist/harness/compact-benchmark-corpus.d.ts.map +1 -0
  88. package/dist/harness/compact-benchmark-corpus.js +56 -0
  89. package/dist/harness/compact-benchmark-corpus.js.map +1 -0
  90. package/dist/harness/context-harness.d.ts +23 -3
  91. package/dist/harness/context-harness.d.ts.map +1 -1
  92. package/dist/harness/context-harness.js +183 -11
  93. package/dist/harness/context-harness.js.map +1 -1
  94. package/dist/harness/contract.d.ts.map +1 -1
  95. package/dist/harness/contract.js +106 -11
  96. package/dist/harness/contract.js.map +1 -1
  97. package/dist/harness/drift-guard.d.ts +2 -1
  98. package/dist/harness/drift-guard.d.ts.map +1 -1
  99. package/dist/harness/drift-guard.js +315 -20
  100. package/dist/harness/drift-guard.js.map +1 -1
  101. package/dist/harness/evidence.d.ts.map +1 -1
  102. package/dist/harness/evidence.js +25 -12
  103. package/dist/harness/evidence.js.map +1 -1
  104. package/dist/harness/index.d.ts +7 -2
  105. package/dist/harness/index.d.ts.map +1 -1
  106. package/dist/harness/index.js +19 -1
  107. package/dist/harness/index.js.map +1 -1
  108. package/dist/harness/ledger.d.ts.map +1 -1
  109. package/dist/harness/ledger.js +44 -14
  110. package/dist/harness/ledger.js.map +1 -1
  111. package/dist/harness/progress-controller.d.ts +14 -0
  112. package/dist/harness/progress-controller.d.ts.map +1 -0
  113. package/dist/harness/progress-controller.js +110 -0
  114. package/dist/harness/progress-controller.js.map +1 -0
  115. package/dist/harness/state.d.ts.map +1 -1
  116. package/dist/harness/state.js +5 -2
  117. package/dist/harness/state.js.map +1 -1
  118. package/dist/harness/stop-controller.d.ts +15 -0
  119. package/dist/harness/stop-controller.d.ts.map +1 -0
  120. package/dist/harness/stop-controller.js +87 -0
  121. package/dist/harness/stop-controller.js.map +1 -0
  122. package/dist/harness/types.d.ts +137 -0
  123. package/dist/harness/types.d.ts.map +1 -1
  124. package/dist/harness/verification.d.ts +7 -0
  125. package/dist/harness/verification.d.ts.map +1 -0
  126. package/dist/harness/verification.js +122 -0
  127. package/dist/harness/verification.js.map +1 -0
  128. package/dist/index.d.ts +5 -5
  129. package/dist/index.d.ts.map +1 -1
  130. package/dist/index.js +7 -1
  131. package/dist/index.js.map +1 -1
  132. package/dist/ink-ui/screens/ReplScreen.d.ts +3 -0
  133. package/dist/ink-ui/screens/ReplScreen.d.ts.map +1 -1
  134. package/dist/ink-ui/screens/ReplScreen.js +30 -5
  135. package/dist/ink-ui/screens/ReplScreen.js.map +1 -1
  136. package/dist/memory/index.d.ts +1 -0
  137. package/dist/memory/index.d.ts.map +1 -1
  138. package/dist/memory/index.js +1 -0
  139. package/dist/memory/index.js.map +1 -1
  140. package/dist/memory/prompt-context.d.ts +42 -0
  141. package/dist/memory/prompt-context.d.ts.map +1 -0
  142. package/dist/memory/prompt-context.js +129 -0
  143. package/dist/memory/prompt-context.js.map +1 -0
  144. package/dist/print-ui/launch.d.ts +1 -0
  145. package/dist/print-ui/launch.d.ts.map +1 -1
  146. package/dist/print-ui/launch.js +5 -2
  147. package/dist/print-ui/launch.js.map +1 -1
  148. package/dist/runtime/agent-runtime-controller.d.ts.map +1 -1
  149. package/dist/runtime/agent-runtime-controller.js +60 -22
  150. package/dist/runtime/agent-runtime-controller.js.map +1 -1
  151. package/dist/runtime/chat-checkpoint.d.ts.map +1 -1
  152. package/dist/runtime/chat-checkpoint.js +2 -5
  153. package/dist/runtime/chat-checkpoint.js.map +1 -1
  154. package/dist/runtime/chat-command-scope.d.ts +5 -0
  155. package/dist/runtime/chat-command-scope.d.ts.map +1 -0
  156. package/dist/runtime/chat-command-scope.js +22 -0
  157. package/dist/runtime/chat-command-scope.js.map +1 -0
  158. package/dist/runtime/chat-compact.d.ts +26 -0
  159. package/dist/runtime/chat-compact.d.ts.map +1 -0
  160. package/dist/runtime/chat-compact.js +132 -0
  161. package/dist/runtime/chat-compact.js.map +1 -0
  162. package/dist/runtime/chat-controller.d.ts.map +1 -1
  163. package/dist/runtime/chat-controller.js +102 -130
  164. package/dist/runtime/chat-controller.js.map +1 -1
  165. package/dist/runtime/chat-presentation.d.ts +6 -3
  166. package/dist/runtime/chat-presentation.d.ts.map +1 -1
  167. package/dist/runtime/chat-presentation.js +30 -6
  168. package/dist/runtime/chat-presentation.js.map +1 -1
  169. package/dist/runtime/goals/coordinator.d.ts +8 -0
  170. package/dist/runtime/goals/coordinator.d.ts.map +1 -1
  171. package/dist/runtime/goals/coordinator.js +103 -30
  172. package/dist/runtime/goals/coordinator.js.map +1 -1
  173. package/dist/runtime/goals/evidence.d.ts +10 -0
  174. package/dist/runtime/goals/evidence.d.ts.map +1 -1
  175. package/dist/runtime/goals/evidence.js +18 -0
  176. package/dist/runtime/goals/evidence.js.map +1 -1
  177. package/dist/runtime/goals/lifecycle.d.ts +13 -0
  178. package/dist/runtime/goals/lifecycle.d.ts.map +1 -0
  179. package/dist/runtime/goals/lifecycle.js +46 -0
  180. package/dist/runtime/goals/lifecycle.js.map +1 -0
  181. package/dist/runtime/goals/objective.d.ts +18 -0
  182. package/dist/runtime/goals/objective.d.ts.map +1 -0
  183. package/dist/runtime/goals/objective.js +60 -0
  184. package/dist/runtime/goals/objective.js.map +1 -0
  185. package/dist/runtime/goals/prompt.d.ts.map +1 -1
  186. package/dist/runtime/goals/prompt.js +7 -1
  187. package/dist/runtime/goals/prompt.js.map +1 -1
  188. package/dist/runtime/goals/stop-policy.d.ts +9 -0
  189. package/dist/runtime/goals/stop-policy.d.ts.map +1 -0
  190. package/dist/runtime/goals/stop-policy.js +83 -0
  191. package/dist/runtime/goals/stop-policy.js.map +1 -0
  192. package/dist/runtime/goals/tools.d.ts.map +1 -1
  193. package/dist/runtime/goals/tools.js +2 -2
  194. package/dist/runtime/goals/tools.js.map +1 -1
  195. package/dist/runtime/goals/types.d.ts +4 -1
  196. package/dist/runtime/goals/types.d.ts.map +1 -1
  197. package/dist/runtime/goals/types.js +2 -1
  198. package/dist/runtime/goals/types.js.map +1 -1
  199. package/dist/runtime/loop-budget.d.ts +2 -0
  200. package/dist/runtime/loop-budget.d.ts.map +1 -1
  201. package/dist/runtime/loop-budget.js +6 -2
  202. package/dist/runtime/loop-budget.js.map +1 -1
  203. package/dist/runtime/model-coordinator.d.ts +44 -1
  204. package/dist/runtime/model-coordinator.d.ts.map +1 -1
  205. package/dist/runtime/model-coordinator.js +161 -19
  206. package/dist/runtime/model-coordinator.js.map +1 -1
  207. package/dist/runtime/subagents/index.d.ts +1 -1
  208. package/dist/runtime/subagents/index.d.ts.map +1 -1
  209. package/dist/runtime/subagents/index.js.map +1 -1
  210. package/dist/runtime/subagents/production.d.ts +6 -2
  211. package/dist/runtime/subagents/production.d.ts.map +1 -1
  212. package/dist/runtime/subagents/production.js +25 -6
  213. package/dist/runtime/subagents/production.js.map +1 -1
  214. package/dist/runtime/subagents/runner.d.ts +12 -1
  215. package/dist/runtime/subagents/runner.d.ts.map +1 -1
  216. package/dist/runtime/subagents/runner.js +62 -1
  217. package/dist/runtime/subagents/runner.js.map +1 -1
  218. package/dist/runtime/subagents/runtime-integration.d.ts.map +1 -1
  219. package/dist/runtime/subagents/runtime-integration.js +2 -1
  220. package/dist/runtime/subagents/runtime-integration.js.map +1 -1
  221. package/dist/runtime/subagents/supervisor.d.ts.map +1 -1
  222. package/dist/runtime/subagents/supervisor.js +6 -0
  223. package/dist/runtime/subagents/supervisor.js.map +1 -1
  224. package/dist/runtime/subagents/types.d.ts +4 -1
  225. package/dist/runtime/subagents/types.d.ts.map +1 -1
  226. package/dist/runtime/subagents/types.js.map +1 -1
  227. package/dist/runtime/ui-events.d.ts +37 -1
  228. package/dist/runtime/ui-events.d.ts.map +1 -1
  229. package/dist/runtime/ui-events.js.map +1 -1
  230. package/dist/runtime/ui-view-model.d.ts +10 -1
  231. package/dist/runtime/ui-view-model.d.ts.map +1 -1
  232. package/dist/runtime/ui-view-model.js +93 -4
  233. package/dist/runtime/ui-view-model.js.map +1 -1
  234. package/dist/services/bounded-response.d.ts +12 -0
  235. package/dist/services/bounded-response.d.ts.map +1 -0
  236. package/dist/services/bounded-response.js +67 -0
  237. package/dist/services/bounded-response.js.map +1 -0
  238. package/dist/services/compact/auto-compact.d.ts +82 -2
  239. package/dist/services/compact/auto-compact.d.ts.map +1 -1
  240. package/dist/services/compact/auto-compact.js +226 -44
  241. package/dist/services/compact/auto-compact.js.map +1 -1
  242. package/dist/services/compact/compact.d.ts +54 -37
  243. package/dist/services/compact/compact.d.ts.map +1 -1
  244. package/dist/services/compact/compact.js +512 -127
  245. package/dist/services/compact/compact.js.map +1 -1
  246. package/dist/services/compact/coordinator.d.ts +5 -1
  247. package/dist/services/compact/coordinator.d.ts.map +1 -1
  248. package/dist/services/compact/coordinator.js +10 -1
  249. package/dist/services/compact/coordinator.js.map +1 -1
  250. package/dist/services/compact/fingerprint.d.ts +27 -0
  251. package/dist/services/compact/fingerprint.d.ts.map +1 -0
  252. package/dist/services/compact/fingerprint.js +69 -0
  253. package/dist/services/compact/fingerprint.js.map +1 -0
  254. package/dist/services/compact/index.d.ts +7 -5
  255. package/dist/services/compact/index.d.ts.map +1 -1
  256. package/dist/services/compact/index.js +19 -6
  257. package/dist/services/compact/index.js.map +1 -1
  258. package/dist/services/compact/planner.d.ts +41 -0
  259. package/dist/services/compact/planner.d.ts.map +1 -0
  260. package/dist/services/compact/planner.js +102 -0
  261. package/dist/services/compact/planner.js.map +1 -0
  262. package/dist/services/compact/semantic-summary.d.ts +93 -0
  263. package/dist/services/compact/semantic-summary.d.ts.map +1 -0
  264. package/dist/services/compact/semantic-summary.js +158 -0
  265. package/dist/services/compact/semantic-summary.js.map +1 -0
  266. package/dist/services/compact/summary-generator.d.ts +23 -0
  267. package/dist/services/compact/summary-generator.d.ts.map +1 -1
  268. package/dist/services/compact/summary-generator.js +173 -39
  269. package/dist/services/compact/summary-generator.js.map +1 -1
  270. package/dist/services/concurrent-sessions.d.ts +1 -0
  271. package/dist/services/concurrent-sessions.d.ts.map +1 -1
  272. package/dist/services/concurrent-sessions.js +35 -22
  273. package/dist/services/concurrent-sessions.js.map +1 -1
  274. package/dist/services/file-context.d.ts.map +1 -1
  275. package/dist/services/file-context.js +29 -33
  276. package/dist/services/file-context.js.map +1 -1
  277. package/dist/services/file-lock.d.ts.map +1 -1
  278. package/dist/services/file-lock.js +60 -27
  279. package/dist/services/file-lock.js.map +1 -1
  280. package/dist/services/global-config.d.ts +6 -0
  281. package/dist/services/global-config.d.ts.map +1 -1
  282. package/dist/services/global-config.js +23 -13
  283. package/dist/services/global-config.js.map +1 -1
  284. package/dist/services/goal-storage.d.ts +1 -1
  285. package/dist/services/goal-storage.d.ts.map +1 -1
  286. package/dist/services/goal-storage.js +14 -4
  287. package/dist/services/goal-storage.js.map +1 -1
  288. package/dist/services/llm.d.ts +3 -0
  289. package/dist/services/llm.d.ts.map +1 -1
  290. package/dist/services/llm.js +7 -0
  291. package/dist/services/llm.js.map +1 -1
  292. package/dist/services/project-instructions.d.ts +8 -0
  293. package/dist/services/project-instructions.d.ts.map +1 -1
  294. package/dist/services/project-instructions.js +101 -51
  295. package/dist/services/project-instructions.js.map +1 -1
  296. package/dist/services/provider-resilience/request-gate.d.ts +9 -5
  297. package/dist/services/provider-resilience/request-gate.d.ts.map +1 -1
  298. package/dist/services/provider-resilience/request-gate.js +71 -23
  299. package/dist/services/provider-resilience/request-gate.js.map +1 -1
  300. package/dist/services/safe-project-reader.d.ts +30 -0
  301. package/dist/services/safe-project-reader.d.ts.map +1 -0
  302. package/dist/services/safe-project-reader.js +141 -0
  303. package/dist/services/safe-project-reader.js.map +1 -0
  304. package/dist/services/session-storage.d.ts +146 -10
  305. package/dist/services/session-storage.d.ts.map +1 -1
  306. package/dist/services/session-storage.js +851 -120
  307. package/dist/services/session-storage.js.map +1 -1
  308. package/dist/services/tool-allowlist.d.ts +4 -4
  309. package/dist/services/tool-allowlist.js +4 -4
  310. package/dist/services/usage-state.d.ts +7 -0
  311. package/dist/services/usage-state.d.ts.map +1 -1
  312. package/dist/services/usage-state.js +117 -16
  313. package/dist/services/usage-state.js.map +1 -1
  314. package/dist/services/web-search-adapters.d.ts.map +1 -1
  315. package/dist/services/web-search-adapters.js +24 -10
  316. package/dist/services/web-search-adapters.js.map +1 -1
  317. package/dist/services/web-search-mcp.d.ts.map +1 -1
  318. package/dist/services/web-search-mcp.js +13 -3
  319. package/dist/services/web-search-mcp.js.map +1 -1
  320. package/dist/services/workspace-containment.d.ts +8 -0
  321. package/dist/services/workspace-containment.d.ts.map +1 -0
  322. package/dist/services/workspace-containment.js +44 -0
  323. package/dist/services/workspace-containment.js.map +1 -0
  324. package/dist/skills/runtime.d.ts.map +1 -1
  325. package/dist/skills/runtime.js +34 -35
  326. package/dist/skills/runtime.js.map +1 -1
  327. package/dist/terminal-ui/completion.d.ts.map +1 -1
  328. package/dist/terminal-ui/completion.js +5 -1
  329. package/dist/terminal-ui/completion.js.map +1 -1
  330. package/dist/terminal-ui/launch.d.ts +5 -0
  331. package/dist/terminal-ui/launch.d.ts.map +1 -1
  332. package/dist/terminal-ui/launch.js +53 -17
  333. package/dist/terminal-ui/launch.js.map +1 -1
  334. package/dist/tools/git.d.ts.map +1 -1
  335. package/dist/tools/git.js +47 -12
  336. package/dist/tools/git.js.map +1 -1
  337. package/dist/tools/index.d.ts.map +1 -1
  338. package/dist/tools/index.js +73 -64
  339. package/dist/tools/index.js.map +1 -1
  340. package/dist/tools/plan.d.ts.map +1 -1
  341. package/dist/tools/plan.js +7 -6
  342. package/dist/tools/plan.js.map +1 -1
  343. package/dist/tools/web.d.ts.map +1 -1
  344. package/dist/tools/web.js +11 -2
  345. package/dist/tools/web.js.map +1 -1
  346. package/dist/tui-ui/chrome-view-model.js +1 -1
  347. package/dist/tui-ui/chrome-view-model.js.map +1 -1
  348. package/dist/tui-ui/inline-surface.d.ts +1 -1
  349. package/dist/tui-ui/inline-surface.d.ts.map +1 -1
  350. package/dist/tui-ui/inline-surface.js +24 -16
  351. package/dist/tui-ui/inline-surface.js.map +1 -1
  352. package/dist/tui-ui/state.d.ts.map +1 -1
  353. package/dist/tui-ui/state.js +93 -13
  354. package/dist/tui-ui/state.js.map +1 -1
  355. package/dist/tui-ui/transcript-layout.d.ts.map +1 -1
  356. package/dist/tui-ui/transcript-layout.js +38 -29
  357. package/dist/tui-ui/transcript-layout.js.map +1 -1
  358. package/dist/ui/command-panel.d.ts +2 -0
  359. package/dist/ui/command-panel.d.ts.map +1 -1
  360. package/dist/ui/command-panel.js +46 -20
  361. package/dist/ui/command-panel.js.map +1 -1
  362. package/dist/ui/file-completion.d.ts.map +1 -1
  363. package/dist/ui/file-completion.js +12 -6
  364. package/dist/ui/file-completion.js.map +1 -1
  365. package/dist/ui/shared/command-palette.d.ts.map +1 -1
  366. package/dist/ui/shared/command-palette.js +13 -9
  367. package/dist/ui/shared/command-palette.js.map +1 -1
  368. package/dist/ui/shared/text.d.ts +2 -0
  369. package/dist/ui/shared/text.d.ts.map +1 -1
  370. package/dist/ui/shared/text.js +20 -12
  371. package/dist/ui/shared/text.js.map +1 -1
  372. package/dist/ui/suggestions.d.ts.map +1 -1
  373. package/dist/ui/suggestions.js +7 -6
  374. package/dist/ui/suggestions.js.map +1 -1
  375. package/docs/migration/v0.1.8-to-v0.1.9.md +60 -0
  376. package/docs/plan/v0.1.9-release-checklist.md +64 -0
  377. package/docs/readme.md +27 -0
  378. package/npm-shrinkwrap.json +7124 -0
  379. package/package.json +17 -7
  380. package/dist/ink/index.d.ts +0 -54
  381. package/dist/ink/index.d.ts.map +0 -1
  382. package/dist/ink/index.js +0 -111
  383. package/dist/ink/index.js.map +0 -1
  384. package/dist/services/compact/micro-compact.d.ts +0 -21
  385. package/dist/services/compact/micro-compact.d.ts.map +0 -1
  386. package/dist/services/compact/micro-compact.js +0 -99
  387. package/dist/services/compact/micro-compact.js.map +0 -1
  388. package/dist/ui-v2/components/command-palette.d.ts +0 -2
  389. package/dist/ui-v2/components/command-palette.d.ts.map +0 -1
  390. package/dist/ui-v2/components/command-palette.js +0 -18
  391. package/dist/ui-v2/components/command-palette.js.map +0 -1
  392. package/dist/ui-v2/components/edit-preview-picker.d.ts +0 -26
  393. package/dist/ui-v2/components/edit-preview-picker.d.ts.map +0 -1
  394. package/dist/ui-v2/components/edit-preview-picker.js +0 -79
  395. package/dist/ui-v2/components/edit-preview-picker.js.map +0 -1
  396. package/dist/ui-v2/components/session-picker.d.ts +0 -19
  397. package/dist/ui-v2/components/session-picker.d.ts.map +0 -1
  398. package/dist/ui-v2/components/session-picker.js +0 -52
  399. package/dist/ui-v2/components/session-picker.js.map +0 -1
  400. package/dist/ui-v2/components/shell.d.ts +0 -36
  401. package/dist/ui-v2/components/shell.d.ts.map +0 -1
  402. package/dist/ui-v2/components/shell.js +0 -131
  403. package/dist/ui-v2/components/shell.js.map +0 -1
  404. package/dist/ui-v2/index.d.ts +0 -11
  405. package/dist/ui-v2/index.d.ts.map +0 -1
  406. package/dist/ui-v2/index.js +0 -27
  407. package/dist/ui-v2/index.js.map +0 -1
  408. package/dist/ui-v2/runtime/text.d.ts +0 -2
  409. package/dist/ui-v2/runtime/text.d.ts.map +0 -1
  410. package/dist/ui-v2/runtime/text.js +0 -18
  411. package/dist/ui-v2/runtime/text.js.map +0 -1
  412. package/dist/ui-v2/state/input-reducer.d.ts +0 -39
  413. package/dist/ui-v2/state/input-reducer.d.ts.map +0 -1
  414. package/dist/ui-v2/state/input-reducer.js +0 -82
  415. package/dist/ui-v2/state/input-reducer.js.map +0 -1
  416. package/dist/ui-v2/state/picker.d.ts +0 -16
  417. package/dist/ui-v2/state/picker.d.ts.map +0 -1
  418. package/dist/ui-v2/state/picker.js +0 -52
  419. package/dist/ui-v2/state/picker.js.map +0 -1
  420. package/dist/ui-v2/state/sessions.d.ts +0 -15
  421. package/dist/ui-v2/state/sessions.d.ts.map +0 -1
  422. package/dist/ui-v2/state/sessions.js +0 -70
  423. package/dist/ui-v2/state/sessions.js.map +0 -1
  424. package/dist/ui-v2/state/suggestions.d.ts +0 -2
  425. package/dist/ui-v2/state/suggestions.d.ts.map +0 -1
  426. package/dist/ui-v2/state/suggestions.js +0 -18
  427. package/dist/ui-v2/state/suggestions.js.map +0 -1
  428. package/dist/ui-v2/types.d.ts +0 -2
  429. package/dist/ui-v2/types.d.ts.map +0 -1
  430. package/dist/ui-v2/types.js +0 -18
  431. package/dist/ui-v2/types.js.map +0 -1
@@ -10,6 +10,8 @@ exports.SESSION_SIDECAR_SUFFIXES = exports.redactTraceText = void 0;
10
10
  exports.resolveProjectPath = resolveProjectPath;
11
11
  exports.getProjectKey = getProjectKey;
12
12
  exports.isSessionMetaFile = isSessionMetaFile;
13
+ exports.prepareSessionCompactSourceReceipt = prepareSessionCompactSourceReceipt;
14
+ exports.advanceSessionCompactSourceReceipt = advanceSessionCompactSourceReceipt;
13
15
  exports.createSession = createSession;
14
16
  exports.saveSessionMeta = saveSessionMeta;
15
17
  exports.updateSessionEffort = updateSessionEffort;
@@ -18,6 +20,7 @@ exports.updateSessionStats = updateSessionStats;
18
20
  exports.resumeSession = resumeSession;
19
21
  exports.updateSessionGoalBinding = updateSessionGoalBinding;
20
22
  exports.clearSessionGoalBinding = clearSessionGoalBinding;
23
+ exports.restoreSessionGoalBinding = restoreSessionGoalBinding;
21
24
  exports.updateSessionHarnessState = updateSessionHarnessState;
22
25
  exports.loadSessionHarnessState = loadSessionHarnessState;
23
26
  exports.updateSessionSkills = updateSessionSkills;
@@ -59,11 +62,55 @@ const tool_call_groups_1 = require("./compact/tool-call-groups");
59
62
  const redaction_1 = require("./redaction");
60
63
  const debug_log_1 = require("../utils/debug-log");
61
64
  const goal_storage_1 = require("./goal-storage");
65
+ const fingerprint_1 = require("./compact/fingerprint");
66
+ const token_estimate_1 = require("../utils/token-estimate");
62
67
  const harness_1 = require("../harness");
63
68
  var redaction_2 = require("./redaction");
64
69
  Object.defineProperty(exports, "redactTraceText", { enumerable: true, get: function () { return redaction_2.redactTraceText; } });
65
70
  function sanitizeTraceEvent(event) {
66
71
  const sanitized = { ...event };
72
+ if (sanitized.stopDecision) {
73
+ sanitized.stopDecision = {
74
+ ...sanitized.stopDecision,
75
+ reason: {
76
+ ...sanitized.stopDecision.reason,
77
+ message: (0, redaction_1.redactTraceText)(sanitized.stopDecision.reason.message),
78
+ },
79
+ evidence: sanitized.stopDecision.evidence.map(item => ({
80
+ ...item,
81
+ source: (0, redaction_1.redactTraceText)(item.source),
82
+ detail: (0, redaction_1.redactTraceText)(item.detail),
83
+ })),
84
+ nextActions: sanitized.stopDecision.nextActions.map(item => ({
85
+ ...item,
86
+ label: (0, redaction_1.redactTraceText)(item.label),
87
+ command: item.command ? (0, redaction_1.redactTraceText)(item.command) : undefined,
88
+ })),
89
+ resources: Object.fromEntries(Object.entries(sanitized.stopDecision.resources).map(([key, value]) => [
90
+ key,
91
+ value ? { ...value } : value,
92
+ ])),
93
+ criterionStates: sanitized.stopDecision.criterionStates?.map(item => ({
94
+ ...item,
95
+ id: (0, redaction_1.redactTraceText)(item.id),
96
+ })),
97
+ evidenceRefs: sanitized.stopDecision.evidenceRefs?.map(redaction_1.redactTraceText),
98
+ progressDelta: sanitized.stopDecision.progressDelta
99
+ ? {
100
+ ...sanitized.stopDecision.progressDelta,
101
+ criterionChanges: sanitized.stopDecision.progressDelta.criterionChanges.map(item => ({
102
+ ...item,
103
+ id: (0, redaction_1.redactTraceText)(item.id),
104
+ })),
105
+ newEvidenceRefs: sanitized.stopDecision.progressDelta.newEvidenceRefs.map(redaction_1.redactTraceText),
106
+ newChangedFiles: sanitized.stopDecision.progressDelta.newChangedFiles.map(redaction_1.redactTraceText),
107
+ newDecisions: sanitized.stopDecision.progressDelta.newDecisions.map(redaction_1.redactTraceText),
108
+ newBlockers: sanitized.stopDecision.progressDelta.newBlockers.map(redaction_1.redactTraceText),
109
+ newDiagnostics: sanitized.stopDecision.progressDelta.newDiagnostics.map(redaction_1.redactTraceText),
110
+ }
111
+ : undefined,
112
+ };
113
+ }
67
114
  for (const key of [
68
115
  'argsSummary',
69
116
  'error',
@@ -71,6 +118,8 @@ function sanitizeTraceEvent(event) {
71
118
  'permissionReason',
72
119
  'continuationHint',
73
120
  'goalStopReason',
121
+ 'budgetExceededReason',
122
+ 'lastToolSummary',
74
123
  ]) {
75
124
  if (typeof sanitized[key] === 'string') {
76
125
  sanitized[key] = (0, redaction_1.redactTraceText)(sanitized[key]);
@@ -91,6 +140,14 @@ function sanitizeTraceEvent(event) {
91
140
  if (sanitized.promptOmittedEvidence) {
92
141
  sanitized.promptOmittedEvidence = sanitized.promptOmittedEvidence.map(redaction_1.redactTraceText);
93
142
  }
143
+ if (sanitized.promptSectionManifest) {
144
+ sanitized.promptSectionManifest = sanitized.promptSectionManifest.map(item => ({
145
+ ...item,
146
+ name: (0, redaction_1.redactTraceText)(item.name),
147
+ source: (0, redaction_1.redactTraceText)(item.source),
148
+ reason: item.reason ? (0, redaction_1.redactTraceText)(item.reason) : undefined,
149
+ }));
150
+ }
94
151
  if (sanitized.workspaceChangedByTurn) {
95
152
  sanitized.workspaceChangedByTurn = sanitized.workspaceChangedByTurn.map(redaction_1.redactTraceText);
96
153
  }
@@ -416,10 +473,277 @@ function parseHarnessSidecarFile(path) {
416
473
  return null;
417
474
  }
418
475
  }
476
+ function canonicalizeForHash(value) {
477
+ if (Array.isArray(value))
478
+ return value.map(canonicalizeForHash);
479
+ if (!value || typeof value !== 'object')
480
+ return value;
481
+ return Object.fromEntries(Object.entries(value)
482
+ .filter(([, entry]) => entry !== undefined)
483
+ .sort(([left], [right]) => left.localeCompare(right))
484
+ .map(([key, entry]) => [key, canonicalizeForHash(entry)]));
485
+ }
486
+ function canonicalContentHash(value) {
487
+ return (0, crypto_1.createHash)('sha256')
488
+ .update(JSON.stringify(canonicalizeForHash(value)), 'utf-8')
489
+ .digest('hex');
490
+ }
491
+ function compactBindingHash(parts) {
492
+ return canonicalContentHash(parts);
493
+ }
494
+ function isSha256(value) {
495
+ return typeof value === 'string' && /^[a-f0-9]{64}$/.test(value);
496
+ }
497
+ function isRecord(value) {
498
+ return !!value && typeof value === 'object' && !Array.isArray(value);
499
+ }
500
+ function isStringArray(value) {
501
+ return Array.isArray(value) && value.every(item => typeof item === 'string' && item.length > 0);
502
+ }
503
+ function sameStrings(left, right) {
504
+ return left.length === right.length && left.every((value, index) => value === right[index]);
505
+ }
506
+ function validatePrepareSourceReceipt(receipt, sessionId) {
507
+ if (receipt.schemaVersion !== 1 ||
508
+ receipt.sessionId !== sessionId ||
509
+ !Number.isInteger(receipt.sourceMessageCount) ||
510
+ receipt.sourceMessageCount < 0 ||
511
+ !isSha256(receipt.sourcePrefixHash) ||
512
+ (receipt.activeCheckpointId !== null &&
513
+ (typeof receipt.activeCheckpointId !== 'string' || !receipt.activeCheckpointId))) {
514
+ throw new Error('Invalid compact prepare-source receipt');
515
+ }
516
+ }
517
+ function assertPrepareSourceMatches(receipt, session, rawMessages) {
518
+ validatePrepareSourceReceipt(receipt, session.id);
519
+ const activeCheckpointId = session.activeCompactCheckpointId ?? null;
520
+ if (activeCheckpointId !== receipt.activeCheckpointId) {
521
+ throw new Error(`Stale compact candidate: active checkpoint changed from ${receipt.activeCheckpointId ?? 'none'} to ${activeCheckpointId ?? 'none'}`);
522
+ }
523
+ if (rawMessages.length !== receipt.sourceMessageCount) {
524
+ throw new Error(`Stale compact candidate: source message count changed from ${receipt.sourceMessageCount} to ${rawMessages.length}`);
525
+ }
526
+ const prefixHash = canonicalContentHash(rawMessages);
527
+ if (prefixHash !== receipt.sourcePrefixHash) {
528
+ throw new Error('Stale compact candidate: source transcript hash changed during preparation');
529
+ }
530
+ }
531
+ /** Capture the append-only transcript identity before preparing a compact candidate. */
532
+ function prepareSessionCompactSourceReceipt(sessionId) {
533
+ const receipt = withLockedSession(sessionId, session => {
534
+ const rawMessages = readSessionMessagesForSession(session);
535
+ return {
536
+ schemaVersion: 1,
537
+ sessionId,
538
+ sourceMessageCount: rawMessages.length,
539
+ sourcePrefixHash: canonicalContentHash(rawMessages),
540
+ activeCheckpointId: session.activeCompactCheckpointId ?? null,
541
+ };
542
+ });
543
+ if (!receipt)
544
+ throw new Error(`Session not found: ${sessionId}`);
545
+ return receipt;
546
+ }
547
+ /**
548
+ * Advance a prepare receipt only across the exact messages written by the
549
+ * preparing turn. Any interleaved writer fails closed instead of being claimed
550
+ * by a candidate that never observed its messages.
551
+ */
552
+ function advanceSessionCompactSourceReceipt(receipt, appendedMessages) {
553
+ const advanced = withLockedSession(receipt.sessionId, session => {
554
+ validatePrepareSourceReceipt(receipt, session.id);
555
+ if ((session.activeCompactCheckpointId ?? null) !== receipt.activeCheckpointId) {
556
+ throw new Error('Stale compact candidate: active checkpoint changed during preparation');
557
+ }
558
+ const rawMessages = readSessionMessagesForSession(session);
559
+ if (rawMessages.length !== receipt.sourceMessageCount + appendedMessages.length ||
560
+ canonicalContentHash(rawMessages.slice(0, receipt.sourceMessageCount)) !==
561
+ receipt.sourcePrefixHash ||
562
+ canonicalContentHash(rawMessages.slice(receipt.sourceMessageCount)) !==
563
+ canonicalContentHash(appendedMessages)) {
564
+ throw new Error('Stale compact candidate: transcript tail contains concurrent or unexpected messages');
565
+ }
566
+ return {
567
+ ...receipt,
568
+ sourceMessageCount: rawMessages.length,
569
+ sourcePrefixHash: canonicalContentHash(rawMessages),
570
+ };
571
+ });
572
+ if (!advanced)
573
+ throw new Error(`Session not found: ${receipt.sessionId}`);
574
+ return advanced;
575
+ }
576
+ function inspectSemanticSummary(value) {
577
+ if (!isRecord(value) || value.version !== 1) {
578
+ throw new Error('Compact semantic summary schema is invalid');
579
+ }
580
+ if (!Number.isInteger(value.taskEpoch) || value.taskEpoch < 1) {
581
+ throw new Error('Compact semantic summary taskEpoch is invalid');
582
+ }
583
+ for (const field of [
584
+ 'constraints',
585
+ 'decisions',
586
+ 'completed',
587
+ 'pending',
588
+ 'blockers',
589
+ 'files',
590
+ 'evidenceRefs',
591
+ ]) {
592
+ if (!isStringArray(value[field])) {
593
+ throw new Error(`Compact semantic summary ${field} must be a string array`);
594
+ }
595
+ }
596
+ if (!isRecord(value.coverage) || !isStringArray(value.coverage.groupIds)) {
597
+ throw new Error('Compact semantic summary coverage is invalid');
598
+ }
599
+ const coverageGroupIds = value.coverage.groupIds;
600
+ if (value.coverage.groupCount !== coverageGroupIds.length ||
601
+ !Number.isInteger(value.coverage.messageCount) ||
602
+ value.coverage.messageCount < 0) {
603
+ throw new Error('Compact semantic summary coverage counts are inconsistent');
604
+ }
605
+ if (!Array.isArray(value.items)) {
606
+ throw new Error('Compact semantic summary items must be an array');
607
+ }
608
+ const taskEpochs = [];
609
+ for (const item of value.items) {
610
+ if (!isRecord(item) ||
611
+ typeof item.id !== 'string' ||
612
+ !item.id ||
613
+ typeof item.groupId !== 'string' ||
614
+ !item.groupId ||
615
+ !isStringArray(item.sourceRefs) ||
616
+ !Number.isInteger(item.tokenEstimate) ||
617
+ item.tokenEstimate < 0 ||
618
+ !Number.isInteger(item.taskEpoch) ||
619
+ item.taskEpoch < 1 ||
620
+ typeof item.text !== 'string') {
621
+ throw new Error('Compact semantic summary contains an invalid ContextItem');
622
+ }
623
+ taskEpochs.push(item.taskEpoch);
624
+ }
625
+ const criterionStatesValue = value.criterionStates;
626
+ if (criterionStatesValue !== undefined && !Array.isArray(criterionStatesValue)) {
627
+ throw new Error('Compact semantic criterionStates must be an array');
628
+ }
629
+ const criterionStates = (criterionStatesValue ?? []).map(item => {
630
+ if (!isRecord(item) ||
631
+ typeof item.id !== 'string' ||
632
+ !item.id ||
633
+ typeof item.statement !== 'string' ||
634
+ !['pending', 'passed', 'failed', 'waived'].includes(String(item.status)) ||
635
+ !isStringArray(item.evidenceRefs)) {
636
+ throw new Error('Compact semantic summary contains an invalid criterion state');
637
+ }
638
+ return {
639
+ id: item.id,
640
+ status: String(item.status),
641
+ evidenceRefs: item.evidenceRefs,
642
+ };
643
+ });
644
+ if (new Set(criterionStates.map(item => item.id)).size !== criterionStates.length) {
645
+ throw new Error('Compact semantic summary contains duplicate criterion IDs');
646
+ }
647
+ return {
648
+ coverageGroupIds,
649
+ criterionStates,
650
+ taskEpoch: value.taskEpoch,
651
+ taskEpochs,
652
+ };
653
+ }
654
+ function semanticValidationReceipt(semanticSummary, candidate, harnessState) {
655
+ const view = inspectSemanticSummary(semanticSummary);
656
+ const expectedCoverage = candidate.plan.evictedGroups.map(group => group.id);
657
+ if (!sameStrings(view.coverageGroupIds, expectedCoverage)) {
658
+ throw new Error('Compact semantic summary does not cover the prepared evicted groups');
659
+ }
660
+ const expectedTaskEpoch = Math.max(1, Math.floor(harnessState?.taskEpoch ?? harnessState?.contract?.taskEpoch ?? view.taskEpochs[0] ?? 1));
661
+ if (view.taskEpoch !== expectedTaskEpoch ||
662
+ view.taskEpochs.some(taskEpoch => taskEpoch !== expectedTaskEpoch)) {
663
+ throw new Error('Compact semantic summary taskEpoch does not match the active Harness epoch');
664
+ }
665
+ const contractCriteria = harnessState?.contract?.criteria ?? [];
666
+ const expectedCriteria = contractCriteria.map(criterion => ({
667
+ id: criterion.id,
668
+ status: criterion.status ?? 'pending',
669
+ evidenceRefs: [...criterion.evidenceRefs],
670
+ }));
671
+ if (expectedCriteria.length > 0) {
672
+ if (view.criterionStates.length !== expectedCriteria.length) {
673
+ throw new Error('Compact semantic summary omitted active task criteria');
674
+ }
675
+ for (const [index, expected] of expectedCriteria.entries()) {
676
+ const actual = view.criterionStates[index];
677
+ if (actual.id !== expected.id ||
678
+ actual.status !== expected.status ||
679
+ !sameStrings(actual.evidenceRefs, expected.evidenceRefs)) {
680
+ throw new Error(`Compact semantic criterion mismatch: ${expected.id}`);
681
+ }
682
+ }
683
+ }
684
+ const knownEvidenceRefs = new Set([
685
+ ...(harnessState?.ledger.flatMap(item => [item.id, `ledger:${item.id}`]) ?? []),
686
+ ...(harnessState?.evidenceIndex?.flatMap(item => [item.id, `evidence:${item.id}`]) ?? []),
687
+ ...(harnessState?.capsule?.keyFacts.flatMap(item => [item.id, `fact:${item.id}`]) ?? []),
688
+ ]);
689
+ const validatedEvidenceRefs = [
690
+ ...new Set(view.criterionStates.flatMap(item => item.evidenceRefs)),
691
+ ];
692
+ if (validatedEvidenceRefs.length > 0 && !harnessState) {
693
+ throw new Error('Compact semantic evidence references require an authoritative Harness state');
694
+ }
695
+ for (const criterion of view.criterionStates) {
696
+ if (criterion.status === 'passed' && criterion.evidenceRefs.length === 0) {
697
+ throw new Error(`Passed compact criterion lacks evidence: ${criterion.id}`);
698
+ }
699
+ for (const evidenceRef of criterion.evidenceRefs) {
700
+ if (!knownEvidenceRefs.has(evidenceRef)) {
701
+ throw new Error(`Dangling compact criterion evidence reference: ${evidenceRef}`);
702
+ }
703
+ }
704
+ }
705
+ return {
706
+ schemaValid: true,
707
+ coverageValid: true,
708
+ taskEpoch: expectedTaskEpoch,
709
+ taskEpochVerified: true,
710
+ criterionEvidenceRefsValid: true,
711
+ preservedCriterionIds: view.criterionStates.map(item => item.id),
712
+ validatedEvidenceRefs,
713
+ };
714
+ }
715
+ function validatePersistedSemanticReceipt(semanticSummary, receipt) {
716
+ const view = inspectSemanticSummary(semanticSummary);
717
+ if (receipt.schemaValid !== true ||
718
+ receipt.coverageValid !== true ||
719
+ receipt.taskEpochVerified !== true ||
720
+ receipt.criterionEvidenceRefsValid !== true ||
721
+ !Number.isInteger(receipt.taskEpoch) ||
722
+ receipt.taskEpoch < 1 ||
723
+ !isStringArray(receipt.preservedCriterionIds) ||
724
+ !Array.isArray(receipt.validatedEvidenceRefs) ||
725
+ !receipt.validatedEvidenceRefs.every(ref => typeof ref === 'string' && ref.length > 0) ||
726
+ view.taskEpoch !== receipt.taskEpoch ||
727
+ view.taskEpochs.some(taskEpoch => taskEpoch !== receipt.taskEpoch) ||
728
+ !sameStrings(view.criterionStates.map(item => item.id), receipt.preservedCriterionIds) ||
729
+ !sameStrings([...new Set(view.criterionStates.flatMap(item => item.evidenceRefs))], receipt.validatedEvidenceRefs)) {
730
+ throw new Error('Compact semantic validation receipt does not match its summary');
731
+ }
732
+ }
733
+ function reportInvalidCompactCheckpoint(path, reason) {
734
+ (0, debug_log_1.debugError)('session-storage.validateCompactCheckpoint', new Error(`Invalid compact checkpoint: ${reason}`), path);
735
+ return null;
736
+ }
737
+ function compactCandidatePath(path) {
738
+ return `${path}.candidate`;
739
+ }
740
+ function compactPreviousPath(path) {
741
+ return `${path}.previous`;
742
+ }
419
743
  function parseCompactCheckpointFile(path) {
420
744
  try {
421
745
  const parsed = JSON.parse((0, fs_1.readFileSync)(path, 'utf-8'));
422
- if (parsed?.version !== 1 ||
746
+ if ((parsed?.version !== 1 && parsed?.version !== 2) ||
423
747
  !parsed.checkpointId ||
424
748
  !parsed.sessionId ||
425
749
  !Array.isArray(parsed.modelHistory) ||
@@ -427,6 +751,117 @@ function parseCompactCheckpointFile(path) {
427
751
  parsed.sourceMessageCount < 0 ||
428
752
  !parsed.summary ||
429
753
  typeof parsed.summary.text !== 'string') {
754
+ return reportInvalidCompactCheckpoint(path, 'base schema mismatch');
755
+ }
756
+ if (parsed.version === 1)
757
+ return parsed;
758
+ if (parsed.sourceBoundary?.startMessageIndex !== 0 ||
759
+ parsed.sourceBoundary.endMessageIndexExclusive !== parsed.sourceMessageCount ||
760
+ !isSha256(parsed.sourcePrefixHash) ||
761
+ !isSha256(parsed.modelHistoryHash) ||
762
+ parsed.summary.schemaVersion !== 2 ||
763
+ typeof parsed.summary.strategy !== 'string' ||
764
+ !parsed.summary.strategy ||
765
+ !Number.isInteger(parsed.contractVersion) ||
766
+ parsed.contractVersion < 1 ||
767
+ !Number.isInteger(parsed.harnessStateVersion) ||
768
+ parsed.harnessStateVersion < 1 ||
769
+ parsed.validation?.schemaValid !== true ||
770
+ parsed.validation.toolCallGroupsValid !== true ||
771
+ parsed.validation.sourcePrefixVerified !== true ||
772
+ parsed.validation.targetMet !== true ||
773
+ !isSha256(parsed.validation.bindingHash) ||
774
+ !Number.isFinite(parsed.validation.targetHeadroomRatio) ||
775
+ !Number.isFinite(parsed.validation.achievedUsageRatio) ||
776
+ !parsed.candidateReceipt ||
777
+ !['semantic_candidate', 'compatibility_adapter'].includes(parsed.candidateReceipt.source) ||
778
+ !isSha256(parsed.candidateReceipt.candidateFingerprint) ||
779
+ !isSha256(parsed.candidateReceipt.persistedProjectionFingerprint) ||
780
+ !Number.isInteger(parsed.candidateReceipt.beforeTokens) ||
781
+ parsed.candidateReceipt.beforeTokens < 0 ||
782
+ !Number.isInteger(parsed.candidateReceipt.afterTokens) ||
783
+ parsed.candidateReceipt.afterTokens < 0 ||
784
+ (parsed.candidateReceipt.targetTokens !== undefined &&
785
+ (!Number.isInteger(parsed.candidateReceipt.targetTokens) ||
786
+ parsed.candidateReceipt.targetTokens < 0)) ||
787
+ !Number.isFinite(parsed.candidateReceipt.targetRatio) ||
788
+ !Array.isArray(parsed.candidateReceipt.diagnostics) ||
789
+ (parsed.harnessBinding !== undefined &&
790
+ (!Number.isInteger(parsed.harnessBinding.schemaVersion) ||
791
+ parsed.harnessBinding.schemaVersion < 1 ||
792
+ !isSha256(parsed.harnessBinding.stateHash))) ||
793
+ (parsed.goalBinding !== undefined &&
794
+ (!parsed.goalBinding.goalId ||
795
+ !Number.isInteger(parsed.goalBinding.revision) ||
796
+ parsed.goalBinding.revision < 0 ||
797
+ !isSha256(parsed.goalBinding.stateHash)))) {
798
+ return reportInvalidCompactCheckpoint(path, 'V2 receipt mismatch');
799
+ }
800
+ if (canonicalContentHash(parsed.modelHistory) !== parsed.modelHistoryHash) {
801
+ return reportInvalidCompactCheckpoint(path, 'replacement history hash mismatch');
802
+ }
803
+ if ((0, fingerprint_1.canonicalMessagesFingerprint)(parsed.modelHistory) !==
804
+ parsed.candidateReceipt.persistedProjectionFingerprint) {
805
+ return reportInvalidCompactCheckpoint(path, 'persisted projection fingerprint mismatch');
806
+ }
807
+ const semanticSummaryHash = parsed.candidateReceipt.semanticSummary
808
+ ? canonicalContentHash(parsed.candidateReceipt.semanticSummary)
809
+ : undefined;
810
+ if (semanticSummaryHash !== parsed.candidateReceipt.semanticSummaryHash) {
811
+ return reportInvalidCompactCheckpoint(path, 'semantic summary hash mismatch');
812
+ }
813
+ const coverageHash = parsed.candidateReceipt.semanticSummary
814
+ ? canonicalContentHash(parsed.candidateReceipt.semanticSummary.coverage)
815
+ : undefined;
816
+ if (coverageHash !== parsed.candidateReceipt.coverageHash) {
817
+ return reportInvalidCompactCheckpoint(path, 'semantic coverage hash mismatch');
818
+ }
819
+ if (parsed.candidateReceipt.source === 'semantic_candidate') {
820
+ if (!parsed.candidateReceipt.prepareSource ||
821
+ !parsed.candidateReceipt.semanticSummary ||
822
+ !parsed.candidateReceipt.semanticValidation ||
823
+ parsed.validation.prepareSourceVerified !== true ||
824
+ parsed.validation.candidateTokensVerified !== true ||
825
+ parsed.validation.semanticReceiptVerified !== true) {
826
+ return reportInvalidCompactCheckpoint(path, 'semantic candidate validation is incomplete');
827
+ }
828
+ try {
829
+ validatePrepareSourceReceipt(parsed.candidateReceipt.prepareSource, parsed.sessionId);
830
+ if (parsed.candidateReceipt.prepareSource.sourceMessageCount !== parsed.sourceMessageCount ||
831
+ parsed.candidateReceipt.prepareSource.sourcePrefixHash !== parsed.sourcePrefixHash) {
832
+ return reportInvalidCompactCheckpoint(path, 'prepare-source boundary mismatch');
833
+ }
834
+ validatePersistedSemanticReceipt(parsed.candidateReceipt.semanticSummary, parsed.candidateReceipt.semanticValidation);
835
+ }
836
+ catch (error) {
837
+ return reportInvalidCompactCheckpoint(path, error instanceof Error ? error.message : String(error));
838
+ }
839
+ }
840
+ const prepareSourceHash = parsed.candidateReceipt.prepareSource
841
+ ? canonicalContentHash(parsed.candidateReceipt.prepareSource)
842
+ : undefined;
843
+ const semanticValidationHash = parsed.candidateReceipt.semanticValidation
844
+ ? canonicalContentHash(parsed.candidateReceipt.semanticValidation)
845
+ : undefined;
846
+ const expectedBindingHash = compactBindingHash({
847
+ sourcePrefixHash: parsed.sourcePrefixHash,
848
+ modelHistoryHash: parsed.modelHistoryHash,
849
+ candidateFingerprint: parsed.candidateReceipt.candidateFingerprint,
850
+ persistedProjectionFingerprint: parsed.candidateReceipt.persistedProjectionFingerprint,
851
+ semanticSummaryHash,
852
+ prepareSourceHash,
853
+ semanticValidationHash,
854
+ harnessStateHash: parsed.harnessBinding?.stateHash,
855
+ goalStateHash: parsed.goalBinding?.stateHash,
856
+ });
857
+ if (expectedBindingHash !== parsed.validation.bindingHash) {
858
+ return reportInvalidCompactCheckpoint(path, 'cross-state binding hash mismatch');
859
+ }
860
+ try {
861
+ (0, tool_call_groups_1.assertToolCallGroups)(parsed.modelHistory);
862
+ }
863
+ catch (error) {
864
+ (0, debug_log_1.debugError)('session-storage.validateCompactCheckpoint.toolGroups', error, path);
430
865
  return null;
431
866
  }
432
867
  return parsed;
@@ -627,6 +1062,24 @@ function clearSessionGoalBinding(sessionId, expectedGoalId) {
627
1062
  session.updatedAtIso = new Date(session.updatedAt).toISOString();
628
1063
  });
629
1064
  }
1065
+ /**
1066
+ * Restore a Goal binding after a later sidecar mutation failed. A different
1067
+ * active binding belongs to a newer lifecycle and must never be overwritten.
1068
+ */
1069
+ function restoreSessionGoalBinding(sessionId, goal, expectedClearedAt) {
1070
+ return mutateSessionMeta(sessionId, session => {
1071
+ if (session.activeGoalId && session.activeGoalId !== goal.goalId) {
1072
+ throw new Error(`Session Goal binding changed from ${goal.goalId} to ${session.activeGoalId}; refusing to overwrite the newer Goal during rollback.`);
1073
+ }
1074
+ if (!session.activeGoalId && session.updatedAt !== expectedClearedAt) {
1075
+ throw new Error(`Session metadata changed after Goal ${goal.goalId} was cleared; refusing to overwrite newer session state during rollback.`);
1076
+ }
1077
+ session.activeGoalId = goal.goalId;
1078
+ session.activeGoalObjective = goal.objective;
1079
+ session.updatedAt = Date.now();
1080
+ session.updatedAtIso = new Date(session.updatedAt).toISOString();
1081
+ });
1082
+ }
630
1083
  /**
631
1084
  * 更新会话 Harness 状态。
632
1085
  */
@@ -712,30 +1165,158 @@ function persistSessionCompactHistory(sessionId, messages, timestamp = Date.now(
712
1165
  appendSessionMessages(sessionId, compactMessages);
713
1166
  return markSessionTranscriptDisplayStart(sessionId, timestamp);
714
1167
  }
715
- function loadSessionCompactCheckpoint(sessionId) {
716
- const session = loadSessionMeta(sessionId);
717
- if (!session)
718
- return null;
719
- const checkpoint = parseCompactCheckpointFile((0, config_dir_1.getProjectSessionCompactPath)(session.projectPath, sessionId));
720
- if (!checkpoint || checkpoint.sessionId !== sessionId)
721
- return null;
722
- if (session.activeCompactCheckpointId !== checkpoint.checkpointId) {
723
- return null;
1168
+ function loadCompactCheckpointForSession(session, messages) {
1169
+ const compactPath = (0, config_dir_1.getProjectSessionCompactPath)(session.projectPath, session.id);
1170
+ const candidatePaths = [compactPath, compactPreviousPath(compactPath)];
1171
+ for (const path of candidatePaths) {
1172
+ if (!(0, fs_1.existsSync)(path))
1173
+ continue;
1174
+ const checkpoint = parseCompactCheckpointFile(path);
1175
+ if (!checkpoint || checkpoint.sessionId !== session.id)
1176
+ continue;
1177
+ if (session.activeCompactCheckpointId !== checkpoint.checkpointId)
1178
+ continue;
1179
+ if (checkpoint.sourceMessageCount > messages.length)
1180
+ continue;
1181
+ if (checkpoint.version === 2 &&
1182
+ canonicalContentHash(messages.slice(0, checkpoint.sourceMessageCount)) !==
1183
+ checkpoint.sourcePrefixHash) {
1184
+ reportInvalidCompactCheckpoint(path, 'covered transcript prefix hash mismatch');
1185
+ continue;
1186
+ }
1187
+ return checkpoint;
724
1188
  }
725
- const rawCount = readSessionMessages(sessionId).length;
726
- if (checkpoint.sourceMessageCount > rawCount)
727
- return null;
728
- return checkpoint;
1189
+ return null;
1190
+ }
1191
+ function loadSessionCompactCheckpoint(sessionId) {
1192
+ return (withLockedSession(sessionId, session => loadCompactCheckpointForSession(session, readSessionMessagesForSession(session))) ?? null);
729
1193
  }
730
1194
  function commitSessionCompactCheckpoint(input) {
731
1195
  const checkpoint = withLockedSession(input.sessionId, session => {
732
- const rawCount = readSessionMessagesForSession(session).length;
1196
+ const rawMessages = readSessionMessagesForSession(session);
1197
+ const rawCount = rawMessages.length;
1198
+ if (input.candidate && !input.prepareSource) {
1199
+ throw new Error('Semantic compact candidate requires a prepare-source receipt');
1200
+ }
1201
+ if (input.prepareSource) {
1202
+ if (input.prepareSource.sourceMessageCount !== input.sourceMessageCount) {
1203
+ throw new Error('Compact source boundary differs from its prepare-source receipt');
1204
+ }
1205
+ assertPrepareSourceMatches(input.prepareSource, session, rawMessages);
1206
+ }
733
1207
  if (input.sourceMessageCount < 0 || input.sourceMessageCount > rawCount) {
734
1208
  throw new Error(`Invalid compact source boundary ${input.sourceMessageCount}; transcript has ${rawCount} messages`);
735
1209
  }
736
1210
  const createdAt = input.createdAt ?? Date.now();
1211
+ const candidateModelHistory = input.modelHistory.map(message => JSON.parse(JSON.stringify(message)));
1212
+ (0, tool_call_groups_1.assertToolCallGroups)(candidateModelHistory);
1213
+ const modelHistory = input.modelHistory
1214
+ .filter(message => message.role !== 'system')
1215
+ .map(message => JSON.parse(JSON.stringify(message)));
1216
+ (0, tool_call_groups_1.assertToolCallGroups)(modelHistory);
1217
+ const targetHeadroomRatio = 0.65;
1218
+ const safeInputBudget = Math.max(1, input.afterUsage.safeInputBudget ?? input.afterUsage.contextWindow);
1219
+ const achievedUsageRatio = input.afterUsage.usedTokens / safeInputBudget;
1220
+ if (!Number.isFinite(achievedUsageRatio) || achievedUsageRatio > targetHeadroomRatio) {
1221
+ throw new Error(`Compact candidate did not reach the required ${Math.round(targetHeadroomRatio * 100)}% safe-input headroom target (${Math.round(achievedUsageRatio * 100)}% used)`);
1222
+ }
1223
+ const sourcePrefixHash = canonicalContentHash(rawMessages.slice(0, input.sourceMessageCount));
1224
+ const modelHistoryHash = canonicalContentHash(modelHistory);
1225
+ const persistedProjectionFingerprint = (0, fingerprint_1.canonicalMessagesFingerprint)(modelHistory);
1226
+ const candidateBeforeTokens = input.candidate?.beforeTokens ?? input.beforeUsage.usedTokens;
1227
+ const candidateAfterTokens = input.candidate?.afterTokens ?? input.afterUsage.usedTokens;
1228
+ const candidateTargetRatio = input.candidate?.plan.targetRatio ?? targetHeadroomRatio;
1229
+ const candidateFingerprint = input.candidate?.fingerprint ?? persistedProjectionFingerprint;
1230
+ for (const [name, value] of [
1231
+ ['beforeTokens', candidateBeforeTokens],
1232
+ ['afterTokens', candidateAfterTokens],
1233
+ ]) {
1234
+ if (!Number.isInteger(value) || value < 0) {
1235
+ throw new Error(`Compact candidate ${name} must be a non-negative integer`);
1236
+ }
1237
+ }
1238
+ if (!isSha256(candidateFingerprint)) {
1239
+ throw new Error('Compact candidate fingerprint must be a SHA-256 digest');
1240
+ }
1241
+ if (input.candidate) {
1242
+ const actualCandidateTokens = (0, token_estimate_1.estimateMessagesTokens)(candidateModelHistory);
1243
+ if (candidateAfterTokens !== actualCandidateTokens) {
1244
+ throw new Error(`Compact candidate afterTokens ${candidateAfterTokens} does not match actual history ${actualCandidateTokens}`);
1245
+ }
1246
+ if (input.candidate.plan.targetTokens !== undefined &&
1247
+ candidateAfterTokens > input.candidate.plan.targetTokens) {
1248
+ throw new Error('Compact candidate exceeds its prepared target token budget');
1249
+ }
1250
+ }
1251
+ if (!Number.isFinite(candidateTargetRatio) ||
1252
+ candidateTargetRatio <= 0 ||
1253
+ candidateTargetRatio > targetHeadroomRatio) {
1254
+ throw new Error(`Compact candidate target ratio must be within (0, ${targetHeadroomRatio}]`);
1255
+ }
1256
+ const semanticSummary = input.candidate
1257
+ ? JSON.parse(JSON.stringify(input.candidate.semanticSummary))
1258
+ : undefined;
1259
+ const semanticSummaryHash = semanticSummary ? canonicalContentHash(semanticSummary) : undefined;
1260
+ const coverageHash = semanticSummary
1261
+ ? canonicalContentHash(semanticSummary.coverage)
1262
+ : undefined;
1263
+ const semanticValidation = input.candidate && semanticSummary
1264
+ ? semanticValidationReceipt(semanticSummary, input.candidate, input.semanticHarnessState ?? input.harnessState)
1265
+ : undefined;
1266
+ const prepareSource = input.prepareSource
1267
+ ? { ...input.prepareSource }
1268
+ : undefined;
1269
+ const harnessBinding = input.harnessState
1270
+ ? {
1271
+ schemaVersion: input.harnessState.version ?? input.harnessStateVersion ?? 2,
1272
+ stateHash: canonicalContentHash(input.harnessState),
1273
+ }
1274
+ : undefined;
1275
+ const goalBinding = input.goalBinding
1276
+ ? {
1277
+ goalId: input.goalBinding.goalId,
1278
+ revision: input.goalBinding.revision,
1279
+ stateHash: canonicalContentHash(input.goalBinding.state ?? {
1280
+ goalId: input.goalBinding.goalId,
1281
+ revision: input.goalBinding.revision,
1282
+ }),
1283
+ }
1284
+ : undefined;
1285
+ const candidateReceipt = {
1286
+ source: input.candidate ? 'semantic_candidate' : 'compatibility_adapter',
1287
+ candidateFingerprint,
1288
+ persistedProjectionFingerprint,
1289
+ beforeTokens: candidateBeforeTokens,
1290
+ afterTokens: candidateAfterTokens,
1291
+ targetTokens: input.candidate?.plan.targetTokens,
1292
+ targetRatio: candidateTargetRatio,
1293
+ semanticSummary,
1294
+ semanticSummaryHash,
1295
+ diagnostics: input.candidate?.diagnostics.map(diagnostic => ({
1296
+ ...diagnostic,
1297
+ message: (0, redaction_1.redactTraceText)(diagnostic.message),
1298
+ })) ?? [],
1299
+ coverageHash,
1300
+ prepareSource,
1301
+ semanticValidation,
1302
+ };
1303
+ const prepareSourceHash = prepareSource ? canonicalContentHash(prepareSource) : undefined;
1304
+ const semanticValidationHash = semanticValidation
1305
+ ? canonicalContentHash(semanticValidation)
1306
+ : undefined;
1307
+ const bindingHash = compactBindingHash({
1308
+ sourcePrefixHash,
1309
+ modelHistoryHash,
1310
+ candidateFingerprint,
1311
+ persistedProjectionFingerprint,
1312
+ semanticSummaryHash,
1313
+ prepareSourceHash,
1314
+ semanticValidationHash,
1315
+ harnessStateHash: harnessBinding?.stateHash,
1316
+ goalStateHash: goalBinding?.stateHash,
1317
+ });
737
1318
  const nextCheckpoint = {
738
- version: 1,
1319
+ version: 2,
739
1320
  checkpointId: (0, crypto_1.randomUUID)(),
740
1321
  sessionId: input.sessionId,
741
1322
  createdAt,
@@ -743,34 +1324,128 @@ function commitSessionCompactCheckpoint(input) {
743
1324
  modelId: input.modelId,
744
1325
  sourceMessageCount: input.sourceMessageCount,
745
1326
  transcriptStartMessageIndex: Math.max(0, Math.min(input.sourceMessageCount, input.transcriptStartMessageIndex)),
746
- modelHistory: input.modelHistory
747
- .filter(message => message.role !== 'system')
748
- .map(message => ({ ...message })),
1327
+ sourceBoundary: {
1328
+ startMessageIndex: 0,
1329
+ endMessageIndexExclusive: input.sourceMessageCount,
1330
+ },
1331
+ sourcePrefixHash,
1332
+ modelHistory,
1333
+ modelHistoryHash,
749
1334
  summary: {
750
1335
  ...input.summary,
751
1336
  sourceMessageCount: input.sourceMessageCount,
1337
+ schemaVersion: 2,
1338
+ strategy: input.strategy ??
1339
+ (input.summary.source === 'llm' ? 'semantic-llm-v2' : 'deterministic-fallback-v2'),
752
1340
  },
753
1341
  beforeUsage: { ...input.beforeUsage },
754
1342
  afterUsage: { ...input.afterUsage },
1343
+ contractVersion: (() => {
1344
+ const derivedContractVersion = input.harnessState?.contract?.version ?? 1;
1345
+ if (input.contractVersion !== undefined &&
1346
+ input.harnessState?.contract?.version !== undefined &&
1347
+ input.contractVersion !== derivedContractVersion) {
1348
+ throw new Error(`Compact contractVersion ${input.contractVersion} does not match Harness contract ${derivedContractVersion}`);
1349
+ }
1350
+ return input.contractVersion ?? derivedContractVersion;
1351
+ })(),
1352
+ harnessStateVersion: harnessBinding?.schemaVersion ?? input.harnessStateVersion ?? 2,
1353
+ harnessBinding,
1354
+ goalBinding,
1355
+ candidateReceipt,
1356
+ validation: {
1357
+ schemaValid: true,
1358
+ toolCallGroupsValid: true,
1359
+ sourcePrefixVerified: true,
1360
+ targetHeadroomRatio,
1361
+ achievedUsageRatio,
1362
+ targetMet: true,
1363
+ prepareSourceVerified: prepareSource ? true : undefined,
1364
+ candidateTokensVerified: input.candidate ? true : undefined,
1365
+ semanticReceiptVerified: semanticValidation ? true : undefined,
1366
+ bindingHash,
1367
+ validatedAt: createdAt,
1368
+ },
755
1369
  };
756
1370
  const previousId = session.activeCompactCheckpointId;
757
1371
  const previousTime = session.lastCompactAt;
758
- session.activeCompactCheckpointId = nextCheckpoint.checkpointId;
759
- session.lastCompactAt = createdAt;
760
- session.updatedAt = createdAt;
761
- session.updatedAtIso = new Date(createdAt).toISOString();
762
- // Commit the pointer before replacing the atomic sidecar. If the process
763
- // crashes between writes, the loader safely uses the last valid sidecar.
764
- writeSessionMetaUnlocked(session);
1372
+ const previousUpdatedAt = session.updatedAt;
1373
+ const previousUpdatedAtIso = session.updatedAtIso;
1374
+ const compactPath = (0, config_dir_1.getProjectSessionCompactPath)(session.projectPath, input.sessionId);
1375
+ const candidatePath = compactCandidatePath(compactPath);
1376
+ const previousPath = compactPreviousPath(compactPath);
1377
+ // Recover a process that crashed after installing a new sidecar but before
1378
+ // committing its pointer. The previous file is keyed by the durable pointer.
1379
+ if ((0, fs_1.existsSync)(previousPath)) {
1380
+ const installed = (0, fs_1.existsSync)(compactPath) ? parseCompactCheckpointFile(compactPath) : null;
1381
+ const previous = parseCompactCheckpointFile(previousPath);
1382
+ if (previousId &&
1383
+ installed?.checkpointId !== previousId &&
1384
+ previous?.checkpointId === previousId) {
1385
+ if ((0, fs_1.existsSync)(compactPath))
1386
+ (0, fs_1.unlinkSync)(compactPath);
1387
+ (0, fs_1.renameSync)(previousPath, compactPath);
1388
+ }
1389
+ else if (installed?.checkpointId === previousId) {
1390
+ (0, fs_1.unlinkSync)(previousPath);
1391
+ }
1392
+ else {
1393
+ throw new Error('Cannot reconcile interrupted compact checkpoint transaction');
1394
+ }
1395
+ }
1396
+ (0, atomic_write_1.atomicWriteFileSync)(candidatePath, JSON.stringify(nextCheckpoint, null, 2), { mode: 0o600 });
1397
+ const prepared = parseCompactCheckpointFile(candidatePath);
1398
+ if (prepared?.version !== 2 || prepared.checkpointId !== nextCheckpoint.checkpointId) {
1399
+ if ((0, fs_1.existsSync)(candidatePath))
1400
+ (0, fs_1.unlinkSync)(candidatePath);
1401
+ throw new Error('Compact candidate failed durable reload validation');
1402
+ }
1403
+ let previousInstalled = false;
1404
+ let candidateInstalled = false;
765
1405
  try {
766
- (0, atomic_write_1.atomicWriteFileSync)((0, config_dir_1.getProjectSessionCompactPath)(session.projectPath, input.sessionId), JSON.stringify(nextCheckpoint, null, 2), { mode: 0o600 });
1406
+ if ((0, fs_1.existsSync)(compactPath)) {
1407
+ (0, fs_1.renameSync)(compactPath, previousPath);
1408
+ previousInstalled = true;
1409
+ }
1410
+ (0, fs_1.renameSync)(candidatePath, compactPath);
1411
+ candidateInstalled = true;
1412
+ session.activeCompactCheckpointId = nextCheckpoint.checkpointId;
1413
+ session.lastCompactAt = createdAt;
1414
+ session.updatedAt = createdAt;
1415
+ session.updatedAtIso = new Date(createdAt).toISOString();
1416
+ writeSessionMetaUnlocked(session);
1417
+ const persistedSession = readSessionMetaAtPath((0, config_dir_1.getProjectSessionMetaPath)(session.projectPath, session.id));
1418
+ const persistedCheckpoint = parseCompactCheckpointFile(compactPath);
1419
+ if (persistedSession?.activeCompactCheckpointId !== nextCheckpoint.checkpointId ||
1420
+ persistedCheckpoint?.checkpointId !== nextCheckpoint.checkpointId) {
1421
+ throw new Error('Compact checkpoint commit could not be read back atomically');
1422
+ }
767
1423
  }
768
1424
  catch (error) {
769
1425
  session.activeCompactCheckpointId = previousId;
770
1426
  session.lastCompactAt = previousTime;
771
- writeSessionMetaUnlocked(session);
1427
+ session.updatedAt = previousUpdatedAt;
1428
+ session.updatedAtIso = previousUpdatedAtIso;
1429
+ if (candidateInstalled && (0, fs_1.existsSync)(compactPath))
1430
+ (0, fs_1.unlinkSync)(compactPath);
1431
+ if (previousInstalled && (0, fs_1.existsSync)(previousPath))
1432
+ (0, fs_1.renameSync)(previousPath, compactPath);
1433
+ if ((0, fs_1.existsSync)(candidatePath))
1434
+ (0, fs_1.unlinkSync)(candidatePath);
1435
+ const persistedSession = readSessionMetaAtPath((0, config_dir_1.getProjectSessionMetaPath)(session.projectPath, session.id));
1436
+ if (persistedSession?.activeCompactCheckpointId === nextCheckpoint.checkpointId) {
1437
+ writeSessionMetaUnlocked(session);
1438
+ }
772
1439
  throw error;
773
1440
  }
1441
+ if ((0, fs_1.existsSync)(previousPath)) {
1442
+ try {
1443
+ (0, fs_1.unlinkSync)(previousPath);
1444
+ }
1445
+ catch (error) {
1446
+ (0, debug_log_1.debugError)('session-storage.compactPreviousCleanup', error, previousPath);
1447
+ }
1448
+ }
774
1449
  return nextCheckpoint;
775
1450
  });
776
1451
  if (!checkpoint)
@@ -778,16 +1453,16 @@ function commitSessionCompactCheckpoint(input) {
778
1453
  return checkpoint;
779
1454
  }
780
1455
  function loadSessionTranscriptMessages(sessionId) {
781
- const messages = readSessionMessages(sessionId);
782
- const checkpoint = loadSessionCompactCheckpoint(sessionId);
783
- if (checkpoint) {
784
- return messages.slice(checkpoint.transcriptStartMessageIndex);
785
- }
786
- const session = loadSessionMeta(sessionId);
787
- const displayStartTime = session?.transcriptDisplayStartTime;
788
- return typeof displayStartTime === 'number'
789
- ? messages.filter(message => (message.timestamp ?? 0) >= displayStartTime)
790
- : messages;
1456
+ return (withLockedSession(sessionId, session => {
1457
+ const messages = readSessionMessagesForSession(session);
1458
+ const checkpoint = loadCompactCheckpointForSession(session, messages);
1459
+ if (checkpoint) {
1460
+ return messages.slice(checkpoint.transcriptStartMessageIndex);
1461
+ }
1462
+ return typeof session.transcriptDisplayStartTime === 'number'
1463
+ ? messages.filter(message => (message.timestamp ?? 0) >= (session.transcriptDisplayStartTime ?? 0))
1464
+ : messages;
1465
+ }) ?? []);
791
1466
  }
792
1467
  function hasPersistedCompactContext(messages) {
793
1468
  return messages.some(message => message.content.includes('[Orion Code Context State v2]') ||
@@ -808,8 +1483,7 @@ function endSession(sessionId) {
808
1483
  * 更新会话任务摘要
809
1484
  * 从会话消息中提取关键信息并更新元数据
810
1485
  */
811
- function updateSessionSummary(sessionId, messages) {
812
- // 提取工具使用列表
1486
+ function summarizeSessionMessages(messages) {
813
1487
  const toolsUsed = [];
814
1488
  const filesModified = [];
815
1489
  for (const msg of messages) {
@@ -835,15 +1509,52 @@ function updateSessionSummary(sessionId, messages) {
835
1509
  }
836
1510
  // 提取任务摘要(从第一个用户消息)
837
1511
  const firstUserMsg = messages.find(m => m.role === 'user' && m.content);
838
- const taskSummary = (0, redaction_1.redactTraceText)(firstUserMsg?.content ?? '').slice(0, 100);
839
- mutateSessionMeta(sessionId, session => {
840
- session.toolsUsed = [...new Set(toolsUsed)]; // unique
841
- session.filesModified = [...new Set(filesModified)]; // unique
842
- session.taskSummary =
843
- taskSummary.length > 100 ? taskSummary.slice(0, 100) + '...' : taskSummary;
844
- session.messageCount = messages.length;
1512
+ const taskText = (0, redaction_1.redactTraceText)(firstUserMsg?.content ?? '');
1513
+ const taskSummary = taskText.length > 100 ? `${taskText.slice(0, 100)}...` : taskText;
1514
+ return {
1515
+ toolsUsed: [...new Set(toolsUsed)],
1516
+ filesModified: [...new Set(filesModified)],
1517
+ taskSummary,
1518
+ messageCount: messages.length,
1519
+ };
1520
+ }
1521
+ function mergeMessageSummary(session, message) {
1522
+ if (!session.taskSummary && message.role === 'user' && message.content) {
1523
+ const taskText = (0, redaction_1.redactTraceText)(message.content);
1524
+ session.taskSummary = taskText.length > 100 ? `${taskText.slice(0, 100)}...` : taskText;
1525
+ }
1526
+ if (message.role !== 'assistant' || !message.tool_calls)
1527
+ return;
1528
+ const tools = new Set(session.toolsUsed ?? []);
1529
+ const files = new Set(session.filesModified ?? []);
1530
+ for (const toolCall of message.tool_calls) {
1531
+ tools.add(toolCall.function.name);
1532
+ if (toolCall.function.name !== 'write_file' && toolCall.function.name !== 'edit_file')
1533
+ continue;
1534
+ try {
1535
+ const args = JSON.parse(toolCall.function.arguments);
1536
+ if (typeof args.path === 'string')
1537
+ files.add(args.path);
1538
+ }
1539
+ catch (error) {
1540
+ (0, debug_log_1.debugError)('session-storage.parseToolArgs', error, toolCall.function.name);
1541
+ }
1542
+ }
1543
+ session.toolsUsed = [...tools];
1544
+ session.filesModified = [...files];
1545
+ }
1546
+ function updateSessionSummary(sessionId, _messages) {
1547
+ // Callers may hold a stale transcript snapshot. Re-read under the same lock
1548
+ // used by append/delete so reconciliation can never roll metadata backwards.
1549
+ withLockedSession(sessionId, session => {
1550
+ const summary = summarizeSessionMessages(readSessionMessagesForSession(session));
1551
+ session.toolsUsed = summary.toolsUsed;
1552
+ session.filesModified = summary.filesModified;
1553
+ session.taskSummary = summary.taskSummary;
1554
+ session.messageCount = summary.messageCount;
845
1555
  session.updatedAt = Date.now();
846
1556
  session.updatedAtIso = new Date(session.updatedAt).toISOString();
1557
+ writeSessionMetaUnlocked(session);
847
1558
  });
848
1559
  }
849
1560
  /**
@@ -924,6 +1635,7 @@ function appendSessionMessage(sessionId, message) {
924
1635
  // Keep transcript, metadata, and search index inside the same session
925
1636
  // critical section so concurrent processes cannot lose counters.
926
1637
  (0, session_index_1.updateSessionIndex)(sessionId, session.projectPath, message);
1638
+ mergeMessageSummary(session, message);
927
1639
  session.updatedAt = Date.now();
928
1640
  session.updatedAtIso = new Date(session.updatedAt).toISOString();
929
1641
  session.messageCount = (session.messageCount ?? 0) + 1;
@@ -945,6 +1657,7 @@ function appendSessionMessages(sessionId, messages) {
945
1657
  });
946
1658
  for (const message of messages) {
947
1659
  (0, session_index_1.updateSessionIndex)(sessionId, session.projectPath, message);
1660
+ mergeMessageSummary(session, message);
948
1661
  }
949
1662
  session.updatedAt = Date.now();
950
1663
  session.updatedAtIso = new Date(session.updatedAt).toISOString();
@@ -982,6 +1695,10 @@ function overwriteSessionMessagesUnlocked(session, messages) {
982
1695
  (0, session_index_1.updateSessionIndex)(session.id, session.projectPath, message);
983
1696
  }
984
1697
  session.messageCount = messages.length;
1698
+ const summary = summarizeSessionMessages(messages);
1699
+ session.toolsUsed = summary.toolsUsed;
1700
+ session.filesModified = summary.filesModified;
1701
+ session.taskSummary = summary.taskSummary;
985
1702
  session.updatedAt = Date.now();
986
1703
  session.updatedAtIso = new Date(session.updatedAt).toISOString();
987
1704
  writeSessionMetaUnlocked(session);
@@ -1068,87 +1785,98 @@ function readSessionMessagesForSession(session) {
1068
1785
  }
1069
1786
  }
1070
1787
  function appendSessionTraceEvent(sessionId, event) {
1071
- const session = loadSessionMeta(sessionId);
1072
- if (!session)
1073
- return null;
1074
- (0, config_dir_1.ensureConfigDir)();
1075
- const safeEvent = sanitizeTraceEvent(event);
1076
- const traceEvent = {
1077
- ...safeEvent,
1078
- sessionId,
1079
- turnId: String(safeEvent.turnId),
1080
- timestamp: safeEvent.timestamp ?? Date.now(),
1081
- };
1082
- (0, fs_1.appendFileSync)((0, config_dir_1.getProjectSessionTracePath)(session.projectPath, sessionId), `${JSON.stringify(traceEvent)}\n`, { mode: 0o600 });
1083
- return traceEvent;
1788
+ return withLockedSession(sessionId, session => {
1789
+ (0, config_dir_1.ensureConfigDir)();
1790
+ const safeEvent = sanitizeTraceEvent(event);
1791
+ const traceEvent = {
1792
+ ...safeEvent,
1793
+ sessionId,
1794
+ turnId: String(safeEvent.turnId),
1795
+ timestamp: safeEvent.timestamp ?? Date.now(),
1796
+ };
1797
+ (0, fs_1.appendFileSync)((0, config_dir_1.getProjectSessionTracePath)(session.projectPath, sessionId), `${JSON.stringify(traceEvent)}\n`, { mode: 0o600 });
1798
+ return traceEvent;
1799
+ });
1084
1800
  }
1085
1801
  function readSessionTraceEvents(sessionId) {
1086
- const session = loadSessionMeta(sessionId);
1087
- if (!session)
1088
- return [];
1089
- const path = (0, config_dir_1.getProjectSessionTracePath)(session.projectPath, sessionId);
1090
- if (!(0, fs_1.existsSync)(path))
1091
- return [];
1092
- try {
1093
- const content = (0, fs_1.readFileSync)(path, 'utf-8');
1094
- return content
1095
- .trim()
1096
- .split('\n')
1097
- .filter(Boolean)
1098
- .flatMap(line => {
1099
- try {
1100
- const parsed = JSON.parse(line);
1101
- if (!parsed.type || !parsed.turnId || !parsed.timestamp)
1802
+ return (withLockedSession(sessionId, session => {
1803
+ const path = (0, config_dir_1.getProjectSessionTracePath)(session.projectPath, sessionId);
1804
+ if (!(0, fs_1.existsSync)(path))
1805
+ return [];
1806
+ try {
1807
+ const content = (0, fs_1.readFileSync)(path, 'utf-8');
1808
+ return content
1809
+ .trim()
1810
+ .split('\n')
1811
+ .filter(Boolean)
1812
+ .flatMap(line => {
1813
+ try {
1814
+ const parsed = JSON.parse(line);
1815
+ if (!parsed.type || !parsed.turnId || !parsed.timestamp)
1816
+ return [];
1817
+ const sanitized = sanitizeTraceEvent({
1818
+ ...parsed,
1819
+ turnId: String(parsed.turnId),
1820
+ });
1821
+ return [
1822
+ {
1823
+ ...sanitized,
1824
+ sessionId,
1825
+ turnId: String(sanitized.turnId),
1826
+ },
1827
+ ];
1828
+ }
1829
+ catch (error) {
1830
+ // Drop only the malformed trace line, keep the rest of the trace.
1831
+ (0, debug_log_1.debugError)('session-storage.parseTraceLine', error);
1102
1832
  return [];
1103
- const sanitized = sanitizeTraceEvent({
1104
- ...parsed,
1105
- turnId: String(parsed.turnId),
1106
- });
1107
- return [
1108
- {
1109
- ...sanitized,
1110
- sessionId,
1111
- turnId: String(sanitized.turnId),
1112
- },
1113
- ];
1114
- }
1115
- catch (error) {
1116
- // Drop only the malformed trace line, keep the rest of the trace.
1117
- (0, debug_log_1.debugError)('session-storage.parseTraceLine', error);
1118
- return [];
1119
- }
1120
- });
1121
- }
1122
- catch (error) {
1123
- // An empty trace looks like "nothing happened" instead of "trace
1124
- // unreadable", which misleads anyone debugging a past run.
1125
- (0, debug_log_1.debugError)('session-storage.readTraceEvents', error, path);
1126
- return [];
1127
- }
1833
+ }
1834
+ });
1835
+ }
1836
+ catch (error) {
1837
+ // An empty trace looks like "nothing happened" instead of "trace
1838
+ // unreadable", which misleads anyone debugging a past run.
1839
+ (0, debug_log_1.debugError)('session-storage.readTraceEvents', error, path);
1840
+ return [];
1841
+ }
1842
+ }) ?? []);
1128
1843
  }
1129
1844
  /**
1130
1845
  * 读取会话消息并转换为 Message 格式(用于恢复对话历史)
1131
1846
  * 包含完整的 tool_calls 信息,确保 LLM 能理解之前的工具调用
1132
1847
  */
1133
1848
  function loadSessionHistory(sessionId) {
1134
- const messages = readSessionMessages(sessionId);
1135
- const checkpoint = loadSessionCompactCheckpoint(sessionId);
1136
- if (checkpoint) {
1137
- return (0, tool_call_groups_1.sealToolCallGroups)([
1138
- ...checkpoint.modelHistory.map(message => ({ ...message })),
1139
- ...messages.slice(checkpoint.sourceMessageCount).map(sessionMessageToModelMessage),
1140
- ]);
1141
- }
1142
- const session = loadSessionMeta(sessionId);
1143
- const displayStartTime = session?.transcriptDisplayStartTime;
1144
- let modelVisibleMessages = messages;
1145
- if (typeof displayStartTime === 'number') {
1146
- const afterDisplayStart = messages.filter(message => (message.timestamp ?? 0) >= displayStartTime);
1147
- modelVisibleMessages = hasPersistedCompactContext(afterDisplayStart)
1148
- ? afterDisplayStart
1149
- : messages;
1150
- }
1151
- return (0, tool_call_groups_1.sealToolCallGroups)(modelVisibleMessages.map(sessionMessageToModelMessage));
1849
+ return (withLockedSession(sessionId, session => {
1850
+ const messages = readSessionMessagesForSession(session);
1851
+ const checkpoint = loadCompactCheckpointForSession(session, messages);
1852
+ if (checkpoint) {
1853
+ const restored = [
1854
+ ...checkpoint.modelHistory.map(message => ({ ...message })),
1855
+ ...messages.slice(checkpoint.sourceMessageCount).map(sessionMessageToModelMessage),
1856
+ ];
1857
+ if (checkpoint.version === 2) {
1858
+ try {
1859
+ (0, tool_call_groups_1.assertToolCallGroups)(restored);
1860
+ return restored;
1861
+ }
1862
+ catch (error) {
1863
+ // Never mutate or synthesize data inside a validated V2
1864
+ // replacement. Fall back to the append-only transcript instead.
1865
+ (0, debug_log_1.debugError)('session-storage.restoreCompactCheckpoint.toolGroups', error, sessionId);
1866
+ return (0, tool_call_groups_1.sealToolCallGroups)(messages.map(sessionMessageToModelMessage));
1867
+ }
1868
+ }
1869
+ return (0, tool_call_groups_1.sealToolCallGroups)(restored);
1870
+ }
1871
+ let modelVisibleMessages = messages;
1872
+ if (typeof session.transcriptDisplayStartTime === 'number') {
1873
+ const afterDisplayStart = messages.filter(message => (message.timestamp ?? 0) >= (session.transcriptDisplayStartTime ?? 0));
1874
+ modelVisibleMessages = hasPersistedCompactContext(afterDisplayStart)
1875
+ ? afterDisplayStart
1876
+ : messages;
1877
+ }
1878
+ return (0, tool_call_groups_1.sealToolCallGroups)(modelVisibleMessages.map(sessionMessageToModelMessage));
1879
+ }) ?? []);
1152
1880
  }
1153
1881
  function sessionMessageToModelMessage(message) {
1154
1882
  const result = {
@@ -1231,11 +1959,14 @@ function renameSession(sessionId, name) {
1231
1959
  function deleteSession(sessionId) {
1232
1960
  return (withLockedSession(sessionId, session => {
1233
1961
  let deleted = false;
1962
+ const compactPath = (0, config_dir_1.getProjectSessionCompactPath)(session.projectPath, sessionId);
1234
1963
  const paths = [
1235
1964
  (0, config_dir_1.getProjectSessionMetaPath)(session.projectPath, sessionId),
1236
1965
  (0, config_dir_1.getProjectSessionMessagesPath)(session.projectPath, sessionId),
1237
1966
  (0, config_dir_1.getProjectSessionHarnessPath)(session.projectPath, sessionId),
1238
- (0, config_dir_1.getProjectSessionCompactPath)(session.projectPath, sessionId),
1967
+ compactPath,
1968
+ compactCandidatePath(compactPath),
1969
+ compactPreviousPath(compactPath),
1239
1970
  (0, config_dir_1.getProjectSessionTracePath)(session.projectPath, sessionId),
1240
1971
  ];
1241
1972
  // Preserve a durable deletion fence so a stale writer in another process