hippo-memory 1.63.2 → 1.64.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 (275) hide show
  1. package/README.md +10 -4
  2. package/dist/agent-memories/claude-code.js +3 -3
  3. package/dist/ambient-store.d.ts +2 -0
  4. package/dist/ambient-store.js +4 -3
  5. package/dist/api/assemble.d.ts +2 -0
  6. package/dist/api/assemble.js +11 -7
  7. package/dist/api/auth.d.ts +15 -11
  8. package/dist/api/auth.js +116 -22
  9. package/dist/api/context-select.d.ts +5 -3
  10. package/dist/api/context-select.js +6 -11
  11. package/dist/api/context-types.d.ts +2 -1
  12. package/dist/api/context.d.ts +1 -1
  13. package/dist/api/context.js +79 -55
  14. package/dist/api/dormant.d.ts +5 -5
  15. package/dist/api/dormant.js +13 -9
  16. package/dist/api/drill-down.d.ts +2 -0
  17. package/dist/api/drill-down.js +12 -7
  18. package/dist/api/forget.d.ts +1 -1
  19. package/dist/api/forget.js +8 -4
  20. package/dist/api/learn.js +5 -3
  21. package/dist/api/outcome.d.ts +1 -1
  22. package/dist/api/outcome.js +2 -1
  23. package/dist/api/promote.js +8 -5
  24. package/dist/api/recall-types.d.ts +4 -2
  25. package/dist/api/recall.js +51 -36
  26. package/dist/api/remember.d.ts +7 -0
  27. package/dist/api/remember.js +26 -2
  28. package/dist/api/sleep-run.d.ts +28 -0
  29. package/dist/api/sleep-run.js +267 -0
  30. package/dist/api/sleep.d.ts +5 -45
  31. package/dist/api/sleep.js +5 -252
  32. package/dist/api/types.d.ts +5 -0
  33. package/dist/api/types.js +4 -0
  34. package/dist/audit-prune.js +1 -1
  35. package/dist/audit.d.ts +1 -3
  36. package/dist/audit.js +7 -103
  37. package/dist/auth.d.ts +48 -4
  38. package/dist/auth.js +111 -42
  39. package/dist/autolearn.js +2 -2
  40. package/dist/capture/caller-session.d.ts +16 -0
  41. package/dist/capture/caller-session.js +58 -0
  42. package/dist/capture/command.d.ts +19 -1
  43. package/dist/capture/command.js +28 -21
  44. package/dist/capture/compact-resume-caller.d.ts +11 -0
  45. package/dist/capture/compact-resume-caller.js +35 -0
  46. package/dist/capture/compact.d.ts +0 -14
  47. package/dist/capture/compact.js +7 -160
  48. package/dist/capture/compaction-items-caller.d.ts +16 -0
  49. package/dist/capture/compaction-items-caller.js +32 -0
  50. package/dist/capture/extract.d.ts +2 -14
  51. package/dist/capture/extract.js +135 -346
  52. package/dist/capture/failure-caller.d.ts +21 -0
  53. package/dist/capture/failure-caller.js +74 -0
  54. package/dist/capture/failure-reading.d.ts +44 -0
  55. package/dist/capture/failure-reading.js +78 -0
  56. package/dist/capture/pre-compact-caller.d.ts +16 -0
  57. package/dist/capture/pre-compact-caller.js +39 -0
  58. package/dist/capture/session-end-caller.d.ts +21 -0
  59. package/dist/capture/session-end-caller.js +30 -0
  60. package/dist/capture/session-texts.d.ts +14 -0
  61. package/dist/capture/session-texts.js +42 -0
  62. package/dist/capture/transcript.d.ts +6 -1
  63. package/dist/capture/transcript.js +20 -16
  64. package/dist/capture/working-state.d.ts +21 -0
  65. package/dist/capture/working-state.js +154 -0
  66. package/dist/capture-error.d.ts +8 -22
  67. package/dist/capture-error.js +9 -61
  68. package/dist/cli/audit.js +25 -0
  69. package/dist/cli/auth.js +4 -3
  70. package/dist/cli/context.js +13 -158
  71. package/dist/cli/continuity.js +2 -1
  72. package/dist/cli/init.js +5 -3
  73. package/dist/cli/last-sleep.d.ts +6 -0
  74. package/dist/cli/last-sleep.js +68 -0
  75. package/dist/cli/quality-repair-once.d.ts +3 -0
  76. package/dist/cli/quality-repair-once.js +36 -0
  77. package/dist/cli/recall.js +2 -1
  78. package/dist/cli/remember.d.ts +2 -0
  79. package/dist/cli/remember.js +4 -1
  80. package/dist/cli/session-hooks.d.ts +0 -3
  81. package/dist/cli/session-hooks.js +12 -49
  82. package/dist/cli/setup.js +10 -2
  83. package/dist/cli/shared.d.ts +7 -14
  84. package/dist/cli/shared.js +19 -78
  85. package/dist/cli/sleep.js +6 -3
  86. package/dist/cli/usage.js +18 -9
  87. package/dist/cli.d.ts +1 -1
  88. package/dist/cli.js +3 -2
  89. package/dist/client.js +1 -1
  90. package/dist/compaction-record.d.ts +19 -1
  91. package/dist/compaction-record.js +61 -132
  92. package/dist/compaction-spool.d.ts +45 -0
  93. package/dist/compaction-spool.js +454 -0
  94. package/dist/config.d.ts +8 -0
  95. package/dist/config.js +49 -0
  96. package/dist/connectors/github/deletion.d.ts +1 -1
  97. package/dist/connectors/github/deletion.js +5 -17
  98. package/dist/consolidate/conflicts.js +4 -0
  99. package/dist/consolidate/flush-units.d.ts +9 -0
  100. package/dist/consolidate/flush-units.js +63 -0
  101. package/dist/consolidate/llm-passes.js +5 -4
  102. package/dist/consolidate/merge.js +5 -1
  103. package/dist/consolidate/run.d.ts +2 -1
  104. package/dist/consolidate/sleep.d.ts +2 -0
  105. package/dist/consolidate/sleep.js +82 -15
  106. package/dist/context-render.d.ts +14 -0
  107. package/dist/context-render.js +28 -0
  108. package/dist/dag.js +6 -0
  109. package/dist/dashboard-actions.js +3 -2
  110. package/dist/dashboard.js +4 -3
  111. package/dist/db/backup.d.ts +6 -0
  112. package/dist/db/backup.js +29 -0
  113. package/dist/db/busy.d.ts +9 -0
  114. package/dist/db/busy.js +35 -0
  115. package/dist/db/continuity.d.ts +1 -0
  116. package/dist/db/continuity.js +31 -4
  117. package/dist/db/meta.d.ts +2 -0
  118. package/dist/db/meta.js +7 -0
  119. package/dist/db/migrate.js +2 -3
  120. package/dist/db/migrations/index.d.ts +1 -1
  121. package/dist/db/migrations/index.js +5 -1
  122. package/dist/db/migrations/v39.js +2 -7
  123. package/dist/db/migrations/v53.d.ts +3 -0
  124. package/dist/db/migrations/v53.js +16 -0
  125. package/dist/db/migrations/v54.d.ts +3 -0
  126. package/dist/db/migrations/v54.js +25 -0
  127. package/dist/db/open.d.ts +11 -0
  128. package/dist/db/open.js +27 -0
  129. package/dist/db/tables.d.ts +2 -0
  130. package/dist/db/tables.js +6 -2
  131. package/dist/db.d.ts +2 -2
  132. package/dist/db.js +2 -2
  133. package/dist/doctor.d.ts +1 -1
  134. package/dist/doctor.js +31 -15
  135. package/dist/dormant.d.ts +16 -20
  136. package/dist/dormant.js +24 -30
  137. package/dist/entry/json-hooks.d.ts +3 -0
  138. package/dist/entry/json-hooks.js +4 -0
  139. package/dist/entry/project-identity.d.ts +2 -0
  140. package/dist/entry/project-identity.js +3 -0
  141. package/dist/entry/session-text.d.ts +7 -0
  142. package/dist/entry/session-text.js +8 -0
  143. package/dist/extract.js +6 -0
  144. package/dist/failure-log.d.ts +10 -2
  145. package/dist/failure-log.js +19 -2
  146. package/dist/gated-write.js +1 -1
  147. package/dist/graph/delta.d.ts +44 -0
  148. package/dist/graph/delta.js +109 -0
  149. package/dist/graph/types.d.ts +5 -0
  150. package/dist/graph/write.d.ts +20 -20
  151. package/dist/graph/write.js +103 -36
  152. package/dist/graph-extract.d.ts +43 -9
  153. package/dist/graph-extract.js +140 -157
  154. package/dist/graph-recall.d.ts +1 -0
  155. package/dist/graph-recall.js +2 -2
  156. package/dist/graph-view.d.ts +1 -0
  157. package/dist/graph-view.js +25 -2
  158. package/dist/half-life-migration.js +5 -1
  159. package/dist/handoff-evidence.d.ts +5 -0
  160. package/dist/handoff-evidence.js +30 -0
  161. package/dist/home-path.d.ts +5 -0
  162. package/dist/home-path.js +24 -0
  163. package/dist/hooks/json-hooks.d.ts +13 -2
  164. package/dist/hooks/json-hooks.js +246 -117
  165. package/dist/hooks/opencode.js +1 -1
  166. package/dist/hooks/shared.d.ts +2 -0
  167. package/dist/hooks/shared.js +6 -1
  168. package/dist/http-util.d.ts +16 -7
  169. package/dist/http-util.js +65 -20
  170. package/dist/importers/vault.js +2 -0
  171. package/dist/index.d.ts +2 -1
  172. package/dist/index.js +1 -0
  173. package/dist/json.d.ts +2 -0
  174. package/dist/json.js +7 -0
  175. package/dist/ledger-db.d.ts +10 -0
  176. package/dist/ledger-db.js +42 -0
  177. package/dist/mcp/admin-tools.d.ts +2 -2
  178. package/dist/mcp/admin-tools.js +47 -5
  179. package/dist/mcp/memory-tools.js +7 -2
  180. package/dist/mcp/protocol.d.ts +6 -0
  181. package/dist/mcp/protocol.js +4 -2
  182. package/dist/mcp/recall-tools.js +19 -6
  183. package/dist/mcp/request.js +4 -0
  184. package/dist/mcp/session-state.d.ts +4 -0
  185. package/dist/mcp/session-state.js +15 -6
  186. package/dist/mcp/shared-gate.d.ts +4 -0
  187. package/dist/mcp/shared-gate.js +22 -0
  188. package/dist/mcp/tools.js +1 -0
  189. package/dist/memory-quality.d.ts +35 -0
  190. package/dist/memory-quality.js +191 -0
  191. package/dist/memory.d.ts +4 -4
  192. package/dist/physics-state.js +1 -1
  193. package/dist/pilot-arm.d.ts +14 -3
  194. package/dist/pilot-arm.js +32 -10
  195. package/dist/postinstall.js +2 -2
  196. package/dist/project-briefs.js +4 -4
  197. package/dist/project-identity.d.ts +14 -10
  198. package/dist/project-identity.js +29 -11
  199. package/dist/project-merge.d.ts +0 -2
  200. package/dist/project-merge.js +9 -13
  201. package/dist/prompt-hook.d.ts +65 -0
  202. package/dist/prompt-hook.js +213 -0
  203. package/dist/prompt-recall.js +1 -1
  204. package/dist/quality-repair.d.ts +27 -0
  205. package/dist/quality-repair.js +220 -0
  206. package/dist/rate-limit.d.ts +1 -0
  207. package/dist/rate-limit.js +5 -3
  208. package/dist/raw-archive.d.ts +1 -1
  209. package/dist/raw-archive.js +6 -16
  210. package/dist/recall-scope.d.ts +28 -5
  211. package/dist/recall-scope.js +52 -7
  212. package/dist/recall-trace.js +1 -1
  213. package/dist/reject-flow.d.ts +2 -0
  214. package/dist/reject-flow.js +20 -6
  215. package/dist/secret-detect.js +7 -8
  216. package/dist/server/auth.d.ts +8 -6
  217. package/dist/server/auth.js +85 -38
  218. package/dist/server/client-ip.d.ts +2 -0
  219. package/dist/server/client-ip.js +16 -1
  220. package/dist/server/mcp-http.d.ts +9 -3
  221. package/dist/server/mcp-http.js +56 -13
  222. package/dist/server/request.d.ts +1 -1
  223. package/dist/server/request.js +5 -5
  224. package/dist/server/routes/admin.js +12 -6
  225. package/dist/server/routes/memories.js +9 -1
  226. package/dist/server/routes/recall.js +14 -2
  227. package/dist/server/types.d.ts +42 -5
  228. package/dist/server/validation.d.ts +5 -2
  229. package/dist/server/validation.js +19 -2
  230. package/dist/server.d.ts +21 -4
  231. package/dist/server.js +207 -49
  232. package/dist/session-digest.d.ts +0 -1
  233. package/dist/session-digest.js +3 -12
  234. package/dist/session-owners.d.ts +7 -0
  235. package/dist/session-owners.js +54 -0
  236. package/dist/share-scrub.d.ts +3 -0
  237. package/dist/share-scrub.js +8 -0
  238. package/dist/shared.d.ts +1 -0
  239. package/dist/shared.js +26 -18
  240. package/dist/store/candidates.d.ts +14 -3
  241. package/dist/store/candidates.js +48 -26
  242. package/dist/store/conflicts.d.ts +15 -0
  243. package/dist/store/conflicts.js +68 -44
  244. package/dist/store/delete-and-batch.d.ts +25 -1
  245. package/dist/store/delete-and-batch.js +121 -69
  246. package/dist/store/entry-reads.d.ts +5 -6
  247. package/dist/store/entry-reads.js +24 -12
  248. package/dist/store/entry-row.d.ts +7 -10
  249. package/dist/store/entry-row.js +40 -18
  250. package/dist/store/entry-writes.d.ts +3 -3
  251. package/dist/store/entry-writes.js +9 -23
  252. package/dist/store/handoffs.d.ts +9 -6
  253. package/dist/store/handoffs.js +72 -59
  254. package/dist/store/index-and-stats.js +2 -2
  255. package/dist/store/open.d.ts +3 -1
  256. package/dist/store/open.js +3 -3
  257. package/dist/store/search-rows.d.ts +8 -5
  258. package/dist/store/search-rows.js +15 -18
  259. package/dist/store/sessions.d.ts +25 -6
  260. package/dist/store/sessions.js +61 -19
  261. package/dist/store/summaries.d.ts +1 -1
  262. package/dist/store/summaries.js +3 -18
  263. package/dist/store/tenant-lookup.d.ts +7 -3
  264. package/dist/store/tenant-lookup.js +9 -9
  265. package/dist/store-port.d.ts +13 -0
  266. package/dist/store-port.js +20 -0
  267. package/dist/version.d.ts +5 -1
  268. package/dist/version.js +5 -1
  269. package/dist/working-memory.js +1 -1
  270. package/dist/write-budget.d.ts +15 -0
  271. package/dist/write-budget.js +15 -0
  272. package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
  273. package/extensions/openclaw-plugin/package.json +1 -1
  274. package/openclaw.plugin.json +1 -1
  275. package/package.json +8 -4
@@ -5,103 +5,13 @@ import { saveActiveTaskSnapshot, loadActiveTaskSnapshot } from '../store/session
5
5
  import { PRE_COMPACT_INSTRUCTION, parsePostCompactPayload, postCompactLine, recordCompactionStart, recordSnapshotSaved, saveCompaction, replayCompactionsAt, COMPACTION_DB_WAIT_MS, } from '../compaction-record.js';
6
6
  import { resolveTenantId } from '../tenant.js';
7
7
  import { defaultPreCompactLogPath } from '../hooks/shared.js';
8
- import { maskEmails, redactSecretsStrict } from '../secret-detect.js';
9
- import { isObjectLike, isStringValue, readClaudeCodePreCompact } from '../capture-contract.js';
8
+ import { readClaudeCodePreCompact } from '../capture-contract.js';
10
9
  import { errorMessage } from '../log.js';
11
- import { PRE_COMPACT_TAIL_BYTES, readTranscriptTail, truncateCodePointSafe } from '../transcript-tail.js';
12
- import { humanUserText, summariseTranscript, resolveLastSessionTranscript } from './transcript.js';
10
+ import { resolveLastSessionTranscript } from './transcript.js';
11
+ import { mergeWorkingState, transcriptWorkingState } from './working-state.js';
13
12
  // ---------------------------------------------------------------------------
14
13
  // `hippo pre-compact` — PreCompact hook producer
15
14
  // ---------------------------------------------------------------------------
16
- export const PRE_COMPACT_TASK_CAP = 200;
17
- export const PRE_COMPACT_SUMMARY_CAP = 2000;
18
- export const PRE_COMPACT_NEXT_STEP_CAP = 500;
19
- /**
20
- * Cap from the RECENT end: summariseTranscript emits user turns oldest-to-
21
- * newest with assistant responses after them, so a head-first cap keeps
22
- * stale context and drops exactly the newest working state this feature
23
- * exists to preserve. Keep the LAST maxChars instead, aligned forward to a
24
- * nearby line start, with a trim marker.
25
- */
26
- export function truncateKeepNewest(text, maxChars) {
27
- if (text.length <= maxChars)
28
- return text;
29
- let start = text.length - maxChars;
30
- const code = text.charCodeAt(start);
31
- if (code >= 0xdc00 && code <= 0xdfff)
32
- start += 1; // never start on a low surrogate
33
- const nl = text.indexOf('\n', start);
34
- if (nl !== -1 && nl + 1 < text.length && nl - start < 200)
35
- start = nl + 1;
36
- return '[...earlier turns trimmed]\n' + text.slice(start);
37
- }
38
- /** Most recent plain-text user message in a JSONL tail. Claude Code transcript shape only (PreCompact is claude-code-only). */
39
- function lastPlainUserMessage(jsonl) {
40
- const lines = jsonl.split('\n').filter((l) => l.trim());
41
- for (let i = lines.length - 1; i >= 0; i--) {
42
- let entry;
43
- try {
44
- entry = JSON.parse(lines[i]);
45
- }
46
- catch {
47
- continue; // the tail read can start mid-line; skip the torn fragment
48
- }
49
- if (!isObjectLike(entry))
50
- continue;
51
- if (!('type' in entry) || entry.type !== 'user')
52
- continue;
53
- // Meta/sidechain lines carry type:'user' but are not the human: after a
54
- // FIRST compaction the transcript holds the compact summary as an isMeta
55
- // user line, and sub-agent turns are isSidechain — deriving "task" from
56
- // either yields junk on every later compaction.
57
- if (('isMeta' in entry && entry.isMeta === true) || ('isSidechain' in entry && entry.isSidechain === true))
58
- continue;
59
- const message = 'message' in entry && isObjectLike(entry.message) ? entry.message : undefined;
60
- if (!message)
61
- continue;
62
- const text = humanUserText(entry, message);
63
- if (text)
64
- return text;
65
- }
66
- return '';
67
- }
68
- /** Last assistant text block in a JSONL tail (skips thinking + tool_use, same as summariseTranscript). */
69
- function lastAssistantTextBlock(jsonl) {
70
- const lines = jsonl.split('\n').filter((l) => l.trim());
71
- for (let i = lines.length - 1; i >= 0; i--) {
72
- let entry;
73
- try {
74
- entry = JSON.parse(lines[i]);
75
- }
76
- catch {
77
- continue; // the tail read can start mid-line; skip the torn fragment
78
- }
79
- if (!isObjectLike(entry))
80
- continue;
81
- if (!('type' in entry) || entry.type !== 'assistant')
82
- continue;
83
- // Same meta/sidechain guard as lastPlainUserMessage: sub-agent turns
84
- // (isSidechain) are not this session's next step.
85
- if (('isMeta' in entry && entry.isMeta === true) || ('isSidechain' in entry && entry.isSidechain === true))
86
- continue;
87
- const message = 'message' in entry && isObjectLike(entry.message) ? entry.message : undefined;
88
- if (!message)
89
- continue;
90
- const content = 'content' in message ? message.content : undefined;
91
- if (!Array.isArray(content))
92
- continue;
93
- for (let j = content.length - 1; j >= 0; j--) {
94
- const block = content[j];
95
- if (isObjectLike(block)) {
96
- const blockText = 'type' in block && block.type === 'text' && 'text' in block ? block.text : undefined;
97
- if (isStringValue(blockText) && blockText.trim()) {
98
- return blockText.trim();
99
- }
100
- }
101
- }
102
- }
103
- return '';
104
- }
105
15
  // Diagnostic-only log; a long-lived install must not grow it unbounded.
106
16
  const PRE_COMPACT_LOG_MAX_BYTES = 256 * 1024;
107
17
  /**
@@ -148,45 +58,6 @@ function printPreCompactInstruction(logFile) {
148
58
  appendPreCompactLog(logFile, `instruction not printed: ${errorMessage(err)}`);
149
59
  }
150
60
  }
151
- /** A session's task, summary and next step from its transcript tail, secrets scrubbed and capped, '' where none; null with a logged reason when nothing is derivable. */
152
- export function transcriptWorkingState(transcriptPath, log) {
153
- let tail = '';
154
- let rawTask = '';
155
- let rawNextStep = '';
156
- try {
157
- // Compaction fires when a big tool_result lands, so the last human and assistant turns can sit
158
- // megabytes back: grow the window a bounded number of times until both turns are in it.
159
- const size = fs.statSync(transcriptPath).size;
160
- for (const cap of [PRE_COMPACT_TAIL_BYTES, PRE_COMPACT_TAIL_BYTES * 4, PRE_COMPACT_TAIL_BYTES * 16]) {
161
- if (cap > PRE_COMPACT_TAIL_BYTES)
162
- log(`tail window grown to ${cap} bytes (last ${rawTask ? 'assistant' : 'user'} turn is further back)`);
163
- tail = readTranscriptTail(transcriptPath, cap);
164
- rawTask = lastPlainUserMessage(tail);
165
- rawNextStep = lastAssistantTextBlock(tail);
166
- if ((rawTask && rawNextStep) || size <= cap)
167
- break;
168
- }
169
- }
170
- catch (err) {
171
- log(`skip: could not read transcript tail: ${errorMessage(err)}`);
172
- return null;
173
- }
174
- const rawSummary = summariseTranscript(tail);
175
- if (!rawTask.trim() && !rawSummary.trim() && !rawNextStep.trim()) {
176
- log('skip: empty summary');
177
- return null;
178
- }
179
- // These fields skip the capture content gate and reach a prompt, so the strict scrub runs. The caps protect the
180
- // re-injection token budget and never split a surrogate pair; `hippo snapshot save` stays uncapped.
181
- const task = maskEmails(redactSecretsStrict(rawTask));
182
- const summary = maskEmails(redactSecretsStrict(rawSummary));
183
- const nextStep = maskEmails(redactSecretsStrict(rawNextStep));
184
- return {
185
- task: task.trim() ? truncateCodePointSafe(task, PRE_COMPACT_TASK_CAP) : '',
186
- summary: summary.trim() ? truncateKeepNewest(summary, PRE_COMPACT_SUMMARY_CAP) : '',
187
- next_step: nextStep.trim() ? truncateCodePointSafe(nextStep, PRE_COMPACT_NEXT_STEP_CAP) : '',
188
- };
189
- }
190
61
  /** Runs the PreCompact producer: records the compaction, asks the summariser for memories, saves a working-state snapshot. Never extracts memories itself; SessionEnd capture owns that. */
191
62
  function runPreCompact(hippoRoot, stdinText, stdinTimedOut, logFile) {
192
63
  // The PreCompact hook fires in every Claude Code project, including
@@ -242,11 +113,6 @@ function resolvePreCompactTranscript(payloadTranscriptPath, stdinText, logFile)
242
113
  }
243
114
  function saveDerivedSnapshot(hippoRoot, logFile, sessionId, recordId, derived) {
244
115
  const tenantId = resolveTenantId({});
245
- // Per-field merge: a tool-heavy tail whose only user turns are
246
- // tool_result arrays derives an empty task even though the summary is
247
- // non-empty. Loading the existing snapshot first lets each field fall
248
- // back independently instead of the whole write clobbering a
249
- // user-authored field with blank text.
250
116
  let existing = null;
251
117
  try {
252
118
  existing = loadActiveTaskSnapshot(hippoRoot, tenantId);
@@ -254,36 +120,17 @@ function saveDerivedSnapshot(hippoRoot, logFile, sessionId, recordId, derived) {
254
120
  catch {
255
121
  // No existing snapshot to merge against — proceed with derived-only.
256
122
  }
257
- // Field fallback must never move content across
258
- // sessions — session A's task carried into a snapshot saved under session
259
- // B's id would pass compact-resume's session gate wearing the wrong
260
- // badge. Fall back only when the existing snapshot has no session, this
261
- // payload has none, or they match.
262
- const fallback = existing !== null &&
263
- (existing.session_id === null || sessionId === null || existing.session_id === sessionId)
264
- ? existing
265
- : null;
266
123
  // Carried-over fields are not re-capped, as `hippo snapshot save` stays uncapped; saveActiveTaskSnapshot scrubs every field.
267
- const task = derived.task || (fallback?.task ?? '');
268
- const summary = derived.summary || (fallback?.summary ?? '');
269
- const nextStep = derived.next_step || (fallback?.next_step ?? '');
270
- // All-empty fields (cross-session tail with nothing derivable) skip the
271
- // write so a foreign session's junk never displaces the owning snapshot.
272
- if (!task && !summary && !nextStep) {
124
+ const merged = mergeWorkingState(derived, existing, sessionId);
125
+ if (merged === null) {
273
126
  appendPreCompactLog(logFile, 'skip: no snapshot content for this session (nothing derivable; fallback blocked or empty)');
274
127
  }
275
128
  else {
276
129
  try {
277
- saveActiveTaskSnapshot(hippoRoot, tenantId, {
278
- task,
279
- summary,
280
- next_step: nextStep,
281
- source: 'pre-compact',
282
- session_id: sessionId,
283
- });
130
+ saveActiveTaskSnapshot(hippoRoot, tenantId, { ...merged, source: 'pre-compact', session_id: sessionId });
284
131
  appendPreCompactLog(logFile, 'snapshot saved');
285
132
  if (recordId !== null)
286
- recordSnapshotSaved(hippoRoot, recordId, (message) => appendPreCompactLog(logFile, message));
133
+ recordSnapshotSaved(hippoRoot, tenantId, recordId, (message) => appendPreCompactLog(logFile, message));
287
134
  }
288
135
  catch (err) {
289
136
  appendPreCompactLog(logFile, `snapshot save failed: ${errorMessage(err)}`);
@@ -0,0 +1,16 @@
1
+ import type { Context } from '../api/types.js';
2
+ import type { CallerProject } from '../prompt-hook.js';
3
+ export interface CallerItemsRequest {
4
+ readonly sessionId: string;
5
+ readonly project: CallerProject;
6
+ readonly trigger: string | null;
7
+ /** The "Memories for hippo" list the caller parsed from its compact summary. */
8
+ readonly items: readonly string[];
9
+ readonly requestId: string;
10
+ }
11
+ export interface CallerItemsResult {
12
+ readonly written: number;
13
+ }
14
+ /** Busy throws, for the server's 503: the retry carries the same request id, so nothing is spooled or deferred here. */
15
+ export declare function saveCompactionItemsForCaller(ctx: Context, req: CallerItemsRequest): CallerItemsResult;
16
+ //# sourceMappingURL=compaction-items-caller.d.ts.map
@@ -0,0 +1,32 @@
1
+ // PostCompact for a caller on another machine: the items it parsed become rows under its owner and project, and a retried request id writes nothing twice.
2
+ import { BadRequestError } from '../api-errors.js';
3
+ import { COMPACTION_ITEM_MAX_CHARS } from '../compaction-items.js';
4
+ import { compactionByRequest, recordSummary, saveItems, scrubCompactionItems } from '../compaction-record.js';
5
+ import { log } from '../log.js';
6
+ import { assertRequestId, assertTrigger, bindCaller, callerInHoldout, withCallerDb } from './caller-session.js';
7
+ /** Busy throws, for the server's 503: the retry carries the same request id, so nothing is spooled or deferred here. */
8
+ export function saveCompactionItemsForCaller(ctx, req) {
9
+ assertTrigger(req.trigger);
10
+ assertRequestId(req.requestId);
11
+ // Refused before the scrub, as failure text is, so the cut after it only ever takes back a mask's growth.
12
+ if (req.items.some((item) => item.length > COMPACTION_ITEM_MAX_CHARS))
13
+ throw new BadRequestError(`items: each at most ${COMPACTION_ITEM_MAX_CHARS} characters`);
14
+ const key = bindCaller(ctx, req.sessionId, req.project);
15
+ if (callerInHoldout(ctx, req.sessionId))
16
+ return { written: 0 };
17
+ return withCallerDb(ctx, (db) => {
18
+ const earlier = compactionByRequest(db, ctx.tenantId, req.requestId, req.sessionId);
19
+ // Past `summarised` the first try finished, so its count is the answer; a `summarised` one failed at the items and is reused.
20
+ if (earlier !== null && earlier.status !== 'summarised')
21
+ return { written: earlier.itemsWritten };
22
+ const meta = { sessionId: req.sessionId, trigger: req.trigger, cwd: null, transcriptPath: null };
23
+ const text = { summary: '', items: scrubCompactionItems(req.items) };
24
+ const record = earlier ?? recordSummary(db, ctx.hippoRoot, ctx.tenantId, meta, text, new Date(), { originProject: req.project.name, requestId: req.requestId });
25
+ const written = saveItems(db, ctx.hippoRoot, {
26
+ tenantId: ctx.tenantId, recordId: record.id, sessionId: req.sessionId, originProject: record.originProject, cwd: null,
27
+ items: record.items, caller: { actor: key.owner, origins: key.project },
28
+ }, (message) => log.info(`post-compact: ${message}`));
29
+ return { written };
30
+ });
31
+ }
32
+ //# sourceMappingURL=compaction-items-caller.js.map
@@ -1,21 +1,9 @@
1
- /**
2
- * Capture actionable items from conversation text.
3
- *
4
- * Uses heuristic pattern matching (no LLM) to extract:
5
- * - Decisions ("we decided", "let's do", "going with")
6
- * - Specs / requirements (bullet lists after spec/feature/plan headings)
7
- * - Rules / constraints ("never", "always", "the rule is", "must")
8
- * - Errors / gotchas ("error:", "bug:", "gotcha:", "watch out")
9
- * - Preferences ("prefer", "use X instead of Y", "don't use")
10
- */
11
1
  export interface ExtractedItem {
12
2
  content: string;
13
3
  category: string;
14
4
  tags: string[];
15
5
  }
16
- export declare function splitSentences(text: string): string[];
17
- /**
18
- * Main extraction function. Scans text for actionable items using heuristics.
19
- */
6
+ /** Captures complete supported statements, each text parsed alone so a fence or heading in one never reaches the next. */
7
+ export declare function extractFromTexts(texts: readonly string[]): ExtractedItem[];
20
8
  export declare function extractFromText(text: string): ExtractedItem[];
21
9
  //# sourceMappingURL=extract.d.ts.map