hippo-memory 1.63.1 → 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 +13 -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/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
package/dist/server.js CHANGED
@@ -2,20 +2,22 @@ import { envPort, envRequireAuth, envV1Rps } from './env.js';
2
2
  import { createServer } from 'node:http';
3
3
  import { existsSync } from 'node:fs';
4
4
  import { detectServer, removePidfileIfOwned, writePidfile } from './server-detect.js';
5
- import { closeHippoDb, getHippoDbPath, isSqliteBusy, openHippoDb, SERVER_DB_WAIT_MS, withBusyWait } from './db.js';
5
+ import { closeHippoDb, getHippoDbPath, isStoreBusy, openHippoDb, SERVER_DB_WAIT_MS, withBusyWait, withSqliteBlocked } from './db.js';
6
+ import { sqliteStore } from './store-port.js';
6
7
  import { auditWriteFailureCount } from './audit.js';
7
8
  import { PACKAGE_VERSION } from './version.js';
8
9
  import { errorFields, log } from './log.js';
9
10
  import { createRateLimiter } from './rate-limit.js';
10
- import { authRevoke, RecallContractError } from './api.js';
11
+ import { authCreateSelf, authRevoke, RecallContractError } from './api.js';
11
12
  import { handleSlackEventsWebhook } from './connectors/slack/webhook.js';
12
13
  import { handleGitHubEventsWebhook } from './connectors/github/webhook.js';
13
- import { BodyTooLargeError, HttpError, JSON_HEADERS, sendJson } from './http-util.js';
14
+ import { BodyTimeoutError, BodyTooLargeError, closeAfterReply, HttpError, JSON_HEADERS, sendJson } from './http-util.js';
14
15
  import { ForbiddenError } from './api-errors.js';
15
- import { isLoopback } from './server/auth.js';
16
+ import { buildContextWithAuth, isLoopback, LIMITER_MAX_KEYS, requireAuth } from './server/auth.js';
16
17
  import { enforceRateLimit } from './server/client-ip.js';
17
18
  import { drainAndClose } from './server/lifecycle.js';
18
19
  import { handleMcpPost, handleMcpStream } from './server/mcp-http.js';
20
+ import { MCP_PROJECT_SCOPED_HEADER } from './project-identity.js';
19
21
  import { logRequestFailure, matchPath, parseRequest, rejectEncodedSlash, replyFor, requestIds, resolveRequestId, sendError } from './server/request.js';
20
22
  import { handleApproveQuarantine, handleCreateAuthKey, handleListAudit, handleListAuthKeys, handleListQuarantine, handleRejectQuarantine, handleRevokeAuthKey } from './server/routes/admin.js';
21
23
  import { handleCloseCustomerNote, handleCreateCustomerNote, handleGetCustomerNote, handleListCustomerNotes, handleSupersedeCustomerNote } from './server/routes/customer-notes.js';
@@ -28,14 +30,36 @@ import { handleCloseProcess, handleCreateProcess, handleGetProcess, handleListPr
28
30
  import { handleCloseProjectBrief, handleCreateProjectBrief, handleGetProjectBrief, handleListProjectBriefs, handleRefreshProjectBrief, handleSupersedeProjectBrief } from './server/routes/project-briefs.js';
29
31
  import { handleAssembleSession, handleDrillRecall, handleGetContext, handleRecallMemories } from './server/routes/recall.js';
30
32
  import { handleCloseSkill, handleCreateSkill, handleExportSkills, handleGetSkill, handleListSkills, handleSupersedeSkill } from './server/routes/skills.js';
31
- // Add-on packages revoke keys through these without importing the whole api surface.
32
- export { authRevoke, ForbiddenError };
33
+ import { parseJsonBody } from './server/validation.js';
34
+ // Add-on packages mint and revoke keys through these without importing the whole api surface.
35
+ export { authCreateSelf, authRevoke, ForbiddenError };
33
36
  // Published on the hippo-memory/server subpath before they moved to http-util.ts, so they stay exported here.
34
37
  export { isCrossSite, LOOPBACK_HOST_HEADER } from './http-util.js';
35
38
  // The code behind these lives in src/server/; this subpath keeps exporting them.
36
39
  export { __resetSessionRecallHistoryHttp } from './server/routes/recall.js';
37
40
  export { clientIpForRateLimit } from './server/client-ip.js';
38
41
  export { isLoopback, isReservedActor } from './server/auth.js';
42
+ // What an add-on route handler needs: HttpError for its 4xx replies, promptHookContext for a caller that renders the prompt hook elsewhere, JsonValue for its body.
43
+ export { HttpError } from './http-util.js';
44
+ export { promptHookContext } from './prompt-hook.js';
45
+ // A session-end route stores the turns its caller read from a transcript on the caller's own machine.
46
+ export { captureSessionTexts } from './capture/session-texts.js';
47
+ // An add-on serves from another database by passing serve() its own HippoStore.
48
+ export { sqliteStore } from './store-port.js';
49
+ export { StoreBusyError } from './db.js';
50
+ // An add-on's install step mints the first admin key into a store folder it names, which `hippo auth create` cannot reach.
51
+ export { authCreate } from './api.js';
52
+ // An add-on that serves a team store checks the flag before it starts.
53
+ export { isSharedStore } from './config.js';
54
+ export { ownerOrSubject } from './api.js';
55
+ // A route that writes for a session binds it to the caller's owner first.
56
+ export { bindSessionOwner } from './session-owners.js';
57
+ // A hook route for a caller on another machine: each call binds the session, then writes under the caller's owner and project.
58
+ export { preCompactForCaller } from './capture/pre-compact-caller.js';
59
+ export { compactResumeForCaller } from './capture/compact-resume-caller.js';
60
+ export { saveCompactionItemsForCaller } from './capture/compaction-items-caller.js';
61
+ export { captureFailureForCaller } from './capture/failure-caller.js';
62
+ export { sessionEndHandoffForCaller } from './capture/session-end-caller.js';
39
63
  // Review patch #2: explicit allow-list for unauthenticated /v1/* routes.
40
64
  // New unauth routes MUST be added here AND get a corresponding entry in
41
65
  // tests/server-bearer-lockdown.test.ts. Do not gate auth elsewhere by
@@ -45,6 +69,7 @@ export { isLoopback, isReservedActor } from './server/auth.js';
45
69
  // `buildContextWithAuth` / `requireAuth`. Adding a route here without
46
70
  // adding the corresponding `isPublicRoute` short-circuit in a handler is
47
71
  // a no-op (auth still applies), so the failure mode is fail-closed.
72
+ // The only other unauthenticated paths are ServeOpts.publicJson's: GET only, checked at boot by assertPublicJson.
48
73
  const PUBLIC_ROUTES = new Set([
49
74
  'POST /v1/connectors/slack/events',
50
75
  'POST /v1/connectors/github/events',
@@ -128,46 +153,122 @@ const V1_ROUTES = [
128
153
  { method: 'POST', regex: /^\/v1\/customer-notes\/(\d+)\/close$/, handler: handleCloseCustomerNote },
129
154
  { method: 'GET', regex: /^\/v1\/customer-notes\/(\d+)$/, handler: handleGetCustomerNote },
130
155
  ];
131
- /**
132
- * Run the first /v1 route whose method and path match. Each matcher runs before its method check, as the
133
- * inline route blocks did, so a malformed `%` escape still throws from matchPath on any method.
134
- */
156
+ /** The route's handler bound to this request's path params, or null when method or path differ. The matcher runs before the
157
+ * method check, as the inline route blocks did, so a malformed `%` escape still throws from matchPath on any method. */
158
+ function routeMatches(route, method, path) {
159
+ if ('path' in route)
160
+ return method === route.method && path === route.path ? route.handler : null;
161
+ if ('pattern' in route) {
162
+ const params = matchPath(route.pattern, path);
163
+ return method === route.method && params ? (r) => route.handler(r, params) : null;
164
+ }
165
+ const match = path.match(route.regex);
166
+ return method === route.method && match ? (r) => route.handler(r, match) : null;
167
+ }
168
+ /** Run the first /v1 route whose method and path match. */
135
169
  async function dispatchV1Route(r, method, path) {
136
170
  for (const route of V1_ROUTES) {
137
- if ('path' in route) {
138
- if (method === route.method && path === route.path) {
139
- await route.handler(r);
140
- return true;
141
- }
142
- }
143
- else if ('pattern' in route) {
144
- const params = matchPath(route.pattern, path);
145
- if (method === route.method && params) {
146
- await route.handler(r, params);
147
- return true;
148
- }
149
- }
150
- else {
151
- const match = path.match(route.regex);
152
- if (method === route.method && match) {
153
- await route.handler(r, match);
154
- return true;
155
- }
156
- }
171
+ const run = routeMatches(route, method, path);
172
+ if (run === null)
173
+ continue;
174
+ if (!route.storeReady)
175
+ await refuseUnportedRoute(r.req, r.opts);
176
+ await run(r);
177
+ return true;
157
178
  }
158
179
  return false;
159
180
  }
181
+ const PLAIN_SEGMENT_RE = /^[A-Za-z0-9._~-]+$/;
182
+ function isPlainV1Path(path) {
183
+ return path.startsWith('/v1/') && new URL(path, 'http://h').pathname === path
184
+ && path.slice('/v1/'.length).split('/').every((segment) => PLAIN_SEGMENT_RE.test(segment));
185
+ }
186
+ // A plain path never holds a `%`, so matchPath cannot throw here.
187
+ function isCorePath(method, path) {
188
+ return PUBLIC_ROUTES.has(`${method} ${path}`) || V1_ROUTES.some((route) => routeMatches(route, method, path) !== null);
189
+ }
190
+ /** Boot-time check: an add-on path must be plain, unique and not one core serves, so no add-on shadows a core route or hides from dispatch. */
191
+ function assertAddonRoutes(routes) {
192
+ const seen = new Set();
193
+ for (const { path } of routes) {
194
+ if (!isPlainV1Path(path))
195
+ throw new Error(`add-on route '${path}' is not a plain /v1/ path (segments use A-Z a-z 0-9 . _ ~ -)`);
196
+ if (seen.has(path))
197
+ throw new Error(`add-on route '${path}' is registered twice`);
198
+ if (isCorePath('POST', path))
199
+ throw new Error(`add-on route '${path}' is already served by core`);
200
+ seen.add(path);
201
+ }
202
+ }
203
+ const PUBLIC_JSON_MAX_BYTES = 64 * 1024;
204
+ /** Boot-time check and serialization: a public path that a core GET route serves would open that route to anyone. */
205
+ function assertPublicJson(publicJson) {
206
+ const bodies = new Map();
207
+ for (const [path, value] of Object.entries(publicJson)) {
208
+ if (!isPlainV1Path(path))
209
+ throw new Error(`public JSON path '${path}' is not a plain /v1/ path (segments use A-Z a-z 0-9 . _ ~ -)`);
210
+ if (isCorePath('GET', path))
211
+ throw new Error(`public JSON path '${path}' is already served by core`);
212
+ const text = JSON.stringify(value);
213
+ if (text === undefined)
214
+ throw new Error(`public JSON at '${path}' is not JSON`);
215
+ if (Buffer.byteLength(text) > PUBLIC_JSON_MAX_BYTES)
216
+ throw new Error(`public JSON at '${path}' is over 64 KiB`);
217
+ bodies.set(path, text);
218
+ }
219
+ return bodies;
220
+ }
221
+ /** Core authenticates and parses before the handler runs, so an add-on route gets the same 401, 400 and 501 as a core one. */
222
+ async function dispatchAddonRoute({ req, res, opts }, method, path) {
223
+ const route = method === 'POST' ? opts.routes?.find((r) => r.path === path) : undefined;
224
+ if (!route)
225
+ return false;
226
+ await refuseUnportedRoute(req, opts);
227
+ const ctx = await buildContextWithAuth(req, opts);
228
+ const body = await parseJsonBody(req, ctx);
229
+ sendJson(res, 200, await route.handler({ ctx, body }));
230
+ return true;
231
+ }
232
+ /** No auth, body read or store access, so a caller with no key gets it under any store. */
233
+ function dispatchPublicJson({ res, opts }, method, path) {
234
+ const text = method === 'GET' ? opts.publicJsonBodies.get(path) : undefined;
235
+ if (text === undefined)
236
+ return false;
237
+ res.writeHead(200, { ...JSON_HEADERS, 'cache-control': 'no-store' });
238
+ res.end(text);
239
+ return true;
240
+ }
241
+ const NOT_ON_STORE_MESSAGE = 'not available on this store';
242
+ function assertSqliteStore(opts) {
243
+ if (opts.store.kind !== 'sqlite')
244
+ throw new HttpError(501, NOT_ON_STORE_MESSAGE);
245
+ }
246
+ /** Under another store, a route not yet ported answers 501 without running; the caller is checked first, so a bad key is still a 401. */
247
+ async function refuseUnportedRoute(req, opts) {
248
+ if (opts.store.kind === 'sqlite')
249
+ return;
250
+ await requireAuth(req, opts);
251
+ assertSqliteStore(opts);
252
+ }
160
253
  async function handleRequest(req, res, opts, startedAt, streamSlots, limiter) {
161
254
  // Pre-decode raw-URL slash check. Catches `%2F` / `%2f` before
162
255
  // Node's URL parser collapses them and they slip past the route table.
163
256
  rejectEncodedSlash(req.url ?? '/');
164
257
  const { method, path, query } = parseRequest(req);
258
+ // An older core ignores X-Hippo-Project, so every /mcp reply, a 429 or 401 too, tells the client this one filters by it.
259
+ if (path === '/mcp')
260
+ res.setHeader(MCP_PROJECT_SCOPED_HEADER, '1');
165
261
  if (method === 'GET' && path === '/health') {
166
262
  sendHealth(req, res, startedAt);
167
263
  return;
168
264
  }
169
265
  enforceRateLimit(req, path, limiter);
170
- if (await dispatchV1Route({ req, res, opts, query }, method, path))
266
+ const routeRequest = { req, res, opts, query };
267
+ if (await dispatchV1Route(routeRequest, method, path))
268
+ return;
269
+ if (dispatchPublicJson(routeRequest, method, path))
270
+ return;
271
+ if (await dispatchAddonRoute(routeRequest, method, path))
171
272
  return;
172
273
  if (method === 'POST' && path === '/v1/connectors/slack/events') {
173
274
  // Bearer auth deliberately skipped: this route is in PUBLIC_ROUTES and authenticates via the Slack HMAC signature.
@@ -175,6 +276,7 @@ async function handleRequest(req, res, opts, startedAt, streamSlots, limiter) {
175
276
  // Defensive: PUBLIC_ROUTES drift would land here. Fail closed.
176
277
  throw new HttpError(401, 'auth required');
177
278
  }
279
+ assertSqliteStore(opts);
178
280
  await handleSlackEventsWebhook({ req, res, opts });
179
281
  return;
180
282
  }
@@ -182,13 +284,16 @@ async function handleRequest(req, res, opts, startedAt, streamSlots, limiter) {
182
284
  if (!isPublicRoute(method, path)) {
183
285
  throw new HttpError(401, 'auth required');
184
286
  }
287
+ assertSqliteStore(opts);
185
288
  await handleGitHubEventsWebhook({ req, res, opts });
186
289
  return;
187
290
  }
188
291
  if (method === 'POST' && path === '/mcp') {
292
+ await refuseUnportedRoute(req, opts);
189
293
  await handleMcpPost(req, res, opts);
190
294
  return;
191
295
  }
296
+ // Store-ready: the stream and its heartbeat only authenticate, and auth goes through the port.
192
297
  if (method === 'GET' && path === '/mcp/stream') {
193
298
  await handleMcpStream(req, res, opts, streamSlots);
194
299
  return;
@@ -232,17 +337,48 @@ async function assertNoLiveServer(hippoRoot) {
232
337
  `Stop that server before starting another on the same hippoRoot.`);
233
338
  }
234
339
  }
235
- function bootRateLimiter() {
340
+ // SHORTCUT: buckets live in this process and reset on restart or LRU eviction; move them to the store if serve ever runs as several processes.
341
+ function limiterFor({ ratePerSec, burst }) {
342
+ return createRateLimiter({ ratePerSec, burst, idleEvictMs: 60000, maxKeys: LIMITER_MAX_KEYS });
343
+ }
344
+ function bootRateLimiter(perAddress) {
345
+ if (perAddress === 'off')
346
+ return undefined;
347
+ if (perAddress !== undefined)
348
+ return limiterFor(perAddress);
236
349
  // Per-IP rate limiter for /v1/* and /mcp*. Built here (not at module scope) so
237
350
  // HIPPO_V1_RPS is read at boot, matching HIPPO_PORT above and letting a test
238
351
  // set the rate before serve(). A non-positive or non-finite value disables
239
352
  // limiting (the opt-out knob).
240
353
  const v1Rps = Number(envV1Rps() ?? 20);
241
- return Number.isFinite(v1Rps) && v1Rps > 0
242
- ? createRateLimiter({ ratePerSec: v1Rps, burst: v1Rps * 2, idleEvictMs: 60000, maxKeys: 10000 })
243
- : undefined;
354
+ return Number.isFinite(v1Rps) && v1Rps > 0 ? limiterFor({ ratePerSec: v1Rps, burst: v1Rps * 2 }) : undefined;
244
355
  }
245
- function createStoreHolder(hippoRoot) {
356
+ const DEFAULT_FAILED_AUTH = { ratePerSec: 20, burst: 40 };
357
+ function assertRateLimitSpec(name, { ratePerSec, burst }) {
358
+ if (!Number.isFinite(ratePerSec) || ratePerSec <= 0)
359
+ throw new Error(`rateLimits.${name}.ratePerSec must be a finite number above 0`);
360
+ if (!Number.isFinite(burst) || burst < 1)
361
+ throw new Error(`rateLimits.${name}.burst must be a finite number of at least 1`);
362
+ }
363
+ /** Refuses a bad spec at boot, since a NaN or zero rate would otherwise turn a bucket off or refuse every request. */
364
+ function bootLimiters(rateLimits) {
365
+ const { perCaller, perAddress, failedAuthPerAddress = DEFAULT_FAILED_AUTH } = rateLimits ?? {};
366
+ if (perCaller !== undefined)
367
+ assertRateLimitSpec('perCaller', perCaller);
368
+ if (perAddress !== undefined && perAddress !== 'off')
369
+ assertRateLimitSpec('perAddress', perAddress);
370
+ assertRateLimitSpec('failedAuthPerAddress', failedAuthPerAddress);
371
+ return {
372
+ perAddress: bootRateLimiter(perAddress),
373
+ callerLimiter: perCaller === undefined ? undefined : limiterFor(perCaller),
374
+ failedAuthLimiter: limiterFor(failedAuthPerAddress),
375
+ };
376
+ }
377
+ function createStoreHolder(hippoRoot, store) {
378
+ if (store.kind !== 'sqlite') {
379
+ log.info(`serve: the '${store.kind}' store serves ${hippoRoot}, so no hippo.db connection is held`);
380
+ return { hold: () => { }, release: () => { } };
381
+ }
246
382
  // Handlers open and close their own connections; while this one is held, none of those closes is SQLite's last,
247
383
  // which checkpoints and deletes the WAL. It opens only once the store exists, so serving never creates one.
248
384
  let heldDb;
@@ -276,8 +412,10 @@ function replyWithFailure(req, res, err, requestId) {
276
412
  catch { /* socket already gone */ }
277
413
  return;
278
414
  }
279
- if (isSqliteBusy(err))
415
+ if (isStoreBusy(err))
280
416
  res.setHeader('Retry-After', '1');
417
+ else if (err instanceof HttpError && err.retryAfterSec !== undefined)
418
+ res.setHeader('Retry-After', String(err.retryAfterSec));
281
419
  if (mapped.status === 500) {
282
420
  // The id lets an operator find the logged cause without the client seeing internal text.
283
421
  sendJson(res, 500, { error: mapped.message, requestId });
@@ -288,10 +426,20 @@ function replyWithFailure(req, res, err, requestId) {
288
426
  sendJson(res, 400, { error: err.message, code: err.code });
289
427
  return;
290
428
  }
429
+ // readBody hit its cap or deadline, so close once the 413 or 408 is out rather than drain what the client keeps sending.
430
+ if (err instanceof BodyTooLargeError || err instanceof BodyTimeoutError)
431
+ res.once('finish', () => closeAfterReply(req));
291
432
  sendError(res, mapped.status, mapped.message);
292
- // readBody hit the 1 MB cap mid-stream, so drop the socket rather than drain unbounded bytes.
293
- if (err instanceof BodyTooLargeError)
294
- req.destroy();
433
+ }
434
+ function replyOrClose(req, res, err, requestId) {
435
+ try {
436
+ replyWithFailure(req, res, err, requestId);
437
+ }
438
+ catch (replyErr) {
439
+ // A throw here would be an unhandled rejection, which stops the daemon for every caller.
440
+ log.error(`serve: failure reply not sent, socket closed: ${replyErr instanceof Error ? replyErr.message : String(replyErr)}`, { requestId });
441
+ res.destroy();
442
+ }
295
443
  }
296
444
  function setKeepAliveTimeouts(server) {
297
445
  // The default 5s keepAliveTimeout closes idle sockets just as clients reuse them (ECONNRESET).
@@ -341,7 +489,7 @@ function installSignalHandlers(stop) {
341
489
  * requireAuth) has shipped and every route checks it except GET /health
342
490
  * (public by design for platform health checks) and the two connector
343
491
  * webhooks in PUBLIC_ROUTES, which are HMAC-gated by their own signing
344
- * secrets and 404 when those secrets are unset. But the loopback
492
+ * secrets and 404 when those secrets are unset, and any publicJson GET path. But the loopback
345
493
  * no-auth fallback inside buildContextWithAuth still admits unauthenticated
346
494
  * requests from a loopback remote address (unless they carry Forwarded,
347
495
  * X-Forwarded-For/-Host/-Proto, X-Real-IP, Cf-Connecting-Ip or True-Client-Ip, which mark a same-host proxy and get
@@ -357,27 +505,35 @@ function installSignalHandlers(stop) {
357
505
  export async function serve(opts) {
358
506
  const host = opts.host ?? '127.0.0.1';
359
507
  const requestedPort = opts.port ?? Number(envPort() ?? 6789);
508
+ // A frozen copy, so a route the caller adds or renames after boot never skips the check below.
509
+ const routes = Object.freeze((opts.routes ?? []).map(({ path, handler }) => Object.freeze({ path, handler })));
510
+ assertAddonRoutes(routes);
511
+ const publicJsonBodies = assertPublicJson(opts.publicJson ?? {});
512
+ const { perAddress: limiter, callerLimiter, failedAuthLimiter } = bootLimiters(opts.rateLimits);
360
513
  assertBindable(host);
361
514
  await assertNoLiveServer(opts.hippoRoot);
362
515
  // The server's start time. Single source of truth: it is returned by every
363
516
  // GET /health response and (below) written into the pidfile, so detectServer
364
517
  // can match the two and prove a pid-reusing impostor is not the real server.
365
518
  const startedAt = new Date().toISOString();
366
- const limiter = bootRateLimiter();
367
519
  // Open /mcp/stream count per client key, so the cap is per server rather than per process.
368
520
  const streamSlots = new Map();
369
- const store = createStoreHolder(opts.hippoRoot);
521
+ const served = {
522
+ ...opts, routes, publicJsonBodies, store: opts.store ?? sqliteStore(opts.hippoRoot), callerLimiter, failedAuthLimiter,
523
+ };
524
+ const { kind } = served.store;
525
+ const holder = createStoreHolder(opts.hippoRoot, served.store);
370
526
  const inflight = new Set();
371
527
  const server = createServer((req, res) => {
372
- res.once('finish', store.hold);
528
+ res.once('finish', holder.hold);
373
529
  inflight.add(res);
374
530
  res.once('close', () => inflight.delete(res));
375
531
  const requestId = resolveRequestId(req.headers['x-request-id']);
376
532
  requestIds.set(req, requestId);
377
533
  res.setHeader('X-Request-Id', requestId);
378
- withBusyWait(SERVER_DB_WAIT_MS, () => handleRequest(req, res, opts, startedAt, streamSlots, limiter)).catch((err) => {
379
- replyWithFailure(req, res, err, requestId);
380
- });
534
+ const run = () => withBusyWait(SERVER_DB_WAIT_MS, () => handleRequest(req, res, served, startedAt, streamSlots, limiter));
535
+ // A missed port under another store would otherwise create and write a hippo.db that store never reads.
536
+ (kind === 'sqlite' ? run() : withSqliteBlocked(kind, run)).catch((err) => replyOrClose(req, res, err, requestId));
381
537
  });
382
538
  setKeepAliveTimeouts(server);
383
539
  await listenOn(server, requestedPort, host);
@@ -389,7 +545,7 @@ export async function serve(opts) {
389
545
  const actualPort = addressInfo.port;
390
546
  const url = `http://${host.includes(':') ? `[${host}]` : host}:${actualPort}`;
391
547
  writePidfile(opts.hippoRoot, { port: actualPort, url, startedAt });
392
- store.hold();
548
+ holder.hold();
393
549
  let stopping = false;
394
550
  const stop = async () => {
395
551
  if (stopping)
@@ -400,7 +556,9 @@ export async function serve(opts) {
400
556
  // unconditional unlink here would orphan it.
401
557
  removePidfileIfOwned(opts.hippoRoot, { pid: process.pid, startedAt });
402
558
  await drainAndClose(server, inflight, opts.shutdownDrainMs ?? 5000);
403
- store.release();
559
+ holder.release();
560
+ if (!opts.store)
561
+ await served.store.close();
404
562
  };
405
563
  if (opts.handleSignals)
406
564
  installSignalHandlers(stop);
@@ -37,7 +37,6 @@ export declare function __setDigestExistsProbe(probe: ((p: string) => boolean) |
37
37
  export declare function realFsPath(p: string): string | null;
38
38
  /** Repo-relative with forward slashes, '' for the root itself, null outside the repo or when the path cannot be resolved. */
39
39
  export declare function repoRelative(p: string, repoRoot: string): string | null;
40
- export declare const USER_SEGMENT: readonly RegExp[];
41
40
  /** Sentences of the reply; code spans and "e.g." are held out of the split so their dots never end a sentence. */
42
41
  export declare function digestSentences(text: string): string[];
43
42
  export declare function sentenceScore(sentence: string): number;
@@ -3,10 +3,10 @@ import { createHash } from 'crypto';
3
3
  import * as fs from 'fs';
4
4
  import * as path from 'path';
5
5
  import { collectSessionTurns } from './capture/transcript.js';
6
- import { splitSentences } from './capture/extract.js';
7
6
  import { isObjectLike, isStringValue } from './capture-contract.js';
8
7
  import { PATCH_SUCCESS_LINE, patchPaths, shellPatch } from './codex-patch.js';
9
8
  import { loadConfig } from './config.js';
9
+ import { USER_SEGMENT } from './home-path.js';
10
10
  import { errorMessage } from './log.js';
11
11
  import { createMemory, Layer } from './memory.js';
12
12
  import { RejectedValueError } from './rejection.js';
@@ -304,16 +304,6 @@ function changedFiles(edits, repoRoot) {
304
304
  }
305
305
  return files;
306
306
  }
307
- // A home-directory path names its user and the store may be shared, so such a sentence is dropped.
308
- export const USER_SEGMENT = [
309
- /[A-Za-z]:[\\/]+(?:Users|Documents and Settings)[\\/]+[^\\/\s]+/i,
310
- /(?<![\w.])(?:\/mnt)?\/[A-Za-z]\/Users\/[^/\s]+/i,
311
- /\\\\\?\\/,
312
- /(?<![\w.~-])(?:\/var)?\/home\/[^/\s]+/,
313
- /(?<![\w.~-])(?:\/var)?\/root(?![\w.-])/,
314
- /(?<![\w.~-])\/Users\/[^/\s]+/,
315
- /[\\/][A-Z0-9_$]{1,6}~\d{1,6}(?:\.[A-Z0-9]{1,3})?(?![\w~])|\b[A-Z0-9_$]{1,6}~\d{1,6}(?:\.[A-Z0-9]{1,3})?[\\/]/,
316
- ];
317
307
  const ABSOLUTE_PATH = /(?<![\w.~/\\:-])(?:\\\\\?\\)?(?:[A-Za-z]:[\\/]|\/)[^\s`'"<>|*?()[\]{},;]+/g;
318
308
  function rewriteRepoPaths(sentence, repoRoot) {
319
309
  return sentence.replace(ABSOLUTE_PATH, (token) => {
@@ -369,7 +359,7 @@ function proseText(text) {
369
359
  export function digestSentences(text) {
370
360
  const held = [];
371
361
  const masked = proseText(text).replace(PROTECTED, (m) => `\uE000${held.push(m) - 1}\uE001`);
372
- return splitSentences(masked)
362
+ return masked.split(/(?<=[.!?])\s+|\n/).filter((s) => s.trim().length > 5)
373
363
  .map((s) => s.replace(/\uE000(\d+)\uE001/g, (_m, i) => held[Number(i)]).trim())
374
364
  .filter(Boolean);
375
365
  }
@@ -410,6 +400,7 @@ function rankedSentences(finalText, windows, repoRoot) {
410
400
  if (/[?:]$/.test(sentence) || opensOnReferent(sentence) || echoes(sentence, windows))
411
401
  return;
412
402
  const text = rewriteRepoPaths(sentence, repoRoot);
403
+ // A home-directory path names its user and the store may be shared, so such a sentence is dropped.
413
404
  if (text.length > MAX_SENTENCE_CHARS || USER_SEGMENT.some((re) => re.test(text)))
414
405
  return;
415
406
  kept.push({ index, text, score: sentenceScore(text) });
@@ -0,0 +1,7 @@
1
+ import { type Context } from './api/types.js';
2
+ import { type DatabaseSyncLike } from './db.js';
3
+ /** Binds under the write lock and returns the stored owner, which is not `owner` when a concurrent first bind won. */
4
+ export declare function insertBinding(db: DatabaseSyncLike, tenantId: string, sessionId: string, owner: string): string | null;
5
+ /** Binds `sessionId` to the caller's owner on first use; a session another owner holds is a 409, so the client can set it aside. */
6
+ export declare function bindSessionOwner(ctx: Context, sessionId: string): void;
7
+ //# sourceMappingURL=session-owners.d.ts.map
@@ -0,0 +1,54 @@
1
+ // A session id belongs to the first owner that binds it, so one developer's client cannot write into another's session.
2
+ import { ConflictError } from './api-errors.js';
3
+ import { ownerOrSubject } from './api/types.js';
4
+ import { closeHippoDb, execWithBusyRetry, HOOK_DB_WAIT_MS, openHippoDb, scopedBusyWait } from './db.js';
5
+ import { raiseMinBinary } from './db/meta.js';
6
+ import { assertTenantId } from './tenant.js';
7
+ import { TASK_OWNER_MIN_BINARY } from './version.js';
8
+ /** Bindings older than this are pruned when a new session binds. */
9
+ const BINDING_RETENTION_DAYS = 90;
10
+ function boundOwner(db, tenantId, sessionId) {
11
+ // SAFETY: the SELECT names exactly this one TEXT column.
12
+ const row = db.prepare(`SELECT owner_subject FROM session_owners WHERE tenant_id = ? AND session_id = ?`).get(tenantId, sessionId);
13
+ return row?.owner_subject ?? null;
14
+ }
15
+ /** Binds under the write lock and returns the stored owner, which is not `owner` when a concurrent first bind won. */
16
+ export function insertBinding(db, tenantId, sessionId, owner) {
17
+ execWithBusyRetry(db, 'BEGIN IMMEDIATE', scopedBusyWait() ?? HOOK_DB_WAIT_MS);
18
+ try {
19
+ const result = db.prepare(`INSERT OR IGNORE INTO session_owners(tenant_id, session_id, owner_subject, created_at) VALUES (?, ?, ?, ?)`)
20
+ .run(tenantId, sessionId, owner, new Date().toISOString());
21
+ // An older binary supersedes across owners, so the first binding shuts it out in the same transaction.
22
+ if (Number(result.changes ?? 0) === 1)
23
+ raiseMinBinary(db, TASK_OWNER_MIN_BINARY);
24
+ // Pruned on write, as the failure log is; a pruned session id is a UUID no other caller knows.
25
+ // SHORTCUT: no created_at index, so each first bind scans the table; add one past a few hundred thousand rows.
26
+ db.prepare(`DELETE FROM session_owners WHERE created_at < ?`).run(new Date(Date.now() - BINDING_RETENTION_DAYS * 86_400_000).toISOString());
27
+ db.exec('COMMIT');
28
+ }
29
+ catch (err) {
30
+ try {
31
+ db.exec('ROLLBACK');
32
+ }
33
+ catch { /* the insert's error is the one to surface */ }
34
+ throw err;
35
+ }
36
+ // Re-read, since a concurrent first bind may have won the INSERT OR IGNORE.
37
+ return boundOwner(db, tenantId, sessionId);
38
+ }
39
+ /** Binds `sessionId` to the caller's owner on first use; a session another owner holds is a 409, so the client can set it aside. */
40
+ export function bindSessionOwner(ctx, sessionId) {
41
+ assertTenantId('bindSessionOwner', ctx.tenantId);
42
+ const owner = ownerOrSubject(ctx.actor);
43
+ const db = openHippoDb(ctx.hippoRoot);
44
+ try {
45
+ // A bound session is the common case after the first prompt, so it must not take the write lock.
46
+ const bound = boundOwner(db, ctx.tenantId, sessionId) ?? insertBinding(db, ctx.tenantId, sessionId, owner);
47
+ if (bound !== owner)
48
+ throw new ConflictError('session id belongs to another caller');
49
+ }
50
+ finally {
51
+ closeHippoDb(db);
52
+ }
53
+ }
54
+ //# sourceMappingURL=session-owners.js.map
@@ -0,0 +1,3 @@
1
+ /** Secrets first, then emails, then home paths: a home-path mask could split a secret so that no pattern matches it. */
2
+ export declare function scrubForSharing(text: string): string;
3
+ //# sourceMappingURL=share-scrub.d.ts.map
@@ -0,0 +1,8 @@
1
+ // The scrub for transcript text that leaves the machine it was typed on.
2
+ import { maskHomePaths } from './home-path.js';
3
+ import { maskEmails, redactSecretsStrict } from './secret-detect.js';
4
+ /** Secrets first, then emails, then home paths: a home-path mask could split a secret so that no pattern matches it. */
5
+ export function scrubForSharing(text) {
6
+ return maskHomePaths(maskEmails(redactSecretsStrict(text)));
7
+ }
8
+ //# sourceMappingURL=share-scrub.js.map
package/dist/shared.d.ts CHANGED
@@ -87,6 +87,7 @@ export interface HybridSearchOptions extends SearchOptions {
87
87
  recallScope?: {
88
88
  requested?: string;
89
89
  additive?: boolean;
90
+ ownScope?: string;
90
91
  };
91
92
  }
92
93
  /**