hippo-memory 0.36.0 → 0.37.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 (199) hide show
  1. package/README.md +16 -0
  2. package/dist/api.d.ts +20 -0
  3. package/dist/api.d.ts.map +1 -1
  4. package/dist/api.js +23 -3
  5. package/dist/api.js.map +1 -1
  6. package/dist/benchmarks/e1.3/incident-recall-eval.js +74 -0
  7. package/dist/benchmarks/e1.3/incident-recall-eval.js.map +1 -0
  8. package/dist/benchmarks/e1.3/scenarios.json +2587 -0
  9. package/dist/benchmarks/e1.3/slack-1000-event-smoke.js +102 -0
  10. package/dist/benchmarks/e1.3/slack-1000-event-smoke.js.map +1 -0
  11. package/dist/cli.js +82 -0
  12. package/dist/cli.js.map +1 -1
  13. package/dist/connectors/slack/backfill.d.ts +42 -0
  14. package/dist/connectors/slack/backfill.d.ts.map +1 -0
  15. package/dist/connectors/slack/backfill.js +76 -0
  16. package/dist/connectors/slack/backfill.js.map +1 -0
  17. package/dist/connectors/slack/deletion.d.ts +14 -0
  18. package/dist/connectors/slack/deletion.d.ts.map +1 -0
  19. package/dist/connectors/slack/deletion.js +46 -0
  20. package/dist/connectors/slack/deletion.js.map +1 -0
  21. package/dist/connectors/slack/dlq.d.ts +21 -0
  22. package/dist/connectors/slack/dlq.d.ts.map +1 -0
  23. package/dist/connectors/slack/dlq.js +23 -0
  24. package/dist/connectors/slack/dlq.js.map +1 -0
  25. package/dist/connectors/slack/idempotency.d.ts +5 -0
  26. package/dist/connectors/slack/idempotency.d.ts.map +1 -0
  27. package/dist/connectors/slack/idempotency.js +13 -0
  28. package/dist/connectors/slack/idempotency.js.map +1 -0
  29. package/dist/connectors/slack/ingest.d.ts +27 -0
  30. package/dist/connectors/slack/ingest.d.ts.map +1 -0
  31. package/dist/connectors/slack/ingest.js +48 -0
  32. package/dist/connectors/slack/ingest.js.map +1 -0
  33. package/dist/connectors/slack/ratelimit.d.ts +9 -0
  34. package/dist/connectors/slack/ratelimit.d.ts.map +1 -0
  35. package/dist/connectors/slack/ratelimit.js +18 -0
  36. package/dist/connectors/slack/ratelimit.js.map +1 -0
  37. package/dist/connectors/slack/scope.d.ts +16 -0
  38. package/dist/connectors/slack/scope.d.ts.map +1 -0
  39. package/dist/connectors/slack/scope.js +13 -0
  40. package/dist/connectors/slack/scope.js.map +1 -0
  41. package/dist/connectors/slack/signature.d.ts +12 -0
  42. package/dist/connectors/slack/signature.d.ts.map +1 -0
  43. package/dist/connectors/slack/signature.js +20 -0
  44. package/dist/connectors/slack/signature.js.map +1 -0
  45. package/dist/connectors/slack/tenant-routing.d.ts +13 -0
  46. package/dist/connectors/slack/tenant-routing.d.ts.map +1 -0
  47. package/dist/connectors/slack/tenant-routing.js +17 -0
  48. package/dist/connectors/slack/tenant-routing.js.map +1 -0
  49. package/dist/connectors/slack/transform.d.ts +20 -0
  50. package/dist/connectors/slack/transform.d.ts.map +1 -0
  51. package/dist/connectors/slack/transform.js +31 -0
  52. package/dist/connectors/slack/transform.js.map +1 -0
  53. package/dist/connectors/slack/types.d.ts +35 -0
  54. package/dist/connectors/slack/types.d.ts.map +1 -0
  55. package/dist/connectors/slack/types.js +23 -0
  56. package/dist/connectors/slack/types.js.map +1 -0
  57. package/dist/connectors/slack/web-client.d.ts +12 -0
  58. package/dist/connectors/slack/web-client.d.ts.map +1 -0
  59. package/dist/connectors/slack/web-client.js +43 -0
  60. package/dist/connectors/slack/web-client.js.map +1 -0
  61. package/dist/db.d.ts.map +1 -1
  62. package/dist/db.js +46 -1
  63. package/dist/db.js.map +1 -1
  64. package/dist/importers.js +3 -3
  65. package/dist/importers.js.map +1 -1
  66. package/dist/mcp/server.js +1 -1
  67. package/dist/server.d.ts.map +1 -1
  68. package/dist/server.js +174 -2
  69. package/dist/server.js.map +1 -1
  70. package/dist/src/ambient.js +147 -0
  71. package/dist/src/ambient.js.map +1 -0
  72. package/dist/src/api.js +343 -0
  73. package/dist/src/api.js.map +1 -0
  74. package/dist/src/audit.js +152 -0
  75. package/dist/src/audit.js.map +1 -0
  76. package/dist/src/auth.js +65 -0
  77. package/dist/src/auth.js.map +1 -0
  78. package/dist/src/autolearn.js +143 -0
  79. package/dist/src/autolearn.js.map +1 -0
  80. package/dist/src/capture.js +512 -0
  81. package/dist/src/capture.js.map +1 -0
  82. package/dist/src/cli.js +4971 -0
  83. package/dist/src/cli.js.map +1 -0
  84. package/dist/src/client.js +181 -0
  85. package/dist/src/client.js.map +1 -0
  86. package/dist/src/config.js +108 -0
  87. package/dist/src/config.js.map +1 -0
  88. package/dist/src/connectors/slack/backfill.js +76 -0
  89. package/dist/src/connectors/slack/backfill.js.map +1 -0
  90. package/dist/src/connectors/slack/deletion.js +46 -0
  91. package/dist/src/connectors/slack/deletion.js.map +1 -0
  92. package/dist/src/connectors/slack/dlq.js +23 -0
  93. package/dist/src/connectors/slack/dlq.js.map +1 -0
  94. package/dist/src/connectors/slack/idempotency.js +13 -0
  95. package/dist/src/connectors/slack/idempotency.js.map +1 -0
  96. package/dist/src/connectors/slack/ingest.js +48 -0
  97. package/dist/src/connectors/slack/ingest.js.map +1 -0
  98. package/dist/src/connectors/slack/ratelimit.js +18 -0
  99. package/dist/src/connectors/slack/ratelimit.js.map +1 -0
  100. package/dist/src/connectors/slack/scope.js +13 -0
  101. package/dist/src/connectors/slack/scope.js.map +1 -0
  102. package/dist/src/connectors/slack/signature.js +20 -0
  103. package/dist/src/connectors/slack/signature.js.map +1 -0
  104. package/dist/src/connectors/slack/tenant-routing.js +17 -0
  105. package/dist/src/connectors/slack/tenant-routing.js.map +1 -0
  106. package/dist/src/connectors/slack/transform.js +31 -0
  107. package/dist/src/connectors/slack/transform.js.map +1 -0
  108. package/dist/src/connectors/slack/types.js +23 -0
  109. package/dist/src/connectors/slack/types.js.map +1 -0
  110. package/dist/src/connectors/slack/web-client.js +43 -0
  111. package/dist/src/connectors/slack/web-client.js.map +1 -0
  112. package/dist/src/consolidate.js +517 -0
  113. package/dist/src/consolidate.js.map +1 -0
  114. package/dist/src/dag.js +104 -0
  115. package/dist/src/dag.js.map +1 -0
  116. package/dist/src/dashboard.js +409 -0
  117. package/dist/src/dashboard.js.map +1 -0
  118. package/dist/src/db.js +584 -0
  119. package/dist/src/db.js.map +1 -0
  120. package/dist/src/embeddings.js +344 -0
  121. package/dist/src/embeddings.js.map +1 -0
  122. package/dist/src/eval-suite.js +289 -0
  123. package/dist/src/eval-suite.js.map +1 -0
  124. package/dist/src/eval.js +187 -0
  125. package/dist/src/eval.js.map +1 -0
  126. package/dist/src/extract.js +87 -0
  127. package/dist/src/extract.js.map +1 -0
  128. package/dist/src/handoff.js +30 -0
  129. package/dist/src/handoff.js.map +1 -0
  130. package/dist/src/hooks.js +582 -0
  131. package/dist/src/hooks.js.map +1 -0
  132. package/dist/src/importers.js +399 -0
  133. package/dist/src/importers.js.map +1 -0
  134. package/dist/src/index.js +25 -0
  135. package/dist/src/index.js.map +1 -0
  136. package/dist/src/invalidation.js +94 -0
  137. package/dist/src/invalidation.js.map +1 -0
  138. package/dist/src/mcp/framing.js +45 -0
  139. package/dist/src/mcp/framing.js.map +1 -0
  140. package/dist/src/mcp/server.js +510 -0
  141. package/dist/src/mcp/server.js.map +1 -0
  142. package/dist/src/memory.js +280 -0
  143. package/dist/src/memory.js.map +1 -0
  144. package/dist/src/multihop.js +32 -0
  145. package/dist/src/multihop.js.map +1 -0
  146. package/dist/src/path-context.js +32 -0
  147. package/dist/src/path-context.js.map +1 -0
  148. package/dist/src/physics-config.js +26 -0
  149. package/dist/src/physics-config.js.map +1 -0
  150. package/dist/src/physics-state.js +163 -0
  151. package/dist/src/physics-state.js.map +1 -0
  152. package/dist/src/physics.js +361 -0
  153. package/dist/src/physics.js.map +1 -0
  154. package/dist/src/postinstall.js +68 -0
  155. package/dist/src/postinstall.js.map +1 -0
  156. package/dist/src/raw-archive.js +72 -0
  157. package/dist/src/raw-archive.js.map +1 -0
  158. package/dist/src/refine-llm.js +147 -0
  159. package/dist/src/refine-llm.js.map +1 -0
  160. package/dist/src/replay.js +117 -0
  161. package/dist/src/replay.js.map +1 -0
  162. package/dist/src/salience.js +74 -0
  163. package/dist/src/salience.js.map +1 -0
  164. package/dist/src/scheduler.js +67 -0
  165. package/dist/src/scheduler.js.map +1 -0
  166. package/dist/src/scope.js +35 -0
  167. package/dist/src/scope.js.map +1 -0
  168. package/dist/src/search.js +801 -0
  169. package/dist/src/search.js.map +1 -0
  170. package/dist/src/server-detect.js +70 -0
  171. package/dist/src/server-detect.js.map +1 -0
  172. package/dist/src/server.js +784 -0
  173. package/dist/src/server.js.map +1 -0
  174. package/dist/src/shared.js +309 -0
  175. package/dist/src/shared.js.map +1 -0
  176. package/dist/src/sso.js +22 -0
  177. package/dist/src/sso.js.map +1 -0
  178. package/dist/src/store.js +1390 -0
  179. package/dist/src/store.js.map +1 -0
  180. package/dist/src/tenant.js +17 -0
  181. package/dist/src/tenant.js.map +1 -0
  182. package/dist/src/trace.js +64 -0
  183. package/dist/src/trace.js.map +1 -0
  184. package/dist/src/working-memory.js +149 -0
  185. package/dist/src/working-memory.js.map +1 -0
  186. package/dist/src/yaml.js +98 -0
  187. package/dist/src/yaml.js.map +1 -0
  188. package/dist/store.d.ts +9 -1
  189. package/dist/store.d.ts.map +1 -1
  190. package/dist/store.js +30 -2
  191. package/dist/store.js.map +1 -1
  192. package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
  193. package/extensions/openclaw-plugin/package.json +1 -1
  194. package/openclaw.plugin.json +1 -1
  195. package/package.json +2 -2
  196. package/dist/import.d.ts +0 -31
  197. package/dist/import.d.ts.map +0 -1
  198. package/dist/import.js +0 -307
  199. package/dist/import.js.map +0 -1
@@ -0,0 +1,42 @@
1
+ import type { Context } from '../../api.js';
2
+ import type { SlackMessageEvent } from './types.js';
3
+ import type { ChannelMeta } from './scope.js';
4
+ export interface SlackHistoryPage {
5
+ messages: SlackMessageEvent[];
6
+ next_cursor: string | null;
7
+ }
8
+ export type SlackHistoryFetcher = (args: {
9
+ channelId: string;
10
+ /** Slack's opaque pagination token. Null on first page of a backfill run. */
11
+ cursor: string | null;
12
+ /**
13
+ * Incremental-resume bound: skip messages with ts <= oldest. Set from
14
+ * `slack_cursors.latest_ts` on the FIRST page of a rerun; unused on
15
+ * subsequent pages within one run (where `cursor` carries us forward).
16
+ * Distinct from `cursor` because Slack treats `cursor` as opaque token
17
+ * and `oldest` as a numeric ts — feeding `latest_ts` as `cursor` breaks
18
+ * against the live API even though it round-trips through test fetchers.
19
+ */
20
+ oldest?: string;
21
+ }) => Promise<SlackHistoryPage>;
22
+ export interface BackfillOpts {
23
+ teamId: string;
24
+ channel: ChannelMeta;
25
+ fetcher: SlackHistoryFetcher;
26
+ /** Stop after this many messages. Default: unlimited. */
27
+ maxMessages?: number;
28
+ }
29
+ /**
30
+ * Page through `conversations.history` via the injected fetcher and ingest each
31
+ * message. The cursor is persisted to `slack_cursors` after every page so a
32
+ * crash mid-backfill resumes near where it left off.
33
+ *
34
+ * Each ingested message uses a synthesized eventId of the form
35
+ * `backfill:${teamId}:${channelId}:${ts}`. Reruns dedupe via the
36
+ * `slack_event_log` PK so calling `backfillChannel` twice is safe.
37
+ */
38
+ export declare function backfillChannel(ctx: Context, opts: BackfillOpts): Promise<{
39
+ ingested: number;
40
+ pages: number;
41
+ }>;
42
+ //# sourceMappingURL=backfill.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backfill.d.ts","sourceRoot":"","sources":["../../../src/connectors/slack/backfill.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAG5C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,MAAM,MAAM,mBAAmB,GAAG,CAAC,IAAI,EAAE;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAC;AAEhC,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,WAAW,CAAC;IACrB,OAAO,EAAE,mBAAmB,CAAC;IAC7B,yDAAyD;IACzD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AA+BD;;;;;;;;GAQG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,YAAY,GACjB,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAoC9C"}
@@ -0,0 +1,76 @@
1
+ import { openHippoDb, closeHippoDb } from '../../db.js';
2
+ import { ingestMessage } from './ingest.js';
3
+ function readCursor(root, tenantId, channelId) {
4
+ const db = openHippoDb(root);
5
+ try {
6
+ const row = db
7
+ .prepare(`SELECT latest_ts FROM slack_cursors WHERE tenant_id=? AND channel_id=?`)
8
+ .get(tenantId, channelId);
9
+ return row?.latest_ts ?? null;
10
+ }
11
+ finally {
12
+ closeHippoDb(db);
13
+ }
14
+ }
15
+ function writeCursor(root, tenantId, channelId, latestTs) {
16
+ const db = openHippoDb(root);
17
+ try {
18
+ db.prepare(`INSERT INTO slack_cursors (tenant_id, channel_id, latest_ts, updated_at) VALUES (?,?,?,?)
19
+ ON CONFLICT(tenant_id, channel_id) DO UPDATE SET latest_ts = excluded.latest_ts, updated_at = excluded.updated_at`).run(tenantId, channelId, latestTs, new Date().toISOString());
20
+ }
21
+ finally {
22
+ closeHippoDb(db);
23
+ }
24
+ }
25
+ /**
26
+ * Page through `conversations.history` via the injected fetcher and ingest each
27
+ * message. The cursor is persisted to `slack_cursors` after every page so a
28
+ * crash mid-backfill resumes near where it left off.
29
+ *
30
+ * Each ingested message uses a synthesized eventId of the form
31
+ * `backfill:${teamId}:${channelId}:${ts}`. Reruns dedupe via the
32
+ * `slack_event_log` PK so calling `backfillChannel` twice is safe.
33
+ */
34
+ export async function backfillChannel(ctx, opts) {
35
+ // Resume bound from previous run; passed as `oldest` (numeric ts) on the
36
+ // first page only. `cursor` starts null — Slack mints the next-page token
37
+ // and we feed it back. Mixing the two would feed a numeric ts as an opaque
38
+ // cursor and break against the live API on rerun.
39
+ const resumeFrom = readCursor(ctx.hippoRoot, ctx.tenantId, opts.channel.id);
40
+ let cursor = null;
41
+ let ingested = 0;
42
+ let pages = 0;
43
+ let latestTs = resumeFrom;
44
+ while (true) {
45
+ const page = await opts.fetcher({
46
+ channelId: opts.channel.id,
47
+ cursor,
48
+ oldest: pages === 0 && resumeFrom ? resumeFrom : undefined,
49
+ });
50
+ pages++;
51
+ for (const msg of page.messages) {
52
+ const r = ingestMessage(ctx, {
53
+ teamId: opts.teamId,
54
+ channel: opts.channel,
55
+ message: msg,
56
+ eventId: `backfill:${opts.teamId}:${opts.channel.id}:${msg.ts}`,
57
+ });
58
+ if (r.status === 'ingested')
59
+ ingested++;
60
+ if (!latestTs || msg.ts > latestTs)
61
+ latestTs = msg.ts;
62
+ if (opts.maxMessages && ingested >= opts.maxMessages) {
63
+ if (latestTs)
64
+ writeCursor(ctx.hippoRoot, ctx.tenantId, opts.channel.id, latestTs);
65
+ return { ingested, pages };
66
+ }
67
+ }
68
+ if (latestTs)
69
+ writeCursor(ctx.hippoRoot, ctx.tenantId, opts.channel.id, latestTs);
70
+ if (!page.next_cursor)
71
+ break;
72
+ cursor = page.next_cursor;
73
+ }
74
+ return { ingested, pages };
75
+ }
76
+ //# sourceMappingURL=backfill.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backfill.js","sourceRoot":"","sources":["../../../src/connectors/slack/backfill.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAgC5C,SAAS,UAAU,CAAC,IAAY,EAAE,QAAgB,EAAE,SAAiB;IACnE,MAAM,EAAE,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;IAC7B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,EAAE;aACX,OAAO,CAAC,wEAAwE,CAAC;aACjF,GAAG,CAAC,QAAQ,EAAE,SAAS,CAAuC,CAAC;QAClE,OAAO,GAAG,EAAE,SAAS,IAAI,IAAI,CAAC;IAChC,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,EAAE,CAAC,CAAC;IACnB,CAAC;AACH,CAAC;AAED,SAAS,WAAW,CAClB,IAAY,EACZ,QAAgB,EAChB,SAAiB,EACjB,QAAgB;IAEhB,MAAM,EAAE,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;IAC7B,IAAI,CAAC;QACH,EAAE,CAAC,OAAO,CACR;yHACmH,CACpH,CAAC,GAAG,CAAC,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;IACjE,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,EAAE,CAAC,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,GAAY,EACZ,IAAkB;IAElB,yEAAyE;IACzE,0EAA0E;IAC1E,2EAA2E;IAC3E,kDAAkD;IAClD,MAAM,UAAU,GAAkB,UAAU,CAAC,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC3F,IAAI,MAAM,GAAkB,IAAI,CAAC;IACjC,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,QAAQ,GAAkB,UAAU,CAAC;IACzC,OAAO,IAAI,EAAE,CAAC;QACZ,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC;YAC9B,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE;YAC1B,MAAM;YACN,MAAM,EAAE,KAAK,KAAK,CAAC,IAAI,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS;SAC3D,CAAC,CAAC;QACH,KAAK,EAAE,CAAC;QACR,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAChC,MAAM,CAAC,GAAG,aAAa,CAAC,GAAG,EAAE;gBAC3B,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,OAAO,EAAE,GAAG;gBACZ,OAAO,EAAE,YAAY,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,OAAO,CAAC,EAAE,IAAI,GAAG,CAAC,EAAE,EAAE;aAChE,CAAC,CAAC;YACH,IAAI,CAAC,CAAC,MAAM,KAAK,UAAU;gBAAE,QAAQ,EAAE,CAAC;YACxC,IAAI,CAAC,QAAQ,IAAI,GAAG,CAAC,EAAE,GAAG,QAAQ;gBAAE,QAAQ,GAAG,GAAG,CAAC,EAAE,CAAC;YACtD,IAAI,IAAI,CAAC,WAAW,IAAI,QAAQ,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;gBACrD,IAAI,QAAQ;oBAAE,WAAW,CAAC,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;gBAClF,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;YAC7B,CAAC;QACH,CAAC;QACD,IAAI,QAAQ;YAAE,WAAW,CAAC,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;QAClF,IAAI,CAAC,IAAI,CAAC,WAAW;YAAE,MAAM;QAC7B,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC;IAC5B,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;AAC7B,CAAC"}
@@ -0,0 +1,14 @@
1
+ import { type Context } from '../../api.js';
2
+ export interface DeletionInput {
3
+ teamId: string;
4
+ channelId: string;
5
+ deletedTs: string;
6
+ eventId: string;
7
+ }
8
+ export type DeletionStatus = 'archived' | 'not_found' | 'duplicate';
9
+ export interface DeletionResult {
10
+ status: DeletionStatus;
11
+ memoryId: string | null;
12
+ }
13
+ export declare function handleMessageDeleted(ctx: Context, input: DeletionInput): DeletionResult;
14
+ //# sourceMappingURL=deletion.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deletion.d.ts","sourceRoot":"","sources":["../../../src/connectors/slack/deletion.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,OAAO,EAAE,MAAM,cAAc,CAAC;AAIxD,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,WAAW,GAAG,WAAW,CAAC;AAEpE,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,cAAc,CAAC;IACvB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,aAAa,GAAG,cAAc,CAgCvF"}
@@ -0,0 +1,46 @@
1
+ import { archiveRaw } from '../../api.js';
2
+ import { openHippoDb, closeHippoDb } from '../../db.js';
3
+ import { markEventSeen, hasSeenEvent } from './idempotency.js';
4
+ export function handleMessageDeleted(ctx, input) {
5
+ const db = openHippoDb(ctx.hippoRoot);
6
+ let memoryId = null;
7
+ try {
8
+ if (hasSeenEvent(db, input.eventId)) {
9
+ return { status: 'duplicate', memoryId: null };
10
+ }
11
+ const ref = `slack://${input.teamId}/${input.channelId}/${input.deletedTs}`;
12
+ // Tenant scope is load-bearing: without `tenant_id = ?` a deletion event
13
+ // from tenant A could archive tenant B's raw row sharing the same
14
+ // artifact_ref. The `kind = 'raw'` filter prevents accidentally targeting
15
+ // distilled rows.
16
+ const row = db
17
+ .prepare(`SELECT id FROM memories WHERE artifact_ref = ? AND tenant_id = ? AND kind = 'raw'`)
18
+ .get(ref, ctx.tenantId);
19
+ memoryId = row?.id ?? null;
20
+ }
21
+ finally {
22
+ closeHippoDb(db);
23
+ }
24
+ if (!memoryId) {
25
+ const db2 = openHippoDb(ctx.hippoRoot);
26
+ try {
27
+ markEventSeen(db2, input.eventId, null);
28
+ }
29
+ finally {
30
+ closeHippoDb(db2);
31
+ }
32
+ return { status: 'not_found', memoryId: null };
33
+ }
34
+ // api.archiveRaw now handles legacy mirror cleanup centrally so every caller
35
+ // (CLI, REST route, MCP tool, this connector) gets the GDPR-correct archive.
36
+ archiveRaw(ctx, memoryId, `source_deleted:slack:${input.teamId}:${input.channelId}:${input.deletedTs}`);
37
+ const db3 = openHippoDb(ctx.hippoRoot);
38
+ try {
39
+ markEventSeen(db3, input.eventId, memoryId);
40
+ }
41
+ finally {
42
+ closeHippoDb(db3);
43
+ }
44
+ return { status: 'archived', memoryId };
45
+ }
46
+ //# sourceMappingURL=deletion.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deletion.js","sourceRoot":"","sources":["../../../src/connectors/slack/deletion.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAgB,MAAM,cAAc,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAgB/D,MAAM,UAAU,oBAAoB,CAAC,GAAY,EAAE,KAAoB;IACrE,MAAM,EAAE,GAAG,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IACtC,IAAI,QAAQ,GAAkB,IAAI,CAAC;IACnC,IAAI,CAAC;QACH,IAAI,YAAY,CAAC,EAAE,EAAE,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;YACpC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;QACjD,CAAC;QACD,MAAM,GAAG,GAAG,WAAW,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,SAAS,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;QAC5E,yEAAyE;QACzE,kEAAkE;QAClE,0EAA0E;QAC1E,kBAAkB;QAClB,MAAM,GAAG,GAAG,EAAE;aACX,OAAO,CAAC,mFAAmF,CAAC;aAC5F,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,QAAQ,CAAgC,CAAC;QACzD,QAAQ,GAAG,GAAG,EAAE,EAAE,IAAI,IAAI,CAAC;IAC7B,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,EAAE,CAAC,CAAC;IACnB,CAAC;IACD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACvC,IAAI,CAAC;YAAC,aAAa,CAAC,GAAG,EAAE,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QAAC,CAAC;gBACxC,CAAC;YAAC,YAAY,CAAC,GAAG,CAAC,CAAC;QAAC,CAAC;QAC9B,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACjD,CAAC;IACD,6EAA6E;IAC7E,6EAA6E;IAC7E,UAAU,CAAC,GAAG,EAAE,QAAQ,EAAE,wBAAwB,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,SAAS,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC;IACxG,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IACvC,IAAI,CAAC;QAAC,aAAa,CAAC,GAAG,EAAE,KAAK,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IAAC,CAAC;YAC5C,CAAC;QAAC,YAAY,CAAC,GAAG,CAAC,CAAC;IAAC,CAAC;IAC9B,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC;AAC1C,CAAC"}
@@ -0,0 +1,21 @@
1
+ import type { DatabaseSyncLike } from '../../db.js';
2
+ export interface DlqItem {
3
+ id: number;
4
+ tenantId: string;
5
+ rawPayload: string;
6
+ error: string;
7
+ receivedAt: string;
8
+ retriedAt: string | null;
9
+ }
10
+ export interface WriteDlqOpts {
11
+ tenantId: string;
12
+ rawPayload: string;
13
+ error: string;
14
+ }
15
+ export declare function writeToDlq(db: DatabaseSyncLike, opts: WriteDlqOpts): number;
16
+ export declare function listDlq(db: DatabaseSyncLike, opts: {
17
+ tenantId: string;
18
+ limit?: number;
19
+ }): DlqItem[];
20
+ export declare function markDlqRetried(db: DatabaseSyncLike, id: number): void;
21
+ //# sourceMappingURL=dlq.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dlq.d.ts","sourceRoot":"","sources":["../../../src/connectors/slack/dlq.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,gBAAgB,EAAE,IAAI,EAAE,YAAY,GAAG,MAAM,CAK3E;AAED,wBAAgB,OAAO,CAAC,EAAE,EAAE,gBAAgB,EAAE,IAAI,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,EAAE,CAYnG;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,gBAAgB,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAErE"}
@@ -0,0 +1,23 @@
1
+ export function writeToDlq(db, opts) {
2
+ const result = db
3
+ .prepare(`INSERT INTO slack_dlq (tenant_id, raw_payload, error, received_at) VALUES (?, ?, ?, ?)`)
4
+ .run(opts.tenantId, opts.rawPayload, opts.error, new Date().toISOString());
5
+ return Number(result.lastInsertRowid);
6
+ }
7
+ export function listDlq(db, opts) {
8
+ const rows = db
9
+ .prepare(`SELECT id, tenant_id, raw_payload, error, received_at, retried_at FROM slack_dlq WHERE tenant_id = ? ORDER BY received_at ASC LIMIT ?`)
10
+ .all(opts.tenantId, opts.limit ?? 100);
11
+ return rows.map((r) => ({
12
+ id: Number(r.id),
13
+ tenantId: String(r.tenant_id),
14
+ rawPayload: String(r.raw_payload),
15
+ error: String(r.error),
16
+ receivedAt: String(r.received_at),
17
+ retriedAt: r.retried_at == null ? null : String(r.retried_at),
18
+ }));
19
+ }
20
+ export function markDlqRetried(db, id) {
21
+ db.prepare(`UPDATE slack_dlq SET retried_at = ? WHERE id = ?`).run(new Date().toISOString(), id);
22
+ }
23
+ //# sourceMappingURL=dlq.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dlq.js","sourceRoot":"","sources":["../../../src/connectors/slack/dlq.ts"],"names":[],"mappings":"AAiBA,MAAM,UAAU,UAAU,CAAC,EAAoB,EAAE,IAAkB;IACjE,MAAM,MAAM,GAAG,EAAE;SACd,OAAO,CAAC,wFAAwF,CAAC;SACjG,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;IAC7E,OAAO,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;AACxC,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,EAAoB,EAAE,IAA0C;IACtF,MAAM,IAAI,GAAG,EAAE;SACZ,OAAO,CAAC,uIAAuI,CAAC;SAChJ,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,KAAK,IAAI,GAAG,CAAmC,CAAC;IAC3E,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACtB,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;QAC7B,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC;QACjC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC;QACtB,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC;QACjC,SAAS,EAAE,CAAC,CAAC,UAAU,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC;KAC9D,CAAC,CAAC,CAAC;AACN,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,EAAoB,EAAE,EAAU;IAC7D,EAAE,CAAC,OAAO,CAAC,kDAAkD,CAAC,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,EAAE,CAAC,CAAC;AACnG,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { DatabaseSyncLike } from '../../db.js';
2
+ export declare function hasSeenEvent(db: DatabaseSyncLike, eventId: string): boolean;
3
+ export declare function markEventSeen(db: DatabaseSyncLike, eventId: string, memoryId: string | null): void;
4
+ export declare function lookupMemoryByEvent(db: DatabaseSyncLike, eventId: string): string | null;
5
+ //# sourceMappingURL=idempotency.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"idempotency.d.ts","sourceRoot":"","sources":["../../../src/connectors/slack/idempotency.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,wBAAgB,YAAY,CAAC,EAAE,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAG3E;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAGlG;AAED,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAKxF"}
@@ -0,0 +1,13 @@
1
+ export function hasSeenEvent(db, eventId) {
2
+ const row = db.prepare(`SELECT 1 FROM slack_event_log WHERE event_id = ?`).get(eventId);
3
+ return !!row;
4
+ }
5
+ export function markEventSeen(db, eventId, memoryId) {
6
+ db.prepare(`INSERT OR IGNORE INTO slack_event_log (event_id, ingested_at, memory_id) VALUES (?, ?, ?)`)
7
+ .run(eventId, new Date().toISOString(), memoryId);
8
+ }
9
+ export function lookupMemoryByEvent(db, eventId) {
10
+ const row = db.prepare(`SELECT memory_id FROM slack_event_log WHERE event_id = ?`).get(eventId);
11
+ return row?.memory_id ?? null;
12
+ }
13
+ //# sourceMappingURL=idempotency.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"idempotency.js","sourceRoot":"","sources":["../../../src/connectors/slack/idempotency.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,YAAY,CAAC,EAAoB,EAAE,OAAe;IAChE,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,kDAAkD,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACxF,OAAO,CAAC,CAAC,GAAG,CAAC;AACf,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,EAAoB,EAAE,OAAe,EAAE,QAAuB;IAC1F,EAAE,CAAC,OAAO,CAAC,2FAA2F,CAAC;SACpG,GAAG,CAAC,OAAO,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,QAAQ,CAAC,CAAC;AACtD,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,EAAoB,EAAE,OAAe;IACvE,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,0DAA0D,CAAC,CAAC,GAAG,CAAC,OAAO,CAEjF,CAAC;IACd,OAAO,GAAG,EAAE,SAAS,IAAI,IAAI,CAAC;AAChC,CAAC"}
@@ -0,0 +1,27 @@
1
+ import { type Context } from '../../api.js';
2
+ import type { ChannelMeta } from './scope.js';
3
+ import type { SlackMessageEvent } from './types.js';
4
+ export interface IngestInput {
5
+ teamId: string;
6
+ channel: ChannelMeta;
7
+ message: SlackMessageEvent;
8
+ /** Slack event_id for the envelope (or for backfill, a synthesized stable id). */
9
+ eventId: string;
10
+ }
11
+ export type IngestStatus = 'ingested' | 'duplicate' | 'skipped';
12
+ export interface IngestResult {
13
+ status: IngestStatus;
14
+ memoryId: string | null;
15
+ }
16
+ /**
17
+ * Ingest a Slack message into hippo as a kind='raw' memory.
18
+ *
19
+ * - Idempotency-checked via slack_event_log (Slack retries within 1 minute).
20
+ * - The memory write and the slack_event_log mark commit atomically through
21
+ * `api.remember`'s `afterWrite` hook — a crash between the two cannot
22
+ * produce a duplicate on the next retry.
23
+ * - Empty-body messages return 'skipped' but still mark seen so a replay
24
+ * returns 'duplicate' rather than re-running the transform.
25
+ */
26
+ export declare function ingestMessage(ctx: Context, input: IngestInput): IngestResult;
27
+ //# sourceMappingURL=ingest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ingest.d.ts","sourceRoot":"","sources":["../../../src/connectors/slack/ingest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,OAAO,EAAE,MAAM,cAAc,CAAC;AAItD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEpD,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,WAAW,CAAC;IACrB,OAAO,EAAE,iBAAiB,CAAC;IAC3B,kFAAkF;IAClF,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,WAAW,GAAG,SAAS,CAAC;AAEhE,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,YAAY,CAAC;IACrB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,GAAG,YAAY,CAmC5E"}
@@ -0,0 +1,48 @@
1
+ import { remember } from '../../api.js';
2
+ import { openHippoDb, closeHippoDb } from '../../db.js';
3
+ import { hasSeenEvent, markEventSeen, lookupMemoryByEvent } from './idempotency.js';
4
+ import { messageToRememberOpts } from './transform.js';
5
+ /**
6
+ * Ingest a Slack message into hippo as a kind='raw' memory.
7
+ *
8
+ * - Idempotency-checked via slack_event_log (Slack retries within 1 minute).
9
+ * - The memory write and the slack_event_log mark commit atomically through
10
+ * `api.remember`'s `afterWrite` hook — a crash between the two cannot
11
+ * produce a duplicate on the next retry.
12
+ * - Empty-body messages return 'skipped' but still mark seen so a replay
13
+ * returns 'duplicate' rather than re-running the transform.
14
+ */
15
+ export function ingestMessage(ctx, input) {
16
+ // Idempotency check: if already seen, return the cached memory_id without
17
+ // re-running the transform or hitting api.remember.
18
+ const db = openHippoDb(ctx.hippoRoot);
19
+ try {
20
+ if (hasSeenEvent(db, input.eventId)) {
21
+ return { status: 'duplicate', memoryId: lookupMemoryByEvent(db, input.eventId) };
22
+ }
23
+ }
24
+ finally {
25
+ closeHippoDb(db);
26
+ }
27
+ const opts = messageToRememberOpts(input);
28
+ if (!opts) {
29
+ const db2 = openHippoDb(ctx.hippoRoot);
30
+ try {
31
+ markEventSeen(db2, input.eventId, null);
32
+ }
33
+ finally {
34
+ closeHippoDb(db2);
35
+ }
36
+ return { status: 'skipped', memoryId: null };
37
+ }
38
+ // Atomic write: the afterWrite callback runs inside writeEntry's SAVEPOINT,
39
+ // so the memory row and the slack_event_log row commit (or roll back)
40
+ // together. Slack's 1-minute retry window can no longer produce a duplicate
41
+ // via the crash-between-handles race.
42
+ const result = remember({ ...ctx, actor: ctx.actor || 'connector:slack' }, {
43
+ ...opts,
44
+ afterWrite: (db, memoryId) => markEventSeen(db, input.eventId, memoryId),
45
+ });
46
+ return { status: 'ingested', memoryId: result.id };
47
+ }
48
+ //# sourceMappingURL=ingest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ingest.js","sourceRoot":"","sources":["../../../src/connectors/slack/ingest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAgB,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACpF,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAmBvD;;;;;;;;;GASG;AACH,MAAM,UAAU,aAAa,CAAC,GAAY,EAAE,KAAkB;IAC5D,0EAA0E;IAC1E,oDAAoD;IACpD,MAAM,EAAE,GAAG,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IACtC,IAAI,CAAC;QACH,IAAI,YAAY,CAAC,EAAE,EAAE,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;YACpC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,mBAAmB,CAAC,EAAE,EAAE,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QACnF,CAAC;IACH,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,EAAE,CAAC,CAAC;IACnB,CAAC;IAED,MAAM,IAAI,GAAG,qBAAqB,CAAC,KAAK,CAAC,CAAC;IAC1C,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACvC,IAAI,CAAC;YACH,aAAa,CAAC,GAAG,EAAE,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QAC1C,CAAC;gBAAS,CAAC;YACT,YAAY,CAAC,GAAG,CAAC,CAAC;QACpB,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC/C,CAAC;IAED,4EAA4E;IAC5E,sEAAsE;IACtE,4EAA4E;IAC5E,sCAAsC;IACtC,MAAM,MAAM,GAAG,QAAQ,CACrB,EAAE,GAAG,GAAG,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,IAAI,iBAAiB,EAAE,EACjD;QACE,GAAG,IAAI;QACP,UAAU,EAAE,CAAC,EAAE,EAAE,QAAQ,EAAE,EAAE,CAAC,aAAa,CAAC,EAAE,EAAE,KAAK,CAAC,OAAO,EAAE,QAAQ,CAAC;KACzE,CACF,CAAC;IACF,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC;AACrD,CAAC"}
@@ -0,0 +1,9 @@
1
+ export interface RetryOpts {
2
+ url: string;
3
+ init?: RequestInit;
4
+ fetchImpl?: typeof fetch;
5
+ sleep?: (ms: number) => Promise<void>;
6
+ maxRetries?: number;
7
+ }
8
+ export declare function fetchWithRetry(opts: RetryOpts): Promise<Response>;
9
+ //# sourceMappingURL=ratelimit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ratelimit.d.ts","sourceRoot":"","sources":["../../../src/connectors/slack/ratelimit.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAsB,cAAc,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,QAAQ,CAAC,CAcvE"}
@@ -0,0 +1,18 @@
1
+ export async function fetchWithRetry(opts) {
2
+ const f = opts.fetchImpl ?? fetch;
3
+ const sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
4
+ const max = opts.maxRetries ?? 3;
5
+ let attempt = 0;
6
+ while (true) {
7
+ const r = await f(opts.url, opts.init);
8
+ if (r.status !== 429)
9
+ return r;
10
+ if (attempt >= max)
11
+ throw new Error(`rate-limited after ${attempt + 1} attempts: ${opts.url}`);
12
+ const ra = r.headers.get('retry-after');
13
+ const delaySec = ra ? Number(ra) : Math.pow(2, attempt);
14
+ await sleep(Math.max(0, delaySec) * 1000);
15
+ attempt++;
16
+ }
17
+ }
18
+ //# sourceMappingURL=ratelimit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ratelimit.js","sourceRoot":"","sources":["../../../src/connectors/slack/ratelimit.ts"],"names":[],"mappings":"AAQA,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,IAAe;IAClD,MAAM,CAAC,GAAG,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC;IAClC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC,EAAU,EAAE,EAAE,CAAC,IAAI,OAAO,CAAO,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IAC1F,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,IAAI,CAAC,CAAC;IACjC,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,OAAO,IAAI,EAAE,CAAC;QACZ,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QACvC,IAAI,CAAC,CAAC,MAAM,KAAK,GAAG;YAAE,OAAO,CAAC,CAAC;QAC/B,IAAI,OAAO,IAAI,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,sBAAsB,OAAO,GAAG,CAAC,cAAc,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QAC/F,MAAM,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;QACxC,MAAM,QAAQ,GAAG,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;QACxD,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,GAAG,IAAI,CAAC,CAAC;QAC1C,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC"}
@@ -0,0 +1,16 @@
1
+ export interface ChannelMeta {
2
+ id: string;
3
+ is_private?: boolean;
4
+ is_im?: boolean;
5
+ is_mpim?: boolean;
6
+ }
7
+ /**
8
+ * Map a Slack channel into a hippo scope string.
9
+ *
10
+ * Default to private when privacy is undetermined. The cost of leaking a
11
+ * public channel into private scope (recall returns nothing) is far smaller
12
+ * than the cost of leaking a private channel into public scope (data exposed
13
+ * to a tenant that should not see it).
14
+ */
15
+ export declare function scopeFromChannel(ch: ChannelMeta): string;
16
+ //# sourceMappingURL=scope.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scope.d.ts","sourceRoot":"","sources":["../../../src/connectors/slack/scope.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,WAAW,GAAG,MAAM,CAGxD"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Map a Slack channel into a hippo scope string.
3
+ *
4
+ * Default to private when privacy is undetermined. The cost of leaking a
5
+ * public channel into private scope (recall returns nothing) is far smaller
6
+ * than the cost of leaking a private channel into public scope (data exposed
7
+ * to a tenant that should not see it).
8
+ */
9
+ export function scopeFromChannel(ch) {
10
+ const isPublic = ch.is_private === false && !ch.is_im && !ch.is_mpim;
11
+ return isPublic ? `slack:public:${ch.id}` : `slack:private:${ch.id}`;
12
+ }
13
+ //# sourceMappingURL=scope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scope.js","sourceRoot":"","sources":["../../../src/connectors/slack/scope.ts"],"names":[],"mappings":"AAOA;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAAC,EAAe;IAC9C,MAAM,QAAQ,GAAG,EAAE,CAAC,UAAU,KAAK,KAAK,IAAI,CAAC,EAAE,CAAC,KAAK,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;IACrE,OAAO,QAAQ,CAAC,CAAC,CAAC,gBAAgB,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,iBAAiB,EAAE,CAAC,EAAE,EAAE,CAAC;AACvE,CAAC"}
@@ -0,0 +1,12 @@
1
+ export interface VerifyOpts {
2
+ rawBody: string;
3
+ timestamp: string;
4
+ signature: string;
5
+ signingSecret: string;
6
+ /** Current unix seconds. Injectable for tests. */
7
+ now?: number;
8
+ /** Max allowed skew in seconds. Default 5 minutes (Slack's recommendation). */
9
+ skewSeconds?: number;
10
+ }
11
+ export declare function verifySlackSignature(opts: VerifyOpts): boolean;
12
+ //# sourceMappingURL=signature.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signature.d.ts","sourceRoot":"","sources":["../../../src/connectors/slack/signature.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,kDAAkD;IAClD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,+EAA+E;IAC/E,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAa9D"}
@@ -0,0 +1,20 @@
1
+ import { createHmac, timingSafeEqual } from 'crypto';
2
+ export function verifySlackSignature(opts) {
3
+ const { rawBody, timestamp, signature, signingSecret } = opts;
4
+ const now = opts.now ?? Math.floor(Date.now() / 1000);
5
+ const skew = opts.skewSeconds ?? 5 * 60;
6
+ const ts = Number(timestamp);
7
+ if (!Number.isFinite(ts))
8
+ return false;
9
+ if (Math.abs(now - ts) > skew)
10
+ return false;
11
+ if (!signature.startsWith('v0='))
12
+ return false;
13
+ const expected = `v0=${createHmac('sha256', signingSecret).update(`v0:${timestamp}:${rawBody}`).digest('hex')}`;
14
+ const a = Buffer.from(signature, 'utf8');
15
+ const b = Buffer.from(expected, 'utf8');
16
+ if (a.length !== b.length)
17
+ return false;
18
+ return timingSafeEqual(a, b);
19
+ }
20
+ //# sourceMappingURL=signature.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signature.js","sourceRoot":"","sources":["../../../src/connectors/slack/signature.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AAarD,MAAM,UAAU,oBAAoB,CAAC,IAAgB;IACnD,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,aAAa,EAAE,GAAG,IAAI,CAAC;IAC9D,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IACtD,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,IAAI,CAAC,GAAG,EAAE,CAAC;IACxC,MAAM,EAAE,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;IAC7B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;QAAE,OAAO,KAAK,CAAC;IACvC,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,IAAI;QAAE,OAAO,KAAK,CAAC;IAC5C,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC/C,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC,MAAM,CAAC,MAAM,SAAS,IAAI,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;IAChH,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IACzC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACxC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IACxC,OAAO,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAC/B,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { DatabaseSyncLike } from '../../db.js';
2
+ /**
3
+ * Look up the tenant_id for a Slack team_id. Returns null when no row exists,
4
+ * which signals "use the deployment's HIPPO_TENANT fallback". Multi-workspace
5
+ * deployments populate slack_workspaces; single-workspace deployments leave
6
+ * the table empty and rely on the env fallback.
7
+ *
8
+ * Review patch #6: dedicated routing seam — never inline this lookup at the
9
+ * route handler so a future schema change (e.g. installation tokens) lands
10
+ * in one place.
11
+ */
12
+ export declare function resolveTenantForTeam(db: DatabaseSyncLike, teamId: string): string | null;
13
+ //# sourceMappingURL=tenant-routing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tenant-routing.d.ts","sourceRoot":"","sources":["../../../src/connectors/slack/tenant-routing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAKxF"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Look up the tenant_id for a Slack team_id. Returns null when no row exists,
3
+ * which signals "use the deployment's HIPPO_TENANT fallback". Multi-workspace
4
+ * deployments populate slack_workspaces; single-workspace deployments leave
5
+ * the table empty and rely on the env fallback.
6
+ *
7
+ * Review patch #6: dedicated routing seam — never inline this lookup at the
8
+ * route handler so a future schema change (e.g. installation tokens) lands
9
+ * in one place.
10
+ */
11
+ export function resolveTenantForTeam(db, teamId) {
12
+ const row = db
13
+ .prepare(`SELECT tenant_id FROM slack_workspaces WHERE team_id = ?`)
14
+ .get(teamId);
15
+ return row?.tenant_id ?? null;
16
+ }
17
+ //# sourceMappingURL=tenant-routing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tenant-routing.js","sourceRoot":"","sources":["../../../src/connectors/slack/tenant-routing.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,MAAM,UAAU,oBAAoB,CAAC,EAAoB,EAAE,MAAc;IACvE,MAAM,GAAG,GAAG,EAAE;SACX,OAAO,CAAC,0DAA0D,CAAC;SACnE,GAAG,CAAC,MAAM,CAAuC,CAAC;IACrD,OAAO,GAAG,EAAE,SAAS,IAAI,IAAI,CAAC;AAChC,CAAC"}
@@ -0,0 +1,20 @@
1
+ import type { RememberOpts } from '../../api.js';
2
+ import { type ChannelMeta } from './scope.js';
3
+ import type { SlackMessageEvent } from './types.js';
4
+ export interface TransformInput {
5
+ teamId: string;
6
+ channel: ChannelMeta;
7
+ message: SlackMessageEvent;
8
+ }
9
+ /**
10
+ * Convert a SlackMessageEvent into RememberOpts for api.remember(). Returns
11
+ * null when the message has no usable body (e.g. system events, bot reactions
12
+ * without text). Caller treats null as "skip but mark idempotency seen".
13
+ *
14
+ * Contract:
15
+ * - kind is the literal 'raw' (E1.x connector boundary, see src/importers.ts).
16
+ * - artifact_ref format MUST be exactly `slack://${teamId}/${channelId}/${ts}`;
17
+ * the deletion path (Task 9) looks up by this string.
18
+ */
19
+ export declare function messageToRememberOpts(input: TransformInput): RememberOpts | null;
20
+ //# sourceMappingURL=transform.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../../../src/connectors/slack/transform.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAoB,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAChE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEpD,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,WAAW,CAAC;IACrB,OAAO,EAAE,iBAAiB,CAAC;CAC5B;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,cAAc,GAAG,YAAY,GAAG,IAAI,CAiBhF"}
@@ -0,0 +1,31 @@
1
+ import { scopeFromChannel } from './scope.js';
2
+ /**
3
+ * Convert a SlackMessageEvent into RememberOpts for api.remember(). Returns
4
+ * null when the message has no usable body (e.g. system events, bot reactions
5
+ * without text). Caller treats null as "skip but mark idempotency seen".
6
+ *
7
+ * Contract:
8
+ * - kind is the literal 'raw' (E1.x connector boundary, see src/importers.ts).
9
+ * - artifact_ref format MUST be exactly `slack://${teamId}/${channelId}/${ts}`;
10
+ * the deletion path (Task 9) looks up by this string.
11
+ */
12
+ export function messageToRememberOpts(input) {
13
+ const text = input.message.text?.trim();
14
+ if (!text)
15
+ return null;
16
+ const artifactRef = `slack://${input.teamId}/${input.channel.id}/${input.message.ts}`;
17
+ const tags = [
18
+ 'source:slack',
19
+ `channel:${input.channel.id}`,
20
+ ...(input.message.user ? [`user:${input.message.user}`] : []),
21
+ ...(input.message.thread_ts ? [`thread:${input.message.thread_ts}`] : []),
22
+ ];
23
+ return {
24
+ content: text,
25
+ kind: 'raw',
26
+ scope: scopeFromChannel(input.channel),
27
+ artifactRef,
28
+ tags,
29
+ };
30
+ }
31
+ //# sourceMappingURL=transform.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transform.js","sourceRoot":"","sources":["../../../src/connectors/slack/transform.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAoB,MAAM,YAAY,CAAC;AAShE;;;;;;;;;GASG;AACH,MAAM,UAAU,qBAAqB,CAAC,KAAqB;IACzD,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC;IACxC,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,MAAM,WAAW,GAAG,WAAW,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,EAAE,IAAI,KAAK,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC;IACtF,MAAM,IAAI,GAAG;QACX,cAAc;QACd,WAAW,KAAK,CAAC,OAAO,CAAC,EAAE,EAAE;QAC7B,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7D,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,UAAU,KAAK,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;KAC1E,CAAC;IACF,OAAO;QACL,OAAO,EAAE,IAAI;QACb,IAAI,EAAE,KAAK;QACX,KAAK,EAAE,gBAAgB,CAAC,KAAK,CAAC,OAAO,CAAC;QACtC,WAAW;QACX,IAAI;KACL,CAAC;AACJ,CAAC"}