hippo-memory 1.63.2 → 1.64.1

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 (280) hide show
  1. package/README.md +11 -5
  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/maintenance.d.ts +2 -1
  76. package/dist/cli/maintenance.js +17 -11
  77. package/dist/cli/quality-repair-once.d.ts +3 -0
  78. package/dist/cli/quality-repair-once.js +36 -0
  79. package/dist/cli/recall.js +2 -1
  80. package/dist/cli/remember.d.ts +2 -0
  81. package/dist/cli/remember.js +4 -1
  82. package/dist/cli/session-hooks.d.ts +0 -3
  83. package/dist/cli/session-hooks.js +12 -49
  84. package/dist/cli/setup.js +10 -2
  85. package/dist/cli/shared.d.ts +7 -14
  86. package/dist/cli/shared.js +19 -78
  87. package/dist/cli/sleep.js +6 -3
  88. package/dist/cli/usage.js +18 -9
  89. package/dist/cli.d.ts +1 -1
  90. package/dist/cli.js +3 -2
  91. package/dist/client.js +1 -1
  92. package/dist/compaction-record.d.ts +19 -1
  93. package/dist/compaction-record.js +61 -132
  94. package/dist/compaction-spool.d.ts +45 -0
  95. package/dist/compaction-spool.js +454 -0
  96. package/dist/config.d.ts +8 -0
  97. package/dist/config.js +49 -0
  98. package/dist/connectors/github/deletion.d.ts +1 -1
  99. package/dist/connectors/github/deletion.js +5 -17
  100. package/dist/consolidate/conflicts.js +4 -0
  101. package/dist/consolidate/flush-units.d.ts +9 -0
  102. package/dist/consolidate/flush-units.js +63 -0
  103. package/dist/consolidate/llm-passes.js +5 -4
  104. package/dist/consolidate/merge.js +5 -1
  105. package/dist/consolidate/run.d.ts +2 -1
  106. package/dist/consolidate/sleep.d.ts +2 -0
  107. package/dist/consolidate/sleep.js +82 -15
  108. package/dist/context-render.d.ts +14 -0
  109. package/dist/context-render.js +28 -0
  110. package/dist/dag.js +6 -0
  111. package/dist/dashboard-actions.js +3 -2
  112. package/dist/dashboard.js +4 -3
  113. package/dist/db/backup.d.ts +6 -0
  114. package/dist/db/backup.js +29 -0
  115. package/dist/db/busy.d.ts +9 -0
  116. package/dist/db/busy.js +35 -0
  117. package/dist/db/continuity.d.ts +1 -0
  118. package/dist/db/continuity.js +31 -4
  119. package/dist/db/meta.d.ts +2 -0
  120. package/dist/db/meta.js +7 -0
  121. package/dist/db/migrate.js +2 -3
  122. package/dist/db/migrations/index.d.ts +1 -1
  123. package/dist/db/migrations/index.js +5 -1
  124. package/dist/db/migrations/v39.js +2 -7
  125. package/dist/db/migrations/v53.d.ts +3 -0
  126. package/dist/db/migrations/v53.js +16 -0
  127. package/dist/db/migrations/v54.d.ts +3 -0
  128. package/dist/db/migrations/v54.js +25 -0
  129. package/dist/db/open.d.ts +11 -0
  130. package/dist/db/open.js +27 -0
  131. package/dist/db/tables.d.ts +2 -0
  132. package/dist/db/tables.js +6 -2
  133. package/dist/db.d.ts +2 -2
  134. package/dist/db.js +2 -2
  135. package/dist/doctor.d.ts +1 -1
  136. package/dist/doctor.js +31 -15
  137. package/dist/dormant.d.ts +16 -20
  138. package/dist/dormant.js +24 -30
  139. package/dist/embedding-provider.js +4 -3
  140. package/dist/entry/json-hooks.d.ts +3 -0
  141. package/dist/entry/json-hooks.js +4 -0
  142. package/dist/entry/project-identity.d.ts +2 -0
  143. package/dist/entry/project-identity.js +3 -0
  144. package/dist/entry/session-text.d.ts +7 -0
  145. package/dist/entry/session-text.js +8 -0
  146. package/dist/extract.js +6 -0
  147. package/dist/failure-log.d.ts +10 -2
  148. package/dist/failure-log.js +19 -2
  149. package/dist/gated-write.js +1 -1
  150. package/dist/graph/delta.d.ts +44 -0
  151. package/dist/graph/delta.js +109 -0
  152. package/dist/graph/types.d.ts +5 -0
  153. package/dist/graph/write.d.ts +20 -20
  154. package/dist/graph/write.js +103 -36
  155. package/dist/graph-extract.d.ts +43 -9
  156. package/dist/graph-extract.js +140 -157
  157. package/dist/graph-recall.d.ts +1 -0
  158. package/dist/graph-recall.js +2 -2
  159. package/dist/graph-view.d.ts +1 -0
  160. package/dist/graph-view.js +25 -2
  161. package/dist/half-life-migration.js +5 -1
  162. package/dist/handoff-evidence.d.ts +5 -0
  163. package/dist/handoff-evidence.js +30 -0
  164. package/dist/home-path.d.ts +5 -0
  165. package/dist/home-path.js +24 -0
  166. package/dist/hooks/json-hooks.d.ts +13 -2
  167. package/dist/hooks/json-hooks.js +246 -117
  168. package/dist/hooks/opencode.js +1 -1
  169. package/dist/hooks/shared.d.ts +2 -0
  170. package/dist/hooks/shared.js +6 -1
  171. package/dist/http-util.d.ts +16 -7
  172. package/dist/http-util.js +65 -20
  173. package/dist/importers/vault.js +2 -0
  174. package/dist/index.d.ts +2 -1
  175. package/dist/index.js +1 -0
  176. package/dist/json.d.ts +2 -0
  177. package/dist/json.js +7 -0
  178. package/dist/ledger-db.d.ts +10 -0
  179. package/dist/ledger-db.js +42 -0
  180. package/dist/local-embedding.d.ts +2 -0
  181. package/dist/local-embedding.js +20 -4
  182. package/dist/mcp/admin-tools.d.ts +2 -2
  183. package/dist/mcp/admin-tools.js +47 -5
  184. package/dist/mcp/memory-tools.js +7 -2
  185. package/dist/mcp/protocol.d.ts +6 -0
  186. package/dist/mcp/protocol.js +4 -2
  187. package/dist/mcp/recall-tools.js +19 -6
  188. package/dist/mcp/request.js +4 -0
  189. package/dist/mcp/session-state.d.ts +4 -0
  190. package/dist/mcp/session-state.js +15 -6
  191. package/dist/mcp/shared-gate.d.ts +4 -0
  192. package/dist/mcp/shared-gate.js +22 -0
  193. package/dist/mcp/tools.js +1 -0
  194. package/dist/memory-quality.d.ts +35 -0
  195. package/dist/memory-quality.js +191 -0
  196. package/dist/memory.d.ts +4 -4
  197. package/dist/physics-state.js +1 -1
  198. package/dist/pilot-arm.d.ts +14 -3
  199. package/dist/pilot-arm.js +32 -10
  200. package/dist/postinstall.js +2 -2
  201. package/dist/project-briefs.js +4 -4
  202. package/dist/project-identity.d.ts +14 -10
  203. package/dist/project-identity.js +29 -11
  204. package/dist/project-merge.d.ts +0 -2
  205. package/dist/project-merge.js +9 -13
  206. package/dist/prompt-hook.d.ts +65 -0
  207. package/dist/prompt-hook.js +213 -0
  208. package/dist/prompt-recall.js +1 -1
  209. package/dist/quality-repair.d.ts +27 -0
  210. package/dist/quality-repair.js +220 -0
  211. package/dist/rate-limit.d.ts +1 -0
  212. package/dist/rate-limit.js +5 -3
  213. package/dist/raw-archive.d.ts +1 -1
  214. package/dist/raw-archive.js +6 -16
  215. package/dist/recall-scope.d.ts +28 -5
  216. package/dist/recall-scope.js +52 -7
  217. package/dist/recall-trace.js +1 -1
  218. package/dist/reject-flow.d.ts +2 -0
  219. package/dist/reject-flow.js +20 -6
  220. package/dist/secret-detect.js +7 -8
  221. package/dist/server/auth.d.ts +8 -6
  222. package/dist/server/auth.js +85 -38
  223. package/dist/server/client-ip.d.ts +2 -0
  224. package/dist/server/client-ip.js +16 -1
  225. package/dist/server/mcp-http.d.ts +9 -3
  226. package/dist/server/mcp-http.js +56 -13
  227. package/dist/server/request.d.ts +1 -1
  228. package/dist/server/request.js +5 -5
  229. package/dist/server/routes/admin.js +12 -6
  230. package/dist/server/routes/memories.js +9 -1
  231. package/dist/server/routes/recall.js +14 -2
  232. package/dist/server/types.d.ts +42 -5
  233. package/dist/server/validation.d.ts +5 -2
  234. package/dist/server/validation.js +19 -2
  235. package/dist/server.d.ts +21 -4
  236. package/dist/server.js +207 -49
  237. package/dist/session-digest.d.ts +0 -1
  238. package/dist/session-digest.js +3 -12
  239. package/dist/session-owners.d.ts +7 -0
  240. package/dist/session-owners.js +54 -0
  241. package/dist/share-scrub.d.ts +3 -0
  242. package/dist/share-scrub.js +8 -0
  243. package/dist/shared.d.ts +1 -0
  244. package/dist/shared.js +26 -18
  245. package/dist/store/candidates.d.ts +14 -3
  246. package/dist/store/candidates.js +48 -26
  247. package/dist/store/conflicts.d.ts +15 -0
  248. package/dist/store/conflicts.js +68 -44
  249. package/dist/store/delete-and-batch.d.ts +25 -1
  250. package/dist/store/delete-and-batch.js +121 -69
  251. package/dist/store/entry-reads.d.ts +5 -6
  252. package/dist/store/entry-reads.js +24 -12
  253. package/dist/store/entry-row.d.ts +7 -10
  254. package/dist/store/entry-row.js +40 -18
  255. package/dist/store/entry-writes.d.ts +3 -3
  256. package/dist/store/entry-writes.js +9 -23
  257. package/dist/store/handoffs.d.ts +9 -6
  258. package/dist/store/handoffs.js +72 -59
  259. package/dist/store/index-and-stats.js +2 -2
  260. package/dist/store/open.d.ts +3 -1
  261. package/dist/store/open.js +3 -3
  262. package/dist/store/search-rows.d.ts +8 -5
  263. package/dist/store/search-rows.js +15 -18
  264. package/dist/store/sessions.d.ts +25 -6
  265. package/dist/store/sessions.js +61 -19
  266. package/dist/store/summaries.d.ts +1 -1
  267. package/dist/store/summaries.js +3 -18
  268. package/dist/store/tenant-lookup.d.ts +7 -3
  269. package/dist/store/tenant-lookup.js +9 -9
  270. package/dist/store-port.d.ts +13 -0
  271. package/dist/store-port.js +20 -0
  272. package/dist/version.d.ts +5 -1
  273. package/dist/version.js +5 -1
  274. package/dist/working-memory.js +1 -1
  275. package/dist/write-budget.d.ts +15 -0
  276. package/dist/write-budget.js +15 -0
  277. package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
  278. package/extensions/openclaw-plugin/package.json +1 -1
  279. package/openclaw.plugin.json +1 -1
  280. package/package.json +8 -4
@@ -1,386 +1,175 @@
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
  import { duplicateKey } from '../same-text.js';
12
- import { isContentWorthStoring } from '../audit.js';
13
- // Sentence-level patterns
14
- //
15
- // Each pattern carries TWO capture groups: group 1 is the discriminating keyword (plus its
16
- // trailing separator, verbatim), group 2 the content after it, so a negation like "never" survives
17
- // ("Never use X" must not store as "use X"). `extractFromPatterns` joins group1 + a clause-bounded
18
- // group2 (`boundToClause`), so group 2 may reach {1,500}. The keyword keeps its own group because
19
- // keywords like "error:" end in a colon that is not a prose clause boundary.
20
- // `PREFERENCE_PATTERNS[0]` is the exception: its two groups are the content spans either side of
21
- // "instead of"/"over"/"not"; left as-is.
2
+ import { assessAutomaticMemory } from '../memory-quality.js';
22
3
  const DECISION_PATTERNS = [
23
- /(?:we(?:'ve| have)?|i(?:'ve| have)?|let's)\s+(decid(?:ed|e)\s+(?:to\s+)?)(.{1,500})/i,
24
- /(?:let's|we(?:'ll| will| should)?)\s+((?:go with|do|use|try|build|implement|switch to)\s+)(.{1,500})/i,
25
- /((?:going|went)\s+with\s+)(.{1,500})/i,
26
- /((?:the plan is|plan:)\s+)(.{1,500})/i,
27
- /(decision:\s*)(.{1,500})/i,
4
+ /\b(?:we(?:'ve| have)?|i(?:'ve| have)?|let's)\s+decid(?:ed|e)\b/i,
5
+ /\b(?:let's|we(?:'ll| will| should)?)\s+(?:go with|do|use|try|build|implement|switch to)\b/i,
6
+ /\b(?:going|went)\s+with\b/i,
7
+ /\b(?:the plan is|plan:|decision:)\s/i,
28
8
  ];
29
9
  const RULE_PATTERNS = [
30
- /\b((?:never|always|must(?:\s+not)?|do(?:n't| not)\s+ever)\s+)(.{1,500})/i,
31
- /((?:the rule is|rule:)\s*)(.{1,500})/i,
32
- /((?:important|critical|remember):\s*)(.{1,500})/i,
33
- /((?:make sure|ensure)\s+(?:to\s+)?)(.{1,500})/i,
10
+ /\b(?:never|always|must(?:\s+not)?|do(?:n't| not)\s+ever)\s/i,
11
+ /\b(?:the rule is|rule:|important:|critical:|remember:)\s/i,
12
+ /\b(?:make sure|ensure)\s/i,
34
13
  ];
35
14
  const ERROR_PATTERNS = [
36
- /((?:error|bug|gotcha|watch out|careful|warning|caveat|trap):\s*)(.{1,500})/i,
37
- /((?:this broke|this breaks|this will break|broke because)\s+)(.{1,500})/i,
38
- /((?:the (?:issue|problem|fix) (?:is|was))\s+)(.{1,500})/i,
39
- /((?:don't forget|easy to miss):\s*)(.{1,500})/i,
15
+ /\b(?:error|bug|gotcha|watch out|careful|warning|caveat|trap):\s/i,
16
+ /\b(?:this broke|this breaks|this will break|broke because)\s/i,
17
+ /\bthe (?:issue|problem|fix) (?:is|was)\s/i,
18
+ /\b(?:don't forget|easy to miss):\s/i,
40
19
  ];
41
- // PREFERENCE_PATTERNS[0] keeps its match[1]-only, unbounded shape; extractFromPatterns
42
- // checks for this exact array element.
43
20
  const PREFERENCE_PATTERNS = [
44
- /(?:prefer|use)\s+(.{5,100})\s+(?:instead of|over|not)\s+(.{3,100})/i,
45
- /((?:don't use|avoid|skip)\s+)(.{1,500})/i,
46
- /((?:we(?:'re| are)\s+using|the stack is|we use)\s+)(.{1,500})/i,
21
+ /\b(?:prefer|use)\s+.+\s+(?:instead of|over|not)\s+/i,
22
+ /\b(?:prefer|don't use|avoid|skip)\s/i,
23
+ /\b(?:we(?:'re| are)\s+using|the stack is|we use)\s/i,
47
24
  ];
48
- // Heading patterns that signal a following list of specs/requirements
25
+ const CATEGORIES = [
26
+ ['decision', DECISION_PATTERNS], ['rule', RULE_PATTERNS],
27
+ ['error', ERROR_PATTERNS], ['preference', PREFERENCE_PATTERNS],
28
+ ];
29
+ const MAX_CHARS = 500;
30
+ const BULLET = /^(?:[-*]|\d+\.)\s+(?:\[[ xX]\]\s+)?(.+)/;
31
+ const LABEL = /^(?:decision|rule|error|bug|gotcha|important|critical|remember|plan):/i;
32
+ const SENTENCE_END = /[.!?]["')\]]*$/;
49
33
  const SPEC_HEADING_PATTERNS = [
50
34
  /^#+\s*(?:features?|requirements?|specs?|specifications?|plan|design|architecture|interface|api|todo|tasks?|implementation|notes?)(?:\s|:|$)/i,
51
35
  /^(?:features?|requirements?|specs?|specifications?|plan|design|tasks?|implementation)(?:\s*:|$)/i,
52
36
  ];
53
- // ---------------------------------------------------------------------------
54
- // Extraction engine
55
- // ---------------------------------------------------------------------------
56
- export function splitSentences(text) {
57
- // Split on sentence boundaries, keeping reasonable chunks
58
- return text
59
- .split(/(?<=[.!?])\s+|\n/)
60
- .map((s) => s.trim())
61
- .filter((s) => s.length > 5);
37
+ // A list item may open a fence on its own line: "- ```".
38
+ const FENCE_OPEN = /^(?:(?:[-*]|\d+\.)\s+)?(`{3,}|~{3,})(.*)$/;
39
+ const FENCE_CLOSE = /^(`{3,}|~{3,})\s*$/;
40
+ // Inline code and abbreviation dots never end a sentence; "etc." does when a capital follows.
41
+ const NO_BREAK = /(`+)[^`\n]+\1|\b(?:[Ee]\.g|[Ii]\.e|[Vv]s|[Cc]f)\.|\betc\.(?=\s+[a-z])/g;
42
+ /** CommonMark fences: a backtick run with a backtick after it is inline code, and only a bare run as long closes. */
43
+ function proseOnly(text) {
44
+ let fence = null;
45
+ let afterProse = false;
46
+ return text.split('\n').map(line => {
47
+ const trimmed = line.trimStart();
48
+ if (fence !== null) {
49
+ const close = trimmed.match(FENCE_CLOSE);
50
+ if (close && close[1][0] === fence[0] && close[1].length >= fence.length)
51
+ fence = null;
52
+ afterProse = false;
53
+ return '';
54
+ }
55
+ const open = trimmed.match(FENCE_OPEN);
56
+ if (open && !(open[1][0] === '`' && open[2].includes('`'))) {
57
+ fence = open[1];
58
+ afterProse = false;
59
+ return '';
60
+ }
61
+ // Quotes, table rows and indented code are not the speaker's own; indented code cannot interrupt a paragraph or list item.
62
+ const dropped = /^\s*[>|]/.test(line) || (!afterProse && /^(?: {4}|\t)(?!\s*(?:[-*]|\d+\.)\s)/.test(line));
63
+ afterProse = !dropped && trimmed !== '' && !/^#{1,6}(?:\s|$)/.test(trimmed);
64
+ return dropped ? '' : line;
65
+ }).join('\n');
62
66
  }
63
- function isLetterOrDigit(ch) {
64
- return ch !== undefined && /[\p{L}\p{N}]/u.test(ch);
67
+ function masked(text) {
68
+ return text.replace(NO_BREAK, span => 'x'.repeat(span.length));
65
69
  }
66
- /** The last plausible CLOSING single quote, or -1. Mirror of the opener rule in `boundToClause`: a closer sits tight
67
- * against its literal and is never followed by a letter, which is what makes "user's" an apostrophe. */
68
- function lastCloserIndex(full) {
69
- // ONE pass, not one per apostrophe: a rescan per apostrophe goes quadratic
70
- // on a transcript full of elisions ("keep 'em", "wait 'til", ...).
71
- //
72
- // A quote CLOSES THE SIDE IT IS TIGHT AGAINST, so the test needs both
73
- // neighbours: these two have the same following character and opposite roles -
74
- // "preserve 'a, b'-style" next '-' -> CLOSER, content on the left
75
- // "then run '--force," next '-' -> OPENER, content on the right
76
- // so no forward-only rule can separate them.
77
- //
78
- // never a closer next is a letter/digit "user's", "keep 'em"
79
- // closer prev is non-whitespace "'a, b' to", "'a, b'-style"
80
- // closer next is whitespace or end "preserve 'a, b ' exactly"
81
- // closer next is clause punctuation "preserve 'a, b ', then"
82
- // that itself ends the token
83
- //
84
- // The last clause is what separates ", " from ".env": a dot followed by a
85
- // letter joins a filename, a dot followed by space ends a sentence.
86
- for (let j = full.length - 1; j >= 0; j--) {
87
- if (full[j] !== "'")
88
- continue;
89
- const prev = full[j - 1];
90
- const next = full[j + 1];
91
- if (isLetterOrDigit(next))
92
- continue;
93
- // "tight against content" excludes an OPENING delimiter: in
94
- // "call parse('--force" the paren is non-whitespace but the quote after
95
- // it is an opener, and treating it as a closer let an earlier elision
96
- // pair across the clause boundary.
97
- const tightBefore = prev !== undefined && !/[\s([{]/.test(prev);
98
- const endsAfter = next === undefined || /\s/.test(next);
99
- // Punctuation splits in two:
100
- // , ; : ! ? ) ] } never join tokens - they end the literal whatever
101
- // follows, so "'echo a, b ';then" closes at the ;
102
- // . - _ / @ # DO join tokens - "'.env" and "'--force" continue a
103
- // filename or flag, so these only close when trailed
104
- // by whitespace
105
- const afterNext = full[j + 2];
106
- const closesRegardless = next !== undefined && /[,;:!?)\]}]/.test(next);
107
- const joinerThenSpace = next !== undefined && /[.\-_/@#]/.test(next) &&
108
- (afterNext === undefined || /\s/.test(afterNext));
109
- if (tightBefore || endsAfter || closesRegardless || joinerThenSpace)
110
- return j;
111
- }
112
- return -1;
70
+ /** A lowercase line continues the line above only when that line has not ended its sentence; "e.g." at a line end has not. */
71
+ function continues(line, previous) {
72
+ if (previous === undefined || !/^[a-z]/.test(line) || LABEL.test(line))
73
+ return false;
74
+ return !SENTENCE_END.test(masked(`${previous} ${line}`).slice(0, previous.length));
113
75
  }
114
- /** Cuts `full` (keyword + content) at the first prose `,;:` or `[.!?]` followed by whitespace, scanning from `searchFrom` so a keyword's
115
- * own colon never counts; the whitespace rule keeps `.env` whole and `maxLen` truncates a long span the 500-char gate would drop. */
116
- function boundToClause(full, searchFrom, maxLen = 200) {
117
- // Depth-awareness matters because memories are full of code; a naive scan cuts
118
- // "Always call build(x, y) before deploy." -> "Always call build(x"
119
- // and the fragment PASSES the write gate, since code punctuation reads as "specific".
120
- //
121
- // So: a separator only ends the clause at bracket depth zero and outside
122
- // quotes. Unbalanced closers are tolerated (depth floors at 0) because
123
- // captured text often starts mid-expression.
124
- const lastCloser = lastCloserIndex(full);
125
- let depth = 0;
126
- let quote = null;
127
- let cutEnd = full.length;
128
- for (let i = searchFrom; i < full.length; i++) {
129
- const ch = full[i];
130
- if (quote) {
131
- if (closesQuote(full, i, quote))
132
- quote = null;
133
- continue;
134
- }
135
- if (ch === '"' || ch === '`') {
136
- quote = ch;
137
- continue;
138
- }
139
- if (ch === "'") {
140
- if (opensSingleQuote(full, i, lastCloser)) {
141
- quote = ch;
142
- }
143
- continue;
144
- }
145
- if (ch === '(' || ch === '[' || ch === '{') {
146
- depth++;
147
- continue;
148
- }
149
- if (ch === ')' || ch === ']' || ch === '}') {
150
- if (depth > 0)
151
- depth--;
152
- continue;
153
- }
154
- if (depth > 0)
76
+ function proseBlocks(text) {
77
+ const blocks = [];
78
+ let pending = [];
79
+ const flush = () => {
80
+ if (pending.length > 0)
81
+ blocks.push(pending.join(' '));
82
+ pending = [];
83
+ };
84
+ for (const line of text.split('\n')) {
85
+ const trimmed = line.trim();
86
+ if (!trimmed || /^#+\s/.test(trimmed)) {
87
+ flush();
155
88
  continue;
156
- const next = full[i + 1];
157
- // Prose clause separator: , ; : followed by whitespace.
158
- if ((ch === ',' || ch === ';' || ch === ':') && next !== undefined && /\s/.test(next)) {
159
- cutEnd = i;
160
- break;
161
- }
162
- // Sentence terminator, but only when followed by whitespace or end — a
163
- // bare [.!?] splits inside .env, capture.ts, v1.35.0.
164
- if ((ch === '.' || ch === '!' || ch === '?') && (next === undefined || /\s/.test(next))) {
165
- cutEnd = i + 1;
166
- break;
167
89
  }
90
+ const bullet = trimmed.match(BULLET);
91
+ if (bullet || !continues(trimmed, pending.at(-1)))
92
+ flush();
93
+ pending.push(bullet?.[1] ?? trimmed);
168
94
  }
169
- let bounded = full.slice(0, cutEnd);
170
- if (bounded.length > maxLen)
171
- bounded = bounded.slice(0, maxLen);
172
- return bounded;
95
+ flush();
96
+ return blocks;
173
97
  }
174
- function closesQuote(full, i, quote) {
175
- // Closing uses the SAME shape test as opening, else a possessive INSIDE a literal closes it early:
176
- // "Always pass 'user's a, b list' to the parser." -> "Always pass 'user's a"
177
- return full[i] === quote && (quote !== "'" || !isLetterOrDigit(full[i + 1]));
98
+ function completeSentences(text) {
99
+ // Masking keeps offsets, so each sentence is cut from the unchanged source.
100
+ const segments = new Intl.Segmenter('en', { granularity: 'sentence' }).segment(masked(text));
101
+ return [...segments].map(({ index, segment }) => text.slice(index, index + segment.length).trim());
178
102
  }
179
- function opensSingleQuote(full, i, lastCloser) {
180
- // Quote handling has to tell an APOSTROPHE from a single-quoted
181
- // LITERAL, because getting either wrong reintroduces the fragment
182
- // defect this change exists to remove, and both fragments PASS the
183
- // write gate (code punctuation reads as "specific"):
184
- // treat every ' as a quote -> "Always ensure it's enabled, then
185
- // restart..." never leaves quote mode, bounding disabled entirely
186
- // treat no ' as a quote -> "Always pass 'a, b' to the parser."
187
- // cuts at the comma and stores "Always pass 'a"
188
- //
189
- // Discriminator: a ' OPENS a literal only at a word boundary - preceded
190
- // by start/whitespace/open-bracket AND followed by non-whitespace. An
191
- // in-word apostrophe ("it's", "user's") has letters on both sides and is
192
- // just a character.
193
- const prev = i > 0 ? full[i - 1] : undefined;
194
- const next = full[i + 1];
195
- const atBoundary = (prev === undefined || /[\s([{]/.test(prev)) &&
196
- next !== undefined && !/\s/.test(next);
197
- // Pairing is VERIFIED, not assumed. A word-boundary test alone still
198
- // opens quote mode on elided forms ("keep 'em", "wait 'til"), which
199
- // have no closer, so the scanner never leaves quote mode and bounding
200
- // is disabled for the rest of the capture. Requiring an actual closing
201
- // quote later in the string replaces a guess with a checkable fact -
202
- // an elided form simply has no partner.
203
- // ...and the partner must LOOK like a closer, not merely be another
204
- // apostrophe. Distance cannot separate the two cases - both put a `'`
205
- // far downstream:
206
- // "Always pass '<600-char literal>' to the parser" -> the far ' IS
207
- // the closer, and pairing must succeed
208
- // "keep 'em enabled, then <520 chars> check user's config" -> the far
209
- // ' is in-word, and pairing against it re-opens quote mode on the
210
- // elision, disabling bounding for the rest of the capture
211
- // So apply the SAME shape rule already used to open a literal, mirrored:
212
- // a closer has non-whitespace before it and whitespace/punctuation (not
213
- // a letter) after. "user's" fails it on both counts.
214
- return atBoundary && lastCloser > i;
103
+ function insideBrackets(lead) {
104
+ return (lead.match(/[([{]/g) ?? []).length > (lead.match(/[)\]}]/g) ?? []).length;
215
105
  }
216
- function cleanExtract(raw) {
217
- let content = raw
218
- .replace(/^[:\s-]+/, '')
219
- .replace(/[.!?,;:\s]+$/, '')
220
- .trim();
221
- // Trailing cleanup: clause-bounding can cut inside a parenthetical and
222
- // leave an unmatched trailing ')' (e.g. "...(two had never got entries),"
223
- // bounds to "never got entries)"). Strip a trailing ')' ONLY while closes
224
- // outnumber opens in the string so far — a balanced parenthetical like
225
- // "always run the suite (twice)" must be left intact.
226
- while (content.endsWith(')')) {
227
- const opens = (content.match(/\(/g) ?? []).length;
228
- const closes = (content.match(/\)/g) ?? []).length;
229
- if (closes <= opens)
230
- break;
231
- content = content
232
- .slice(0, -1)
233
- .replace(/[.!?,;:\s]+$/, '')
234
- .trim();
235
- }
236
- return content;
237
- }
238
- /** The 500-char write gate minus the 200-char bound counted from the keyword. */
239
- const RULE_LEAD_CAP = 300;
240
- /** A modal rule keeps its subject ("We must never ..."); null keeps the keyword-start form when the lead is long or the keyword sits inside a bracket. */
241
- function ruleLead(sentence, keywordStart, contentStart) {
242
- if (keywordStart < 0 || contentStart < 0 || contentStart > RULE_LEAD_CAP)
106
+ function extractFromPatterns(sentence, patterns, category) {
107
+ // A keyword inside brackets does not support a detached assertion; a later one outside them still does.
108
+ const keyed = patterns.some((pattern) => [...sentence.matchAll(new RegExp(pattern.source, `${pattern.flags}g`))]
109
+ .some((match) => !insideBrackets(sentence.slice(0, match.index))));
110
+ if (!keyed)
243
111
  return null;
244
- let depth = 0;
245
- for (const ch of sentence.slice(0, keywordStart)) {
246
- if (ch === '(' || ch === '[' || ch === '{')
247
- depth++;
248
- else if ((ch === ')' || ch === ']' || ch === '}') && depth > 0)
249
- depth--;
250
- }
251
- return depth > 0 ? null : sentence.slice(0, contentStart);
252
- }
253
- function extractFromPatterns(sentence, patterns, category, tag) {
254
- for (const pat of patterns) {
255
- // `d` gives per-group offsets; see the contentStart comment below.
256
- const dpat = pat.flags.includes('d') ? pat : new RegExp(pat.source, pat.flags + 'd');
257
- const match = dpat.exec(sentence);
258
- if (match) {
259
- let bounded;
260
- if (pat === PREFERENCE_PATTERNS[0]) {
261
- // PREFERENCE_PATTERNS[0] skips clause-bounding (see comment at its definition).
262
- bounded = match[1] ?? match[0];
263
- }
264
- else {
265
- // group1 = keyword + separator (verbatim, never clause-bounded);
266
- // group2 = the content that follows it (clause-bounded below).
267
- //
268
- // The keyword is kept only when it carries SEMANTIC SIGN, a
269
- // negation or modality ("never", "must not", "do not ever",
270
- // "always"), because dropping those inverts the meaning. A LABEL keyword ("decision:", "rule:", "error:",
271
- // "important:") carries no sign: it only names the category, which
272
- // is already recorded in `category`/`tags`, so prefixing it onto the
273
- // content is duplication that also breaks value-keyed matching
274
- // (the rejected-value digest hashes the bare content).
275
- // Discriminator: a label ends in its own colon, or is a "the X is"
276
- // phrase.
277
- const rawPrefix = match[1] ?? '';
278
- // ONLY colon-terminated labels are dropped: dropping "the X is/was" leaves a residue starting
279
- // with "to ", which isFragment rejects, so "The plan is to ship on Friday" would store nothing.
280
- const isLabelPrefix = /:\s*$/.test(rawPrefix);
281
- const keywordPrefix = isLabelPrefix ? '' : rawPrefix;
282
- // Scan the UNTRUNCATED remainder, not match[2]: a quoted literal whose closer sits past the
283
- // 500-char group cap would read as prose; boundToClause caps its OUTPUT, so this costs nothing.
284
- // Group 2's REAL offset from the regex engine: DECISION_PATTERNS carry an uncaptured subject
285
- // ("we ", "let's ") ahead of group 1, so `match.index + rawPrefix.length` lands inside the keyword.
286
- const contentStart = match.indices?.[2]?.[0] ?? -1;
287
- const afterKeyword = contentStart >= 0 ? sentence.slice(contentStart) : (match[2] ?? match[0]);
288
- const keywordStart = match.indices?.[1]?.[0] ?? -1;
289
- const lead = pat === RULE_PATTERNS[0] ? ruleLead(sentence, keywordStart, contentStart) : null;
290
- bounded = lead !== null
291
- ? boundToClause(lead + afterKeyword, lead.length, keywordStart + 200)
292
- : boundToClause(keywordPrefix + afterKeyword, keywordPrefix.length);
293
- }
294
- const content = cleanExtract(bounded);
295
- if (content.length >= 8 && content.length <= 500) {
296
- return { content, category, tags: [tag, 'captured'] };
297
- }
298
- }
299
- }
300
- return null;
112
+ const content = sentence
113
+ .replace(/^(?:decision|plan|rule|important|critical|remember|error|bug|gotcha|watch out|careful|warning|caveat|trap|don't forget|easy to miss):\s*/i, '')
114
+ .replace(/[.!?\s]+$/, '').trim();
115
+ return content.length <= MAX_CHARS ? { content, category, tags: [category, 'captured'] } : null;
301
116
  }
302
- /** Extract spec items from bullet lists that follow spec-like headings. */
303
117
  function extractSpecSections(text) {
304
- const items = [];
305
- const lines = text.split('\n');
118
+ const bullets = [];
306
119
  let inSpecSection = false;
307
- for (const line of lines) {
120
+ let open = false;
121
+ for (const line of text.split('\n')) {
308
122
  const trimmed = line.trim();
309
- // Check if this line is a spec heading
310
- if (SPEC_HEADING_PATTERNS.some((p) => p.test(trimmed))) {
123
+ if (SPEC_HEADING_PATTERNS.some(pattern => pattern.test(trimmed))) {
311
124
  inSpecSection = true;
125
+ open = false;
312
126
  continue;
313
127
  }
314
- // Another heading resets the section
315
- if (/^#+\s/.test(trimmed) || /^[A-Z][a-z]+:$/.test(trimmed)) {
128
+ if (/^#+\s/.test(trimmed) || /^[A-Z][a-z]+:$/.test(trimmed))
316
129
  inSpecSection = false;
317
- continue;
318
- }
319
- // Blank line after non-bullet content ends section
320
- if (!trimmed && inSpecSection) {
321
- // Keep going, blank lines within spec sections are ok
322
- continue;
323
- }
324
- if (inSpecSection) {
325
- const bulletMatch = trimmed.match(/^[-*]\s+(.+)/) || trimmed.match(/^\d+\.\s+(.+)/);
326
- if (bulletMatch) {
327
- const content = bulletMatch[1].trim();
328
- if (content.length >= 8 && content.length <= 500) {
329
- items.push({
330
- content,
331
- category: 'spec',
332
- tags: ['spec', 'captured'],
333
- });
130
+ const bullet = trimmed.match(BULLET);
131
+ const wrapped = inSpecSection && open && !bullet && trimmed !== '' && (/^\s/.test(line) || continues(trimmed, bullets.at(-1)));
132
+ if (inSpecSection && bullet)
133
+ bullets.push(bullet[1].trim());
134
+ else if (wrapped)
135
+ bullets[bullets.length - 1] += ` ${trimmed}`;
136
+ open = inSpecSection && (bullet !== null || wrapped);
137
+ }
138
+ return bullets
139
+ .filter(content => content.length <= MAX_CHARS && !content.endsWith('?'))
140
+ .map(content => ({ content, category: 'spec', tags: ['spec', 'captured'] }));
141
+ }
142
+ function extractOne(text) {
143
+ const prose = proseOnly(text);
144
+ const items = extractSpecSections(prose);
145
+ for (const block of proseBlocks(prose)) {
146
+ for (const sentence of completeSentences(block)) {
147
+ // A question asks rather than states; far over the bound, skip before any pattern runs.
148
+ if (sentence.endsWith('?') || sentence.length > 2 * MAX_CHARS)
149
+ continue;
150
+ for (const [category, patterns] of CATEGORIES) {
151
+ const item = extractFromPatterns(sentence, patterns, category);
152
+ if (item) {
153
+ items.push(item);
154
+ break;
334
155
  }
335
156
  }
336
157
  }
337
158
  }
338
159
  return items;
339
160
  }
340
- /**
341
- * Main extraction function. Scans text for actionable items using heuristics.
342
- */
343
- export function extractFromText(text) {
344
- const items = [];
161
+ /** Captures complete supported statements, each text parsed alone so a fence or heading in one never reaches the next. */
162
+ export function extractFromTexts(texts) {
345
163
  const seen = new Set();
346
- const addIfNew = (item) => {
347
- const norm = duplicateKey(item.content);
348
- if (seen.has(norm))
349
- return;
350
- if (!isContentWorthStoring(item.content))
351
- return;
352
- seen.add(norm);
353
- items.push(item);
354
- };
355
- // 1. Extract spec sections (bullet lists under spec headings)
356
- for (const item of extractSpecSections(text)) {
357
- addIfNew(item);
358
- }
359
- // 2. Pattern-match on individual sentences
360
- const sentences = splitSentences(text);
361
- for (const sentence of sentences) {
362
- // Try each category in priority order
363
- const decision = extractFromPatterns(sentence, DECISION_PATTERNS, 'decision', 'decision');
364
- if (decision) {
365
- addIfNew(decision);
366
- continue;
367
- }
368
- const rule = extractFromPatterns(sentence, RULE_PATTERNS, 'rule', 'rule');
369
- if (rule) {
370
- addIfNew(rule);
371
- continue;
372
- }
373
- const error = extractFromPatterns(sentence, ERROR_PATTERNS, 'error', 'error');
374
- if (error) {
375
- addIfNew(error);
376
- continue;
377
- }
378
- const preference = extractFromPatterns(sentence, PREFERENCE_PATTERNS, 'preference', 'preference');
379
- if (preference) {
380
- addIfNew(preference);
381
- continue;
382
- }
383
- }
384
- return items;
164
+ return texts.flatMap(extractOne).filter((item) => {
165
+ const key = duplicateKey(item.content);
166
+ if (seen.has(key) || !assessAutomaticMemory(item.content).accepted)
167
+ return false;
168
+ seen.add(key);
169
+ return true;
170
+ });
171
+ }
172
+ export function extractFromText(text) {
173
+ return extractFromTexts([text]);
385
174
  }
386
175
  //# sourceMappingURL=extract.js.map
@@ -0,0 +1,21 @@
1
+ import type { Context } from '../api/types.js';
2
+ import type { CallerProject } from '../prompt-hook.js';
3
+ import { type CaptureErrorOutcome, type RoutineRule } from './failure-reading.js';
4
+ type FailureSkip = Exclude<CaptureErrorOutcome, 'stored' | 'duplicate'>;
5
+ /** failureReport's reading of the payload, read on the caller's machine, plus the id its retries carry. */
6
+ export interface CallerFailureRequest {
7
+ readonly sessionId: string;
8
+ readonly project: CallerProject;
9
+ readonly tool: string | null;
10
+ readonly text: string | null;
11
+ readonly skip: FailureSkip | null;
12
+ readonly rule: RoutineRule | null;
13
+ readonly detailHash: string | null;
14
+ readonly requestId: string;
15
+ }
16
+ export interface CallerFailureResult {
17
+ readonly outcome: CaptureErrorOutcome;
18
+ }
19
+ export declare function captureFailureForCaller(ctx: Context, req: CallerFailureRequest): CallerFailureResult;
20
+ export {};
21
+ //# sourceMappingURL=failure-caller.d.ts.map
@@ -0,0 +1,74 @@
1
+ // PostToolUseFailure for a caller on another machine: the lesson and its log row carry the caller's owner and project, and a retried request id gets the first answer.
2
+ import { BadRequestError } from '../api-errors.js';
3
+ import { storeLesson } from '../capture-error.js';
4
+ import { recordFailure, requestOutcome, settleFailureOutcome } from '../failure-log.js';
5
+ import { errorMessage, log } from '../log.js';
6
+ import { scrubForSharing } from '../share-scrub.js';
7
+ import { truncateCodePointSafe } from '../transcript-tail.js';
8
+ import { assertRequestId, bindCaller, withCallerDb } from './caller-session.js';
9
+ import { FAILURE_TEXT_MAX_CHARS, failureHash } from './failure-reading.js';
10
+ const SKIPS = { 'skipped-interrupt': true, 'skipped-routine': true, 'skipped-invalid': true };
11
+ const ROUTINE_RULES = { declined: true, 'os-permission': true, 'no-match': true, 'search-tool': true, 'quiet-exit': true };
12
+ /** The shapes lessonFromFailure gives: text for a lesson or a routine skip, none for an interrupt or an unreadable payload, a rule only on a routine skip. */
13
+ function checkedFailure(req) {
14
+ assertRequestId(req.requestId);
15
+ if (req.detailHash !== null && !/^[0-9a-f]{16}$/.test(req.detailHash))
16
+ throw new BadRequestError('detail hash: 16 lowercase hex characters or null');
17
+ const { skip, text, rule } = req;
18
+ if (skip !== null && !Object.hasOwn(SKIPS, skip))
19
+ throw new BadRequestError('skip: skipped-interrupt, skipped-routine, skipped-invalid or null');
20
+ if ((skip === 'skipped-routine') !== (rule !== null) || (rule !== null && !Object.hasOwn(ROUTINE_RULES, rule))) {
21
+ throw new BadRequestError('rule: a routine rule on skipped-routine, else null');
22
+ }
23
+ if (text === null || text.trim() === '') {
24
+ if (skip === null || skip === 'skipped-routine')
25
+ throw new BadRequestError('text: required for a lesson or a routine skip');
26
+ return { skip, text: null };
27
+ }
28
+ if (skip === 'skipped-interrupt' || skip === 'skipped-invalid')
29
+ throw new BadRequestError('text: null for an interrupt or an unreadable failure');
30
+ if (text.length > FAILURE_TEXT_MAX_CHARS)
31
+ throw new BadRequestError(`text: at most ${FAILURE_TEXT_MAX_CHARS} characters`);
32
+ // Scrubbed again, as the caller's scrub is not trusted, and cut after it, as a mask can be longer than what it hides.
33
+ return { skip, text: truncateCodePointSafe(scrubForSharing(text), FAILURE_TEXT_MAX_CHARS) };
34
+ }
35
+ export function captureFailureForCaller(ctx, req) {
36
+ const failure = checkedFailure(req);
37
+ const key = bindCaller(ctx, req.sessionId, req.project);
38
+ const earlier = withCallerDb(ctx, (db) => requestOutcome(db, ctx.tenantId, req.requestId, req.sessionId));
39
+ // A retry after a lost reply gets the first answer and writes nothing; one whose store failed tries the store again.
40
+ if (earlier !== null && earlier !== 'store-failed')
41
+ return { outcome: earlier };
42
+ let logged = 'store-failed';
43
+ try {
44
+ const caller = { actor: key.owner, originProject: req.project.name, origins: key.project };
45
+ const outcome = failure.skip === null ? storeLesson(ctx.hippoRoot, ctx.tenantId, failure.text, caller) : failure.skip;
46
+ logged = outcome;
47
+ return { outcome };
48
+ }
49
+ finally {
50
+ // The hash is of the text as stored, so a repeat counts against the lesson it repeats.
51
+ logOutcome(ctx, { ...req, text: failure.text }, key, logged, earlier !== null);
52
+ }
53
+ }
54
+ /** Runs after the outcome in its own try, so a log error never hides the store's error or its outcome. */
55
+ function logOutcome(ctx, req, key, outcome, retried) {
56
+ try {
57
+ withCallerDb(ctx, (db) => {
58
+ // The request id allows one row, so a retry rewrites the first try's outcome.
59
+ if (retried) {
60
+ settleFailureOutcome(db, ctx.tenantId, req.requestId, outcome);
61
+ return;
62
+ }
63
+ recordFailure(db, {
64
+ tenantId: ctx.tenantId, sessionId: req.sessionId, tool: req.tool, outcome, rule: req.rule,
65
+ sigHash: req.text?.trim() ? failureHash(req.text) : null, detailHash: req.detailHash,
66
+ ownerSubject: key.owner, originProject: req.project.name, requestId: req.requestId,
67
+ });
68
+ });
69
+ }
70
+ catch (err) {
71
+ log.warn(`capture-error: failure not logged: ${errorMessage(err)}`);
72
+ }
73
+ }
74
+ //# sourceMappingURL=failure-caller.js.map