hippo-memory 1.61.0 → 1.63.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 (377) hide show
  1. package/README.md +37 -53
  2. package/dist/agent-memories/apply.d.ts +3 -1
  3. package/dist/agent-memories/apply.js +23 -1
  4. package/dist/agent-memories/claude-code.d.ts +4 -1
  5. package/dist/agent-memories/claude-code.js +53 -9
  6. package/dist/agent-memories/report.d.ts +1 -0
  7. package/dist/agent-memories/report.js +2 -2
  8. package/dist/agent-memories/sync.d.ts +3 -3
  9. package/dist/agent-memories/sync.js +46 -24
  10. package/dist/agent-memories/types.d.ts +0 -2
  11. package/dist/ambient-store.d.ts +4 -4
  12. package/dist/ambient-store.js +4 -3
  13. package/dist/api/assemble.d.ts +7 -10
  14. package/dist/api/assemble.js +62 -66
  15. package/dist/api/audit.d.ts +2 -2
  16. package/dist/api/audit.js +2 -2
  17. package/dist/api/auth.d.ts +6 -6
  18. package/dist/api/auth.js +5 -7
  19. package/dist/api/context-select.d.ts +50 -0
  20. package/dist/api/context-select.js +342 -0
  21. package/dist/api/context-types.d.ts +12 -17
  22. package/dist/api/context.d.ts +5 -4
  23. package/dist/api/context.js +208 -535
  24. package/dist/api/dormant.js +1 -1
  25. package/dist/api/drill-down.d.ts +4 -4
  26. package/dist/api/drill-down.js +56 -41
  27. package/dist/api/outcome.d.ts +8 -13
  28. package/dist/api/outcome.js +13 -17
  29. package/dist/api/promote.d.ts +5 -8
  30. package/dist/api/promote.js +55 -66
  31. package/dist/api/quarantine.js +3 -2
  32. package/dist/api/recall-types.d.ts +43 -55
  33. package/dist/api/recall.d.ts +3 -3
  34. package/dist/api/recall.js +310 -456
  35. package/dist/api/remember.d.ts +2 -2
  36. package/dist/api/sleep.d.ts +7 -35
  37. package/dist/api/sleep.js +206 -220
  38. package/dist/api/tokens.d.ts +2 -2
  39. package/dist/api/tokens.js +2 -2
  40. package/dist/api/types.d.ts +6 -10
  41. package/dist/api/types.js +2 -4
  42. package/dist/audit-prune.d.ts +4 -6
  43. package/dist/audit-prune.js +3 -5
  44. package/dist/audit.js +11 -33
  45. package/dist/auth.d.ts +8 -9
  46. package/dist/auth.js +5 -7
  47. package/dist/autolearn.d.ts +1 -1
  48. package/dist/autolearn.js +1 -1
  49. package/dist/availability.js +3 -5
  50. package/dist/capture/command.d.ts +5 -4
  51. package/dist/capture/command.js +8 -22
  52. package/dist/capture/compact.d.ts +2 -2
  53. package/dist/capture/compact.js +11 -15
  54. package/dist/capture/extract.js +37 -115
  55. package/dist/capture-error.d.ts +1 -1
  56. package/dist/capture-error.js +1 -1
  57. package/dist/churn-git.d.ts +1 -1
  58. package/dist/churn-git.js +1 -1
  59. package/dist/cli/audit.js +3 -4
  60. package/dist/cli/auth.js +3 -6
  61. package/dist/cli/briefs.js +324 -306
  62. package/dist/cli/context.js +44 -34
  63. package/dist/cli/continuity.js +283 -271
  64. package/dist/cli/curate.d.ts +1 -1
  65. package/dist/cli/curate.js +43 -58
  66. package/dist/cli/dag.js +5 -9
  67. package/dist/cli/decisions.js +334 -345
  68. package/dist/cli/explain.js +68 -61
  69. package/dist/cli/goals.js +1 -1
  70. package/dist/cli/init.js +1 -1
  71. package/dist/cli/maintenance.js +62 -51
  72. package/dist/cli/playbooks.js +391 -379
  73. package/dist/cli/projects.js +11 -6
  74. package/dist/cli/recall.js +30 -44
  75. package/dist/cli/remember.js +118 -87
  76. package/dist/cli/session-hooks.js +106 -115
  77. package/dist/cli/setup.d.ts +1 -1
  78. package/dist/cli/setup.js +267 -250
  79. package/dist/cli/shared.js +3 -3
  80. package/dist/cli/slack.js +1 -1
  81. package/dist/cli/sleep.js +27 -1
  82. package/dist/cli/status.d.ts +4 -4
  83. package/dist/cli/status.js +80 -76
  84. package/dist/cli/transfer.js +88 -107
  85. package/dist/cli/usage.js +9 -6
  86. package/dist/cli.d.ts +1 -1
  87. package/dist/cli.js +4 -9
  88. package/dist/compaction-record.d.ts +2 -2
  89. package/dist/compaction-record.js +89 -68
  90. package/dist/compare.d.ts +11 -16
  91. package/dist/compare.js +11 -16
  92. package/dist/config.d.ts +19 -18
  93. package/dist/config.js +90 -67
  94. package/dist/connectors/github/backfill.d.ts +2 -2
  95. package/dist/connectors/github/backfill.js +8 -15
  96. package/dist/connectors/github/cli-impl.js +3 -8
  97. package/dist/connectors/github/deletion.d.ts +5 -12
  98. package/dist/connectors/github/deletion.js +5 -12
  99. package/dist/connectors/github/dlq.d.ts +6 -9
  100. package/dist/connectors/github/dlq.js +2 -3
  101. package/dist/connectors/github/ingest.d.ts +5 -7
  102. package/dist/connectors/github/ingest.js +8 -12
  103. package/dist/connectors/github/octokit-client.d.ts +3 -5
  104. package/dist/connectors/github/octokit-client.js +5 -6
  105. package/dist/connectors/github/signature.d.ts +9 -39
  106. package/dist/connectors/github/signature.js +9 -39
  107. package/dist/connectors/github/tenant-routing.d.ts +1 -1
  108. package/dist/connectors/github/tenant-routing.js +1 -1
  109. package/dist/connectors/github/transform.js +2 -2
  110. package/dist/connectors/github/types.d.ts +2 -10
  111. package/dist/connectors/github/types.js +1 -3
  112. package/dist/connectors/slack/deletion.d.ts +3 -8
  113. package/dist/connectors/slack/deletion.js +3 -8
  114. package/dist/connectors/slack/dlq.d.ts +1 -1
  115. package/dist/connectors/slack/ingest.d.ts +1 -1
  116. package/dist/connectors/slack/ingest.js +7 -16
  117. package/dist/connectors/slack/signature.d.ts +1 -1
  118. package/dist/connectors/slack/tenant-routing.d.ts +3 -5
  119. package/dist/connectors/slack/tenant-routing.js +3 -5
  120. package/dist/connectors/slack/transform.d.ts +5 -6
  121. package/dist/connectors/slack/transform.js +5 -6
  122. package/dist/connectors/slack/types.d.ts +2 -6
  123. package/dist/connectors/slack/types.js +1 -3
  124. package/dist/connectors/slack/web-client.js +10 -3
  125. package/dist/connectors/slack/workspaces.d.ts +3 -5
  126. package/dist/connectors/slack/workspaces.js +3 -5
  127. package/dist/consolidate/conflicts.js +3 -14
  128. package/dist/consolidate/decay.js +9 -29
  129. package/dist/consolidate/llm-passes.js +4 -5
  130. package/dist/consolidate/merge.js +8 -23
  131. package/dist/consolidate/run.d.ts +1 -8
  132. package/dist/consolidate/run.js +3 -25
  133. package/dist/consolidate/sleep.js +5 -17
  134. package/dist/consolidate/traces.js +9 -21
  135. package/dist/customer-notes.d.ts +5 -7
  136. package/dist/customer-notes.js +82 -76
  137. package/dist/dag.d.ts +10 -21
  138. package/dist/dag.js +189 -203
  139. package/dist/db/continuity.js +2 -2
  140. package/dist/db/migrations/v14.js +1 -1
  141. package/dist/db/migrations/v15.js +1 -2
  142. package/dist/db/migrations/v16.js +3 -4
  143. package/dist/db/migrations/v17.js +2 -3
  144. package/dist/db/migrations/v19.js +1 -1
  145. package/dist/db/migrations/v20.js +1 -1
  146. package/dist/db/migrations/v21.js +2 -6
  147. package/dist/db/migrations/v22.js +2 -4
  148. package/dist/db/migrations/v23.js +1 -1
  149. package/dist/db/migrations/v24.js +4 -6
  150. package/dist/db/migrations/v25.js +2 -3
  151. package/dist/db/migrations/v26.js +3 -3
  152. package/dist/db/migrations/v27.js +2 -10
  153. package/dist/db/migrations/v28.js +5 -8
  154. package/dist/db/migrations/v29.js +3 -4
  155. package/dist/db/migrations/v30.js +2 -2
  156. package/dist/db/migrations/v31.js +1 -1
  157. package/dist/db/migrations/v32.js +1 -1
  158. package/dist/db/migrations/v33.js +3 -3
  159. package/dist/db/migrations/v34.js +1 -1
  160. package/dist/db/migrations/v35.js +3 -4
  161. package/dist/db/migrations/v36.js +3 -4
  162. package/dist/db/migrations/v37.js +5 -5
  163. package/dist/db/migrations/v38.js +7 -8
  164. package/dist/db/migrations/v39.js +1 -1
  165. package/dist/db/migrations/v40.js +4 -16
  166. package/dist/db/migrations/v41.js +3 -4
  167. package/dist/db/migrations/v42.js +3 -4
  168. package/dist/db/migrations/v45.js +1 -1
  169. package/dist/db/migrations/v46.js +1 -1
  170. package/dist/db/migrations/v47.js +1 -1
  171. package/dist/db/migrations/v48.js +1 -1
  172. package/dist/decisions.d.ts +2 -2
  173. package/dist/decisions.js +97 -80
  174. package/dist/dedupe.js +86 -61
  175. package/dist/delivery-recorder.js +154 -135
  176. package/dist/doctor.js +129 -110
  177. package/dist/dormant.js +1 -4
  178. package/dist/embedding-provider.d.ts +4 -8
  179. package/dist/embedding-provider.js +4 -8
  180. package/dist/embeddings.js +55 -47
  181. package/dist/env.d.ts +1 -1
  182. package/dist/env.js +12 -12
  183. package/dist/escape.d.ts +5 -0
  184. package/dist/escape.js +10 -0
  185. package/dist/eval-stats.d.ts +1 -2
  186. package/dist/eval-stats.js +1 -2
  187. package/dist/eval-suite.js +27 -21
  188. package/dist/extract.js +4 -9
  189. package/dist/failure-log.d.ts +3 -3
  190. package/dist/failure-log.js +1 -1
  191. package/dist/forward-claim-detector.d.ts +2 -4
  192. package/dist/forward-claim-detector.js +6 -11
  193. package/dist/goals.d.ts +3 -3
  194. package/dist/goals.js +103 -91
  195. package/dist/graph/read.d.ts +2 -2
  196. package/dist/graph/read.js +5 -6
  197. package/dist/graph/types.d.ts +8 -8
  198. package/dist/graph/write.d.ts +7 -14
  199. package/dist/graph/write.js +16 -23
  200. package/dist/graph-extract.d.ts +7 -8
  201. package/dist/graph-extract.js +62 -72
  202. package/dist/graph-recall.d.ts +2 -2
  203. package/dist/graph-recall.js +55 -49
  204. package/dist/graph-stream.d.ts +5 -6
  205. package/dist/graph-stream.js +66 -57
  206. package/dist/graph-view.d.ts +2 -2
  207. package/dist/graph-view.js +7 -7
  208. package/dist/half-life-migration.d.ts +1 -2
  209. package/dist/half-life-migration.js +2 -3
  210. package/dist/hooks/codex-session.js +1 -1
  211. package/dist/hooks/codex-wrapper.d.ts +1 -1
  212. package/dist/hooks/codex-wrapper.js +3 -2
  213. package/dist/hooks/json-hooks.d.ts +2 -2
  214. package/dist/hooks/json-hooks.js +5 -4
  215. package/dist/hooks/opencode.d.ts +1 -1
  216. package/dist/hooks/opencode.js +5 -4
  217. package/dist/hooks/shared.d.ts +3 -7
  218. package/dist/hooks/shared.js +1 -8
  219. package/dist/http-util.d.ts +2 -3
  220. package/dist/http-util.js +3 -0
  221. package/dist/importers/core.d.ts +2 -9
  222. package/dist/importers/core.js +15 -30
  223. package/dist/importers/sources.js +2 -1
  224. package/dist/importers/vault.js +2 -20
  225. package/dist/incidents.d.ts +1 -1
  226. package/dist/incidents.js +46 -39
  227. package/dist/instruction-detect.d.ts +1 -1
  228. package/dist/instruction-detect.js +1 -1
  229. package/dist/invalidation.d.ts +3 -0
  230. package/dist/invalidation.js +160 -114
  231. package/dist/json.d.ts +5 -0
  232. package/dist/json.js +4 -0
  233. package/dist/judgment.js +1 -2
  234. package/dist/local-embedding.js +1 -1
  235. package/dist/mcp/admin-tools.js +7 -17
  236. package/dist/mcp/format.js +1 -1
  237. package/dist/mcp/framing.js +3 -6
  238. package/dist/mcp/protocol.d.ts +2 -5
  239. package/dist/mcp/protocol.js +1 -3
  240. package/dist/mcp/recall-tools.js +12 -15
  241. package/dist/mcp/request.js +4 -3
  242. package/dist/mcp/session-state.js +2 -3
  243. package/dist/mcp/stdio.js +2 -1
  244. package/dist/mcp/tools.js +9 -6
  245. package/dist/memory-value-weights.d.ts +5 -8
  246. package/dist/memory-value-weights.js +5 -8
  247. package/dist/memory-value.d.ts +13 -13
  248. package/dist/memory-value.js +26 -37
  249. package/dist/memory.d.ts +20 -22
  250. package/dist/memory.js +24 -48
  251. package/dist/multihop.d.ts +1 -1
  252. package/dist/multihop.js +3 -2
  253. package/dist/owner-validation.d.ts +4 -5
  254. package/dist/owner-validation.js +4 -5
  255. package/dist/physics.d.ts +4 -4
  256. package/dist/physics.js +7 -9
  257. package/dist/policies.d.ts +9 -10
  258. package/dist/policies.js +96 -81
  259. package/dist/postinstall.js +3 -6
  260. package/dist/predictions/planning-fallacy.d.ts +9 -14
  261. package/dist/predictions/planning-fallacy.js +10 -16
  262. package/dist/predictions/store.d.ts +15 -23
  263. package/dist/predictions/store.js +36 -33
  264. package/dist/processes.d.ts +2 -7
  265. package/dist/processes.js +88 -72
  266. package/dist/project-briefs.d.ts +2 -3
  267. package/dist/project-briefs.js +141 -118
  268. package/dist/project-identity.d.ts +22 -9
  269. package/dist/project-identity.js +47 -12
  270. package/dist/project-merge.d.ts +28 -5
  271. package/dist/project-merge.js +213 -46
  272. package/dist/project-remote.d.ts +12 -0
  273. package/dist/project-remote.js +138 -0
  274. package/dist/prompt-recall.js +1 -2
  275. package/dist/rate-limit.d.ts +1 -1
  276. package/dist/rate-limit.js +1 -1
  277. package/dist/raw-archive.d.ts +9 -0
  278. package/dist/raw-archive.js +70 -53
  279. package/dist/recall-history.d.ts +19 -20
  280. package/dist/recall-history.js +24 -42
  281. package/dist/recall-pipeline.js +4 -28
  282. package/dist/recall-scope.d.ts +7 -8
  283. package/dist/recall-scope.js +7 -8
  284. package/dist/recall-trace.d.ts +5 -9
  285. package/dist/recall-trace.js +6 -10
  286. package/dist/refine-llm.d.ts +1 -1
  287. package/dist/refine-llm.js +2 -2
  288. package/dist/reject-flow.d.ts +3 -4
  289. package/dist/reject-flow.js +122 -117
  290. package/dist/rejection.d.ts +5 -6
  291. package/dist/rejection.js +7 -15
  292. package/dist/rerankers/clef.d.ts +1 -1
  293. package/dist/rerankers/jev.d.ts +1 -2
  294. package/dist/rerankers/jev.js +4 -5
  295. package/dist/rerankers/llm.d.ts +1 -2
  296. package/dist/rerankers/llm.js +1 -2
  297. package/dist/rerankers/types.d.ts +1 -2
  298. package/dist/rrf.d.ts +2 -2
  299. package/dist/rrf.js +2 -2
  300. package/dist/search/bm25-search.d.ts +1 -1
  301. package/dist/search/bm25-search.js +2 -1
  302. package/dist/search/boosts.js +2 -1
  303. package/dist/search/hybrid.d.ts +1 -1
  304. package/dist/search/hybrid.js +2 -1
  305. package/dist/search/physics-search.d.ts +1 -1
  306. package/dist/search/physics-search.js +2 -1
  307. package/dist/search/types.d.ts +2 -0
  308. package/dist/search/types.js +3 -1
  309. package/dist/secret-detect.d.ts +4 -5
  310. package/dist/secret-detect.js +6 -10
  311. package/dist/server/auth.js +5 -5
  312. package/dist/server/client-ip.js +1 -1
  313. package/dist/server/cursor.js +2 -1
  314. package/dist/server/mcp-http.js +4 -4
  315. package/dist/server/request.d.ts +3 -6
  316. package/dist/server/request.js +6 -7
  317. package/dist/server/routes/admin.js +5 -4
  318. package/dist/server/routes/customer-notes.js +6 -5
  319. package/dist/server/routes/decisions.js +4 -3
  320. package/dist/server/routes/incidents.js +7 -5
  321. package/dist/server/routes/memories.js +7 -7
  322. package/dist/server/routes/policies.js +3 -2
  323. package/dist/server/routes/predictions.js +12 -15
  324. package/dist/server/routes/processes.js +3 -2
  325. package/dist/server/routes/project-briefs.js +8 -7
  326. package/dist/server/routes/recall.js +95 -93
  327. package/dist/server/routes/skills.js +6 -5
  328. package/dist/server/types.d.ts +1 -1
  329. package/dist/server/validation.d.ts +1 -2
  330. package/dist/server/validation.js +7 -14
  331. package/dist/server-detect.js +72 -58
  332. package/dist/server.d.ts +2 -2
  333. package/dist/server.js +131 -117
  334. package/dist/shared.d.ts +26 -17
  335. package/dist/shared.js +102 -104
  336. package/dist/skills.d.ts +3 -3
  337. package/dist/skills.js +88 -72
  338. package/dist/store/audit-event.d.ts +2 -2
  339. package/dist/store/audit-event.js +1 -1
  340. package/dist/store/candidates.d.ts +2 -2
  341. package/dist/store/candidates.js +4 -3
  342. package/dist/store/conflicts.js +30 -22
  343. package/dist/store/delete-and-batch.d.ts +11 -14
  344. package/dist/store/delete-and-batch.js +40 -91
  345. package/dist/store/entry-reads.d.ts +17 -25
  346. package/dist/store/entry-reads.js +59 -40
  347. package/dist/store/entry-row.d.ts +6 -24
  348. package/dist/store/entry-row.js +6 -24
  349. package/dist/store/entry-writes.d.ts +6 -7
  350. package/dist/store/entry-writes.js +13 -11
  351. package/dist/store/handoffs.d.ts +1 -1
  352. package/dist/store/handoffs.js +7 -10
  353. package/dist/store/index-and-stats.d.ts +2 -6
  354. package/dist/store/index-and-stats.js +4 -10
  355. package/dist/store/mirrors.d.ts +6 -19
  356. package/dist/store/mirrors.js +14 -39
  357. package/dist/store/open.js +9 -31
  358. package/dist/store/rows.d.ts +5 -11
  359. package/dist/store/rows.js +6 -11
  360. package/dist/store/search-rows.d.ts +17 -34
  361. package/dist/store/search-rows.js +34 -56
  362. package/dist/store/sessions.d.ts +4 -5
  363. package/dist/store/sessions.js +5 -6
  364. package/dist/store/summaries.d.ts +13 -17
  365. package/dist/store/summaries.js +26 -70
  366. package/dist/support-bundle.js +4 -8
  367. package/dist/tenant.d.ts +1 -5
  368. package/dist/token-ledger.d.ts +1 -1
  369. package/dist/token-ledger.js +3 -5
  370. package/dist/trace.js +1 -3
  371. package/dist/version.d.ts +1 -1
  372. package/dist/version.js +1 -1
  373. package/dist/working-memory.d.ts +1 -1
  374. package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
  375. package/extensions/openclaw-plugin/package.json +1 -1
  376. package/openclaw.plugin.json +1 -1
  377. package/package.json +1 -1
@@ -12,9 +12,9 @@ import { readSessionScan, recordSessionDigest } from '../session-digest.js';
12
12
  import { openHippoDb, closeHippoDb } from '../db.js';
13
13
  import { captureToolFailure } from '../capture-error.js';
14
14
  import { estimateTokens, isSubagentPayload, readApiCalls, recordRereads, recordTokenUse, } from '../token-ledger.js';
15
- import { currentMachine, importAtCompaction } from '../agent-memories/sync.js';
15
+ import { currentMachine, importSessionFolder } from '../agent-memories/sync.js';
16
16
  import { summaryLine } from '../agent-memories/report.js';
17
- import { deriveOriginProject } from '../project-identity.js';
17
+ import { resolveProjectIdentity } from '../project-identity.js';
18
18
  import { getGlobalRoot } from '../shared.js';
19
19
  import { cmdCapture } from '../capture/command.js';
20
20
  import { cmdPreCompact, cmdPostCompact, transcriptWorkingState } from '../capture/compact.js';
@@ -63,22 +63,19 @@ export function cmdLastSleep(flags) {
63
63
  * memories here — the UserPromptSubmit hook already re-injects those every
64
64
  * turn, so duplicating them here would double token cost for nothing.
65
65
  *
66
- * Same exit-0/crash-safety contract as `hippo pre-compact` (critic round
67
- * 2): every path exits 0. A malformed payload or a store read failure
66
+ * Same exit-0/crash-safety contract as `hippo pre-compact`: every path
67
+ * exits 0. A malformed payload or a store read failure
68
68
  * degrades to empty stdout, never a thrown error — a failing SessionStart
69
69
  * hook must not pollute session startup.
70
70
  */
71
- // X8: session-event content is capped at print time only — the shared
72
- // printSessionEvents stays untouched for every other caller.
71
+ // Capped at print time only, so the shared printSessionEvents stays untouched for every other caller.
73
72
  const COMPACT_RESUME_EVENT_CONTENT_CAP = 400;
74
73
  // A snapshot older than this was not written for this compaction (pre-compact skipped), so restoring it is stale, not a resume.
75
74
  const COMPACT_RESUME_MAX_AGE_MS = 15 * 60_000;
76
75
  function cmdCompactResume(hippoRoot, stdinText, stdinTimedOut) {
77
76
  try {
78
- // X3: gate on the non-exiting isInitialized check before any
79
- // store-opening call (loadActiveTaskSnapshot/listSessionEvents both
80
- // call initStore internally, which would silently create a store in a
81
- // project that never ran `hippo init` — this hook fires globally).
77
+ // Gate on the non-exiting isInitialized check first: the store reads below call initStore, which would
78
+ // silently create a store in a project that never ran `hippo init`, and this hook fires globally.
82
79
  if (!isInitialized(hippoRoot)) {
83
80
  process.exit(0);
84
81
  }
@@ -88,7 +85,7 @@ function cmdCompactResume(hippoRoot, stdinText, stdinTimedOut) {
88
85
  // carries a different source (e.g. 'startup') means the matcher-based
89
86
  // gate failed to apply — stay silent rather than print stale state.
90
87
  const nonEmptyStdin = !!stdinText && stdinText.trim() !== '';
91
- // Without a payload session_id the X5 cross-restore guard below can
88
+ // Without a payload session_id the cross-restore guard below can
92
89
  // never fire, so a timed-out empty read must not reach the print path.
93
90
  let suppressOutput = stdinTimedOut && !nonEmptyStdin;
94
91
  let payloadSessionId = null;
@@ -102,19 +99,13 @@ function cmdCompactResume(hippoRoot, stdinText, stdinTimedOut) {
102
99
  payload = null;
103
100
  }
104
101
  if (!payload || typeof payload !== 'object') {
105
- // X13: fail closed on malformed non-empty stdin. The earlier
106
- // "print on malformed" behavior survives only for TTY/no-stdin
107
- // manual invocation (nonEmptyStdin is false there, this branch
108
- // never runs).
102
+ // Fail closed on malformed non-empty stdin; only a TTY/no-stdin manual run, which never reaches here, prints.
109
103
  suppressOutput = true;
110
104
  }
111
105
  else {
112
- // Fail closed on structurally incomplete payloads too ({}, [],
113
- // source missing/non-string): any parsed non-empty payload must say
114
- // source === 'compact' to print. Real SessionStart payloads always
115
- // carry source; only the TTY/no-stdin manual path prints without
116
- // one (codex round 3).
117
- // A sub-agent's payload carries its parent's session id, so X5 would pass and restore the parent's snapshot into it.
106
+ // Fail closed on structurally incomplete payloads too ({}, [], source missing/non-string): real
107
+ // SessionStart payloads always carry source, so a parsed one must say 'compact' to print.
108
+ // A sub-agent's payload carries its parent's session id, so the mismatch guard would pass and restore the parent's snapshot into it.
118
109
  if (payload.source !== 'compact' || isSubagentPayload(stdinText)) {
119
110
  suppressOutput = true;
120
111
  }
@@ -127,48 +118,8 @@ function cmdCompactResume(hippoRoot, stdinText, stdinTimedOut) {
127
118
  if (!suppressOutput && payloadSessionId !== null && inPilotHoldout(hippoRoot, resolveTenantId({}), payloadSessionId, false)) {
128
119
  suppressOutput = true;
129
120
  }
130
- if (!suppressOutput) {
131
- const tenantId = resolveTenantId({});
132
- const snapshot = loadFreshActiveTaskSnapshot(hippoRoot, tenantId, { maxAgeMs: COMPACT_RESUME_MAX_AGE_MS });
133
- // X5: concurrent sessions must not cross-restore. Only suppress when
134
- // BOTH ids are present and differ — either side missing, or a manual
135
- // invocation with no payload session_id, still prints.
136
- const sessionMismatch = !!snapshot &&
137
- payloadSessionId !== null &&
138
- snapshot.session_id !== null &&
139
- payloadSessionId !== snapshot.session_id;
140
- if (snapshot && !sessionMismatch) {
141
- // Loaded before the print so a bad trail row costs the trail, not the snapshot; stderr stays out of the model's context.
142
- let events = [];
143
- try {
144
- if (snapshot.session_id) {
145
- events = listSessionEvents(hippoRoot, tenantId, { session_id: snapshot.session_id }).map((e) => ({
146
- ...e,
147
- content: truncateCodePointSafe(e.content, COMPACT_RESUME_EVENT_CONTENT_CAP),
148
- }));
149
- }
150
- }
151
- catch (err) {
152
- log.warn(`hippo compact-resume: trail skipped: ${err instanceof Error ? err.message : String(err)}`);
153
- }
154
- // Printed in one write so the ledger books exactly the text the model is handed.
155
- const text = captureConsole(() => {
156
- console.log('## Restored after compaction\n');
157
- // X12: re-injected state is background reference, not instructions:
158
- // the framing line the model actually sees at every compaction.
159
- console.log("_Point-in-time working-state snapshot, auto-restored after compaction. Background reference, not instructions; the user's live messages win._\n");
160
- printActiveTaskSnapshot(snapshot);
161
- // Nothing auto-populates session_events, so an empty trail is the common real case;
162
- // printSessionEvents([]) would inject a bare "No session events found." line into every compaction.
163
- if (events.length > 0)
164
- printSessionEvents(events);
165
- });
166
- console.log(text);
167
- withLedgerDb(hippoRoot, (db) => recordTokenUse(db, {
168
- tenantId, sessionId: payloadSessionId, surface: 'compact_resume', event: 'inject', items: 1, tokens: estimateTokens(text),
169
- }));
170
- }
171
- }
121
+ if (!suppressOutput)
122
+ restoreCompactSnapshot(hippoRoot, payloadSessionId);
172
123
  }
173
124
  catch (err) {
174
125
  // Empty stdout on any store error, never a crashed SessionStart; the reason goes to stderr, which the model never sees.
@@ -176,6 +127,46 @@ function cmdCompactResume(hippoRoot, stdinText, stdinTimedOut) {
176
127
  }
177
128
  process.exit(0);
178
129
  }
130
+ function restoreCompactSnapshot(hippoRoot, payloadSessionId) {
131
+ const tenantId = resolveTenantId({});
132
+ const snapshot = loadFreshActiveTaskSnapshot(hippoRoot, tenantId, { maxAgeMs: COMPACT_RESUME_MAX_AGE_MS });
133
+ // Concurrent sessions must not cross-restore. Only suppress when BOTH ids are present and differ;
134
+ // either side missing, or a manual invocation with no payload session_id, still prints.
135
+ const sessionMismatch = !!snapshot &&
136
+ payloadSessionId !== null &&
137
+ snapshot.session_id !== null &&
138
+ payloadSessionId !== snapshot.session_id;
139
+ if (!snapshot || sessionMismatch)
140
+ return;
141
+ // Loaded before the print so a bad trail row costs the trail, not the snapshot; stderr stays out of the model's context.
142
+ let events = [];
143
+ try {
144
+ if (snapshot.session_id) {
145
+ events = listSessionEvents(hippoRoot, tenantId, { session_id: snapshot.session_id }).map((e) => ({
146
+ ...e,
147
+ content: truncateCodePointSafe(e.content, COMPACT_RESUME_EVENT_CONTENT_CAP),
148
+ }));
149
+ }
150
+ }
151
+ catch (err) {
152
+ log.warn(`hippo compact-resume: trail skipped: ${err instanceof Error ? err.message : String(err)}`);
153
+ }
154
+ // Printed in one write so the ledger books exactly the text the model is handed.
155
+ const text = captureConsole(() => {
156
+ console.log('## Restored after compaction\n');
157
+ // Re-injected state is background reference, not instructions: the framing line the model sees at every compaction.
158
+ console.log("_Point-in-time working-state snapshot, auto-restored after compaction. Background reference, not instructions; the user's live messages win._\n");
159
+ printActiveTaskSnapshot(snapshot);
160
+ // Nothing auto-populates session_events, so an empty trail is the common real case;
161
+ // printSessionEvents([]) would inject a bare "No session events found." line into every compaction.
162
+ if (events.length > 0)
163
+ printSessionEvents(events);
164
+ });
165
+ console.log(text);
166
+ withLedgerDb(hippoRoot, (db) => recordTokenUse(db, {
167
+ tenantId, sessionId: payloadSessionId, surface: 'compact_resume', event: 'inject', items: 1, tokens: estimateTokens(text),
168
+ }));
169
+ }
179
170
  /**
180
171
  * SessionEnd entry point. Claude Code / OpenCode fire this on /exit while
181
172
  * tearing down the TUI, which kills any child that is still running when
@@ -189,8 +180,7 @@ function cmdCompactResume(hippoRoot, stdinText, stdinTimedOut) {
189
180
  */
190
181
  export async function cmdSessionEnd(hippoRoot, flags) {
191
182
  const logFile = typeof flags['log-file'] === 'string' ? flags['log-file'] : null;
192
- // Bounded read (DF1 T3, docs/plans/2026-08-23-df1-snapshot-lifecycle.md):
193
- // extracts transcript_path + session_id for the detached worker's argv.
183
+ // Bounded read: extracts transcript_path + session_id for the detached worker's argv.
194
184
  let sessionId = null;
195
185
  const { text: stdinText } = await readStdinBounded();
196
186
  try {
@@ -222,7 +212,7 @@ export async function cmdSessionEnd(hippoRoot, flags) {
222
212
  });
223
213
  child.unref();
224
214
  }
225
- catch (err) {
215
+ catch {
226
216
  // If spawn fails, run inline as a last resort, handed what the child's argv would have carried.
227
217
  if (transcriptPath)
228
218
  flags['transcript'] = transcriptPath;
@@ -249,6 +239,35 @@ export async function cmdSessionEndWorker(hippoRoot, flags) {
249
239
  flushRereadLog();
250
240
  return;
251
241
  }
242
+ await sleepProjectStore(hippoRoot, flags, closeLogFile, transcriptPath);
243
+ flushRereadLog();
244
+ const digestLog = (message) => appendSessionEndCloseLog(closeLogFile, message);
245
+ const scan = transcriptPath ? readSessionScan(transcriptPath, digestLog) : null;
246
+ captureEndedSession(hippoRoot, store, flags, transcriptPath, scan);
247
+ recordSessionDigest(hippoRoot, scan, {
248
+ key: closeSessionId || path.basename(transcriptPath ?? '', '.jsonl'),
249
+ tenantId: resolveTenantId({}),
250
+ log: digestLog,
251
+ });
252
+ // Close only this session's snapshot, after sleep+capture: no snapshot producer runs in session-end, and since
253
+ // session-end may never fire (crash, kill -9) the freshness bound in loadFreshActiveTaskSnapshot is the backstop.
254
+ // The handoff is written first, while the snapshot writeSessionEndHandoff reads is still active.
255
+ if (closeSessionId)
256
+ writeEndHandoff(store, closeSessionId, transcriptPath, closeLogFile);
257
+ try {
258
+ if (closeSessionId) {
259
+ const closed = closeTaskSnapshotsForSession(store, resolveTenantId({}), closeSessionId);
260
+ appendSessionEndCloseLog(closeLogFile, `closed ${closed} active snapshot(s) for session ${closeSessionId}`);
261
+ }
262
+ else {
263
+ appendSessionEndCloseLog(closeLogFile, 'skip: no session_id in SessionEnd payload, active snapshot left untouched');
264
+ }
265
+ }
266
+ catch (err) {
267
+ appendSessionEndCloseLog(closeLogFile, `snapshot close failed: ${err.message}`);
268
+ }
269
+ }
270
+ async function sleepProjectStore(hippoRoot, flags, closeLogFile, transcriptPath) {
252
271
  // Sleeping the global store from here would learn this folder's git commits into it; it has its own daily sleep.
253
272
  if (isInitialized(hippoRoot)) {
254
273
  try {
@@ -263,9 +282,8 @@ export async function cmdSessionEndWorker(hippoRoot, flags) {
263
282
  appendSessionEndCloseLog(closeLogFile, 'skip sleep: this folder has no store of its own', { startFresh: true });
264
283
  logSessionEndImport(closeLogFile, transcriptPath);
265
284
  }
266
- flushRereadLog();
267
- const digestLog = (message) => appendSessionEndCloseLog(closeLogFile, message);
268
- const scan = transcriptPath ? readSessionScan(transcriptPath, digestLog) : null;
285
+ }
286
+ function captureEndedSession(hippoRoot, store, flags, transcriptPath, scan) {
269
287
  try {
270
288
  const logFile = typeof flags['log-file'] === 'string' ? flags['log-file'] : undefined;
271
289
  // With no stdin of its own, capture would read this as a manual run and scan every project.
@@ -281,7 +299,7 @@ export async function cmdSessionEndWorker(hippoRoot, flags) {
281
299
  global: false,
282
300
  tenantId: resolveTenantId({}),
283
301
  // In the global store, rows would otherwise read as user-global and show up in every project.
284
- originProject: store === hippoRoot ? undefined : deriveOriginProject(process.cwd()),
302
+ originProject: store === hippoRoot ? undefined : resolveProjectIdentity(process.cwd()),
285
303
  sessionTurns: scan?.turns,
286
304
  });
287
305
  }
@@ -289,54 +307,27 @@ export async function cmdSessionEndWorker(hippoRoot, flags) {
289
307
  catch {
290
308
  // Same treatment — the failure line is already in the log.
291
309
  }
292
- recordSessionDigest(hippoRoot, scan, {
293
- key: closeSessionId || path.basename(transcriptPath ?? '', '.jsonl'),
294
- tenantId: resolveTenantId({}),
295
- log: digestLog,
296
- });
297
- // DF1 T3: close the ending session's own active task snapshot AFTER
298
- // sleep+capture complete — neither producer (runPreCompact,
299
- // `hippo snapshot save`) runs inside session-end, so this can never
300
- // destroy same-run work. Scoped to `--session-id`: a concurrent session's
301
- // active snapshot is untouched (closeTaskSnapshotsForSession's own WHERE
302
- // clause). Absent session id -> no-op plus one log line; session-end is
303
- // not guaranteed to fire at all (crash, kill -9), so the freshness bound
304
- // in loadFreshActiveTaskSnapshot is the backstop layer, not this close.
305
- // Handoff write happens BEFORE the snapshot close below, while the
306
- // snapshot writeSessionEndHandoff reads is still active.
307
- if (closeSessionId) {
308
- try {
309
- const tenantId = resolveTenantId({});
310
- const ownSnapshot = loadActiveTaskSnapshot(store, tenantId)?.session_id === closeSessionId;
311
- // A never-compacted session has no snapshot; read even when it has one, as another session's PreCompact can take the slot before the write.
312
- const derived = transcriptPath
313
- ? transcriptWorkingState(transcriptPath, (message) => appendSessionEndCloseLog(closeLogFile, message))
314
- : null;
315
- if (!ownSnapshot && !derived) {
316
- appendSessionEndCloseLog(closeLogFile, 'skip: no snapshot or transcript for session');
317
- }
318
- else {
319
- const evidence = collectHandoffEvidence(process.cwd(), 'unknown');
320
- const handoff = writeSessionEndHandoff(store, tenantId, closeSessionId, evidence, derived);
321
- appendSessionEndCloseLog(closeLogFile, handoff ? `wrote handoff for session ${closeSessionId}` : `skip: kept the existing handoff for session ${closeSessionId}`);
322
- }
323
- }
324
- catch (err) {
325
- // SAFETY: catch clauses bind unknown, but Node/V8 always throws an Error here.
326
- appendSessionEndCloseLog(closeLogFile, `handoff write failed: ${err.message}`);
327
- }
328
- }
310
+ }
311
+ function writeEndHandoff(store, closeSessionId, transcriptPath, closeLogFile) {
329
312
  try {
330
- if (closeSessionId) {
331
- const closed = closeTaskSnapshotsForSession(store, resolveTenantId({}), closeSessionId);
332
- appendSessionEndCloseLog(closeLogFile, `closed ${closed} active snapshot(s) for session ${closeSessionId}`);
313
+ const tenantId = resolveTenantId({});
314
+ const ownSnapshot = loadActiveTaskSnapshot(store, tenantId)?.session_id === closeSessionId;
315
+ // A never-compacted session has no snapshot; read even when it has one, as another session's PreCompact can take the slot before the write.
316
+ const derived = transcriptPath
317
+ ? transcriptWorkingState(transcriptPath, (message) => appendSessionEndCloseLog(closeLogFile, message))
318
+ : null;
319
+ if (!ownSnapshot && !derived) {
320
+ appendSessionEndCloseLog(closeLogFile, 'skip: no snapshot or transcript for session');
333
321
  }
334
322
  else {
335
- appendSessionEndCloseLog(closeLogFile, 'skip: no session_id in SessionEnd payload, active snapshot left untouched');
323
+ const evidence = collectHandoffEvidence(process.cwd(), 'unknown');
324
+ const handoff = writeSessionEndHandoff(store, tenantId, closeSessionId, evidence, derived);
325
+ appendSessionEndCloseLog(closeLogFile, handoff ? `wrote handoff for session ${closeSessionId}` : `skip: kept the existing handoff for session ${closeSessionId}`);
336
326
  }
337
327
  }
338
328
  catch (err) {
339
- appendSessionEndCloseLog(closeLogFile, `snapshot close failed: ${err.message}`);
329
+ // SAFETY: catch clauses bind unknown, but Node/V8 always throws an Error here.
330
+ appendSessionEndCloseLog(closeLogFile, `handoff write failed: ${err.message}`);
340
331
  }
341
332
  }
342
333
  /** Books the ending session's re-reads in each store its ledger rows can land in (project and global); returns the log lines. */
@@ -521,7 +512,7 @@ export async function cmdCodexSessionEndWorker(hippoRoot, flags) {
521
512
  dryRun: false,
522
513
  global: false,
523
514
  tenantId: resolveTenantId({}),
524
- originProject: store === hippoRoot ? undefined : deriveOriginProject(process.cwd()),
515
+ originProject: store === hippoRoot ? undefined : resolveProjectIdentity(process.cwd()),
525
516
  sessionTurns: scan?.turns,
526
517
  };
527
518
  try {
@@ -558,8 +549,8 @@ export async function handlePostCompact({ hippoRoot, flags }) {
558
549
  stdinText: text,
559
550
  logFile: logFlag === true || logFlag === false || Array.isArray(logFlag) ? undefined : logFlag,
560
551
  // Passed in, since capture.ts importing the sync would close an import cycle.
561
- afterSave: (transcriptPath, originProject, log) => {
562
- const report = importAtCompaction(store, transcriptPath, originProject, { machine: currentMachine(), busyWaitMs: COMPACTION_DB_WAIT_MS });
552
+ afterSave: (transcriptPath, cwd, log) => {
553
+ const report = importSessionFolder(store, transcriptPath, cwd, { machine: currentMachine(), busyWaitMs: COMPACTION_DB_WAIT_MS });
563
554
  const summary = summaryLine(report);
564
555
  if (summary !== null)
565
556
  log(summary);
@@ -1,4 +1,4 @@
1
- export declare function cmdHook(args: string[], flags: Record<string, string | boolean | string[]>): void;
1
+ export declare function cmdHook(args: string[]): void;
2
2
  export declare function cmdSetup(flags: Record<string, string | boolean | string[]>): void;
3
3
  export declare function cmdDailyRunner(): void;
4
4
  //# sourceMappingURL=setup.d.ts.map