hippo-memory 1.57.0 → 1.59.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 (109) hide show
  1. package/README.md +24 -1
  2. package/dist/agent-memories/apply.d.ts +1 -1
  3. package/dist/agent-memories/claude-code.js +1 -1
  4. package/dist/agent-memories/gemini.js +1 -1
  5. package/dist/agent-memories/legacy.js +4 -1
  6. package/dist/api-errors.d.ts +27 -0
  7. package/dist/api-errors.js +37 -0
  8. package/dist/api.d.ts +5 -5
  9. package/dist/api.js +40 -47
  10. package/dist/audit.d.ts +5 -1
  11. package/dist/audit.js +13 -0
  12. package/dist/autolearn.d.ts +1 -1
  13. package/dist/autolearn.js +7 -5
  14. package/dist/capture-contract.d.ts +47 -0
  15. package/dist/capture-contract.js +49 -0
  16. package/dist/capture-error.js +2 -1
  17. package/dist/capture.d.ts +0 -13
  18. package/dist/capture.js +5 -66
  19. package/dist/cli/output.d.ts +3 -0
  20. package/dist/cli/output.js +7 -0
  21. package/dist/cli/projects.d.ts +4 -0
  22. package/dist/cli/projects.js +90 -0
  23. package/dist/cli/shared.js +23 -13
  24. package/dist/cli/sleep.js +5 -3
  25. package/dist/cli.d.ts +1 -0
  26. package/dist/cli.js +486 -397
  27. package/dist/client.js +9 -0
  28. package/dist/codex-patch.js +1 -1
  29. package/dist/compaction-record.d.ts +1 -1
  30. package/dist/compaction-record.js +3 -2
  31. package/dist/config.d.ts +5 -0
  32. package/dist/config.js +17 -0
  33. package/dist/connectors/github/dlq.js +5 -2
  34. package/dist/connectors/github/octokit-client.js +4 -2
  35. package/dist/connectors/slack/dlq.js +6 -2
  36. package/dist/connectors/slack/web-client.js +7 -5
  37. package/dist/consolidate.d.ts +10 -0
  38. package/dist/consolidate.js +48 -35
  39. package/dist/customer-notes.js +14 -13
  40. package/dist/dag.js +7 -4
  41. package/dist/dashboard.js +1 -1
  42. package/dist/db.d.ts +12 -0
  43. package/dist/db.js +62 -1
  44. package/dist/decisions.js +9 -8
  45. package/dist/dedupe.js +1 -1
  46. package/dist/doctor.js +28 -0
  47. package/dist/dormant.d.ts +2 -2
  48. package/dist/embedding-provider.js +3 -3
  49. package/dist/embeddings.d.ts +4 -4
  50. package/dist/embeddings.js +72 -16
  51. package/dist/extract.js +19 -18
  52. package/dist/http-retry.d.ts +21 -0
  53. package/dist/http-retry.js +50 -0
  54. package/dist/http-util.d.ts +8 -0
  55. package/dist/http-util.js +10 -0
  56. package/dist/importers.d.ts +2 -0
  57. package/dist/importers.js +16 -5
  58. package/dist/incidents.js +11 -10
  59. package/dist/judgment.js +10 -17
  60. package/dist/log.d.ts +25 -0
  61. package/dist/log.js +48 -0
  62. package/dist/mcp/server.js +52 -24
  63. package/dist/mcp/tool-args.d.ts +21 -0
  64. package/dist/mcp/tool-args.js +80 -0
  65. package/dist/memory.js +3 -2
  66. package/dist/overlap-index.d.ts +7 -0
  67. package/dist/overlap-index.js +38 -0
  68. package/dist/pilot-arm.d.ts +9 -0
  69. package/dist/pilot-arm.js +47 -0
  70. package/dist/policies.js +12 -11
  71. package/dist/predictions.js +9 -8
  72. package/dist/processes.js +14 -13
  73. package/dist/project-briefs.js +16 -15
  74. package/dist/project-identity.d.ts +1 -1
  75. package/dist/project-identity.js +25 -1
  76. package/dist/project-merge.d.ts +52 -0
  77. package/dist/project-merge.js +168 -0
  78. package/dist/raw-archive.js +7 -6
  79. package/dist/recall-scope.d.ts +5 -4
  80. package/dist/recall-scope.js +7 -5
  81. package/dist/refine-llm.js +3 -2
  82. package/dist/reject-flow.js +6 -9
  83. package/dist/rejection.d.ts +2 -1
  84. package/dist/rejection.js +2 -1
  85. package/dist/rerankers/clef.d.ts +29 -0
  86. package/dist/rerankers/clef.js +182 -0
  87. package/dist/rerankers/index.js +3 -0
  88. package/dist/rerankers/jev.d.ts +11 -0
  89. package/dist/rerankers/jev.js +10 -5
  90. package/dist/rerankers/types.d.ts +16 -0
  91. package/dist/search.js +14 -2
  92. package/dist/secret-detect.d.ts +13 -1
  93. package/dist/secret-detect.js +33 -1
  94. package/dist/server.d.ts +9 -2
  95. package/dist/server.js +188 -411
  96. package/dist/session-digest.js +2 -1
  97. package/dist/shared.js +7 -6
  98. package/dist/skills.js +15 -14
  99. package/dist/store.js +10 -10
  100. package/dist/token-ledger.d.ts +4 -2
  101. package/dist/token-ledger.js +2 -2
  102. package/dist/version.d.ts +1 -1
  103. package/dist/version.js +1 -1
  104. package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
  105. package/extensions/openclaw-plugin/package.json +1 -1
  106. package/openclaw.plugin.json +1 -1
  107. package/package.json +5 -2
  108. package/dist/connectors/slack/ratelimit.d.ts +0 -9
  109. package/dist/connectors/slack/ratelimit.js +0 -18
package/dist/search.js CHANGED
@@ -17,7 +17,13 @@ import { openHippoDb, closeHippoDb } from './db.js';
17
17
  import { rrfFuse } from './rrf.js';
18
18
  import { graphRankStream, selectGraphSeeds, DEFAULT_GRAPH_SEED_COUNT } from './graph-stream.js';
19
19
  import { compareEntryIdentity, compareScoredResults } from './compare.js';
20
+ import { log } from './log.js';
21
+ import { redactSecretsStrict } from './secret-detect.js';
20
22
  export const CHURN_STALE_RANK_MULTIPLIER = 0.5; // SHORTCUT: untuned; FE3 measures before any default.
23
+ // Search runs on every hook prompt, so one line per reason per process says why vectors went unused without flooding stderr.
24
+ function warnBm25Fallback(reason, detail) {
25
+ log.once(`search.bm25-fallback.${reason}`, 'warn', `hybrid search fell back to BM25 only: ${redactSecretsStrict(detail)}`);
26
+ }
21
27
  export function churnStaleFactor(entry) {
22
28
  return entry.tags.includes(CHURN_STALE_TAG) ? CHURN_STALE_RANK_MULTIPLIER : 1.0;
23
29
  }
@@ -267,11 +273,17 @@ export async function hybridSearch(query, entries, options = {}) {
267
273
  embeddingIndex = idx;
268
274
  useEmbeddings = true;
269
275
  }
276
+ else {
277
+ warnBm25Fallback('empty-query-vector', 'the embedding provider returned no vector for the query');
278
+ }
270
279
  }
271
280
  }
281
+ else if (provider.isAvailable()) {
282
+ warnBm25Fallback('reindex', "the embedding index was built by another model or is being rebuilt; run 'hippo embed'");
283
+ }
272
284
  }
273
- catch {
274
- // Fall through to BM25-only
285
+ catch (err) {
286
+ warnBm25Fallback('error', err instanceof Error ? err.message : String(err));
275
287
  }
276
288
  }
277
289
  // Compute cosine similarities for RRF ranking (need all before scoring)
@@ -12,7 +12,8 @@
12
12
  * a deliberate act.
13
13
  *
14
14
  * This is deliberately a thin slice of the A4 lifecycle-compliance item
15
- * (no write-time scrubbing, no PII detection).
15
+ * (no PII detection). Write-time scrubbing covers only text no person
16
+ * typed into hippo; see `vetSecrets`.
16
17
  *
17
18
  * Leaf module: keep free of imports from store/api/shared so all of them
18
19
  * can import it without cycles.
@@ -42,4 +43,15 @@ export declare function redactSecrets(text: string): string;
42
43
  export declare function maskEmails(text: string): string;
43
44
  /** Stricter redaction for text that leaves the machine: no co-occurrence guard, plus Bearer and Basic auth headers and JWTs. */
44
45
  export declare function redactSecretsStrict(text: string): string;
46
+ /** The text a write keeps, plus what to tell its caller about secret material in it. */
47
+ export interface SecretVet {
48
+ content: string;
49
+ warnings: string[];
50
+ }
51
+ /** Write-time veto: `scrub` text no person typed gets capture's strict redaction; typed text is a person's own call, so it is kept and flagged. */
52
+ export declare function vetSecrets(content: string, tags: readonly string[], scrub: boolean): SecretVet;
53
+ /** Appended to a dead-letter row's error when `redactPayload` changed its body. */
54
+ export declare const DLQ_REDACTED_NOTE = "secret-shaped text redacted, signature dropped; replay needs --force";
55
+ /** `vetSecrets` redaction for a raw webhook body: JSON stays valid for replay, and an untouched body stays byte-identical so its signature still verifies. */
56
+ export declare function redactPayload(raw: string): string;
45
57
  //# sourceMappingURL=secret-detect.d.ts.map
@@ -12,7 +12,8 @@
12
12
  * a deliberate act.
13
13
  *
14
14
  * This is deliberately a thin slice of the A4 lifecycle-compliance item
15
- * (no write-time scrubbing, no PII detection).
15
+ * (no PII detection). Write-time scrubbing covers only text no person
16
+ * typed into hippo; see `vetSecrets`.
16
17
  *
17
18
  * Leaf module: keep free of imports from store/api/shared so all of them
18
19
  * can import it without cycles.
@@ -100,6 +101,37 @@ export function maskEmails(text) {
100
101
  export function redactSecretsStrict(text) {
101
102
  return redactText(text, true);
102
103
  }
104
+ /** Write-time veto: `scrub` text no person typed gets capture's strict redaction; typed text is a person's own call, so it is kept and flagged. */
105
+ export function vetSecrets(content, tags, scrub) {
106
+ const kept = scrub ? redactSecretsStrict(content) : content;
107
+ const warnings = [];
108
+ if (kept !== content) {
109
+ const found = detectSecret({ content, tags: [] }).reason;
110
+ warnings.push(`secret-shaped text was redacted before storing${found ? ` (${found})` : ''}`);
111
+ }
112
+ const left = detectSecret({ content: kept, tags: [...tags] });
113
+ if (left.flagged) {
114
+ warnings.push(`content looks like a secret (${left.reason}) and was stored as sent; forget it if it should not be kept`);
115
+ }
116
+ return { content: kept, warnings };
117
+ }
118
+ const JSON_STRING = /"(?:[^"\\]|\\.)*"/g;
119
+ /** Appended to a dead-letter row's error when `redactPayload` changed its body. */
120
+ export const DLQ_REDACTED_NOTE = 'secret-shaped text redacted, signature dropped; replay needs --force';
121
+ /** `vetSecrets` redaction for a raw webhook body: JSON stays valid for replay, and an untouched body stays byte-identical so its signature still verifies. */
122
+ export function redactPayload(raw) {
123
+ try {
124
+ JSON.parse(raw);
125
+ }
126
+ catch {
127
+ return redactSecretsStrict(raw);
128
+ }
129
+ return raw.replace(JSON_STRING, (literal) => {
130
+ const text = JSON.parse(literal);
131
+ const kept = redactSecretsStrict(text);
132
+ return kept === text ? literal : JSON.stringify(kept);
133
+ });
134
+ }
103
135
  function redactText(text, strict) {
104
136
  if (!text)
105
137
  return text;
package/dist/server.d.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  import { type IncomingMessage } from 'node:http';
2
2
  /** Test-only: reset the module-level recall-history Map. Call from beforeEach. */
3
3
  export declare function __resetSessionRecallHistoryHttp(): void;
4
+ import { authRevoke, type Actor, type Context } from './api.js';
5
+ import { ForbiddenError } from './api-errors.js';
6
+ export { authRevoke, ForbiddenError, type Context, type Actor };
4
7
  export interface ServerHandle {
5
8
  port: number;
6
9
  url: string;
@@ -15,7 +18,7 @@ export interface ServerHandle {
15
18
  export interface ResolvedBearer {
16
19
  tenantId: string;
17
20
  subject: string;
18
- /** Not 'admin' means 'member'. Admin is tenant-only, yet can mint member API keys (POST /v1/auth/keys) that outlive IdP deprovisioning. */
21
+ /** Not 'admin' means 'member'. Admin is tenant-only, yet can mint member API keys (POST /v1/auth/keys); an add-on revokes them through the exported authRevoke when the IdP deprovisions the minter. */
19
22
  role: 'admin' | 'member';
20
23
  scopes?: readonly string[];
21
24
  }
@@ -56,6 +59,8 @@ export declare function isCrossSite(req: IncomingMessage): boolean;
56
59
  * bucket per request and bypass the limiter entirely.
57
60
  */
58
61
  export declare function clientIpForRateLimit(req: IncomingMessage): string;
62
+ /** Add-ons call this to refuse a subject that would collide with a built-in actor. */
63
+ export declare function isReservedActor(subject: string): boolean;
59
64
  /**
60
65
  * Boot the HTTP daemon on host:port and write the pidfile under hippoRoot.
61
66
  *
@@ -66,7 +71,9 @@ export declare function clientIpForRateLimit(req: IncomingMessage): string;
66
71
  * webhooks in PUBLIC_ROUTES, which are HMAC-gated by their own signing
67
72
  * secrets and 404 when those secrets are unset. But the loopback
68
73
  * no-auth fallback inside buildContextWithAuth still admits unauthenticated
69
- * requests from a loopback remote address, so binding to a non-loopback host
74
+ * requests from a loopback remote address (unless they carry Forwarded,
75
+ * X-Forwarded-For/-Host/-Proto, X-Real-IP, Cf-Connecting-Ip or True-Client-Ip, which mark a same-host proxy and get
76
+ * a 401 like any keyless remote request), so binding to a non-loopback host
70
77
  * is only safe once that fallback is disabled with HIPPO_REQUIRE_AUTH=1,
71
78
  * which forces every request (loopback or not) through Bearer-token
72
79
  * validation. Without that env var set, a non-loopback bind would expose the