@open-everywhere/provider-notion 0.1.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 (186) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +21 -0
  3. package/README.md +48 -0
  4. package/agent/channels/notion-acting-identity.ts +88 -0
  5. package/agent/channels/notion-ancestry-trust.ts +458 -0
  6. package/agent/channels/notion-api.ts +1290 -0
  7. package/agent/channels/notion-bot-user.ts +250 -0
  8. package/agent/channels/notion-catch-up.ts +339 -0
  9. package/agent/channels/notion-credentials.ts +417 -0
  10. package/agent/channels/notion-engagement.ts +372 -0
  11. package/agent/channels/notion-event-normalizer.ts +587 -0
  12. package/agent/channels/notion-identity.ts +271 -0
  13. package/agent/channels/notion-ingress-drain.ts +289 -0
  14. package/agent/channels/notion-ingress.ts +182 -0
  15. package/agent/channels/notion-message-ops.ts +140 -0
  16. package/agent/channels/notion-origin-codec.ts +38 -0
  17. package/agent/channels/notion-post-arm.ts +189 -0
  18. package/agent/channels/notion-question-return.ts +223 -0
  19. package/agent/channels/notion-read.ts +283 -0
  20. package/agent/channels/notion-scope.ts +560 -0
  21. package/agent/channels/notion-stop.ts +169 -0
  22. package/agent/channels/notion-supervisor.ts +856 -0
  23. package/agent/channels/notion-webhook-timing.ts +131 -0
  24. package/agent/channels/notion-webhook-verifier.ts +55 -0
  25. package/agent/channels/notion-webhook.ts +963 -0
  26. package/agent/channels/notion-work-search.ts +74 -0
  27. package/agent/channels/notion-work.ts +144 -0
  28. package/agent/channels/notion.ts +1242 -0
  29. package/agent/contributions.ts +39 -0
  30. package/agent/docs-generated.ts +15 -0
  31. package/agent/memory/blocks.ts +295 -0
  32. package/agent/memory/memory.ts +622 -0
  33. package/agent/provider-events.ts +198 -0
  34. package/agent/register.ts +144 -0
  35. package/agent/schedules/notion-ingress.ts +20 -0
  36. package/agent/surface-parity.ts +105 -0
  37. package/agent/tools/notion_search.ts +110 -0
  38. package/agent/tools/read_notion_comments.ts +250 -0
  39. package/agent/tools/read_notion_page.ts +351 -0
  40. package/dist/agent/channels/notion-acting-identity.d.ts +23 -0
  41. package/dist/agent/channels/notion-acting-identity.d.ts.map +1 -0
  42. package/dist/agent/channels/notion-acting-identity.js +85 -0
  43. package/dist/agent/channels/notion-acting-identity.js.map +1 -0
  44. package/dist/agent/channels/notion-ancestry-trust.d.ts +208 -0
  45. package/dist/agent/channels/notion-ancestry-trust.d.ts.map +1 -0
  46. package/dist/agent/channels/notion-ancestry-trust.js +331 -0
  47. package/dist/agent/channels/notion-ancestry-trust.js.map +1 -0
  48. package/dist/agent/channels/notion-api.d.ts +655 -0
  49. package/dist/agent/channels/notion-api.d.ts.map +1 -0
  50. package/dist/agent/channels/notion-api.js +938 -0
  51. package/dist/agent/channels/notion-api.js.map +1 -0
  52. package/dist/agent/channels/notion-bot-user.d.ts +107 -0
  53. package/dist/agent/channels/notion-bot-user.d.ts.map +1 -0
  54. package/dist/agent/channels/notion-bot-user.js +192 -0
  55. package/dist/agent/channels/notion-bot-user.js.map +1 -0
  56. package/dist/agent/channels/notion-catch-up.d.ts +64 -0
  57. package/dist/agent/channels/notion-catch-up.d.ts.map +1 -0
  58. package/dist/agent/channels/notion-catch-up.js +289 -0
  59. package/dist/agent/channels/notion-catch-up.js.map +1 -0
  60. package/dist/agent/channels/notion-credentials.d.ts +210 -0
  61. package/dist/agent/channels/notion-credentials.d.ts.map +1 -0
  62. package/dist/agent/channels/notion-credentials.js +360 -0
  63. package/dist/agent/channels/notion-credentials.js.map +1 -0
  64. package/dist/agent/channels/notion-engagement.d.ts +219 -0
  65. package/dist/agent/channels/notion-engagement.d.ts.map +1 -0
  66. package/dist/agent/channels/notion-engagement.js +283 -0
  67. package/dist/agent/channels/notion-engagement.js.map +1 -0
  68. package/dist/agent/channels/notion-event-normalizer.d.ts +194 -0
  69. package/dist/agent/channels/notion-event-normalizer.d.ts.map +1 -0
  70. package/dist/agent/channels/notion-event-normalizer.js +542 -0
  71. package/dist/agent/channels/notion-event-normalizer.js.map +1 -0
  72. package/dist/agent/channels/notion-identity.d.ts +119 -0
  73. package/dist/agent/channels/notion-identity.d.ts.map +1 -0
  74. package/dist/agent/channels/notion-identity.js +205 -0
  75. package/dist/agent/channels/notion-identity.js.map +1 -0
  76. package/dist/agent/channels/notion-ingress-drain.d.ts +66 -0
  77. package/dist/agent/channels/notion-ingress-drain.d.ts.map +1 -0
  78. package/dist/agent/channels/notion-ingress-drain.js +185 -0
  79. package/dist/agent/channels/notion-ingress-drain.js.map +1 -0
  80. package/dist/agent/channels/notion-ingress.d.ts +64 -0
  81. package/dist/agent/channels/notion-ingress.d.ts.map +1 -0
  82. package/dist/agent/channels/notion-ingress.js +130 -0
  83. package/dist/agent/channels/notion-ingress.js.map +1 -0
  84. package/dist/agent/channels/notion-message-ops.d.ts +3 -0
  85. package/dist/agent/channels/notion-message-ops.d.ts.map +1 -0
  86. package/dist/agent/channels/notion-message-ops.js +126 -0
  87. package/dist/agent/channels/notion-message-ops.js.map +1 -0
  88. package/dist/agent/channels/notion-origin-codec.d.ts +7 -0
  89. package/dist/agent/channels/notion-origin-codec.d.ts.map +1 -0
  90. package/dist/agent/channels/notion-origin-codec.js +32 -0
  91. package/dist/agent/channels/notion-origin-codec.js.map +1 -0
  92. package/dist/agent/channels/notion-post-arm.d.ts +3 -0
  93. package/dist/agent/channels/notion-post-arm.d.ts.map +1 -0
  94. package/dist/agent/channels/notion-post-arm.js +185 -0
  95. package/dist/agent/channels/notion-post-arm.js.map +1 -0
  96. package/dist/agent/channels/notion-question-return.d.ts +77 -0
  97. package/dist/agent/channels/notion-question-return.d.ts.map +1 -0
  98. package/dist/agent/channels/notion-question-return.js +183 -0
  99. package/dist/agent/channels/notion-question-return.js.map +1 -0
  100. package/dist/agent/channels/notion-read.d.ts +62 -0
  101. package/dist/agent/channels/notion-read.d.ts.map +1 -0
  102. package/dist/agent/channels/notion-read.js +257 -0
  103. package/dist/agent/channels/notion-read.js.map +1 -0
  104. package/dist/agent/channels/notion-scope.d.ts +188 -0
  105. package/dist/agent/channels/notion-scope.d.ts.map +1 -0
  106. package/dist/agent/channels/notion-scope.js +433 -0
  107. package/dist/agent/channels/notion-scope.js.map +1 -0
  108. package/dist/agent/channels/notion-stop.d.ts +48 -0
  109. package/dist/agent/channels/notion-stop.d.ts.map +1 -0
  110. package/dist/agent/channels/notion-stop.js +141 -0
  111. package/dist/agent/channels/notion-stop.js.map +1 -0
  112. package/dist/agent/channels/notion-supervisor.d.ts +462 -0
  113. package/dist/agent/channels/notion-supervisor.d.ts.map +1 -0
  114. package/dist/agent/channels/notion-supervisor.js +482 -0
  115. package/dist/agent/channels/notion-supervisor.js.map +1 -0
  116. package/dist/agent/channels/notion-webhook-timing.d.ts +35 -0
  117. package/dist/agent/channels/notion-webhook-timing.d.ts.map +1 -0
  118. package/dist/agent/channels/notion-webhook-timing.js +99 -0
  119. package/dist/agent/channels/notion-webhook-timing.js.map +1 -0
  120. package/dist/agent/channels/notion-webhook-verifier.d.ts +5 -0
  121. package/dist/agent/channels/notion-webhook-verifier.d.ts.map +1 -0
  122. package/dist/agent/channels/notion-webhook-verifier.js +53 -0
  123. package/dist/agent/channels/notion-webhook-verifier.js.map +1 -0
  124. package/dist/agent/channels/notion-webhook.d.ts +242 -0
  125. package/dist/agent/channels/notion-webhook.d.ts.map +1 -0
  126. package/dist/agent/channels/notion-webhook.js +675 -0
  127. package/dist/agent/channels/notion-webhook.js.map +1 -0
  128. package/dist/agent/channels/notion-work-search.d.ts +9 -0
  129. package/dist/agent/channels/notion-work-search.d.ts.map +1 -0
  130. package/dist/agent/channels/notion-work-search.js +69 -0
  131. package/dist/agent/channels/notion-work-search.js.map +1 -0
  132. package/dist/agent/channels/notion-work.d.ts +68 -0
  133. package/dist/agent/channels/notion-work.d.ts.map +1 -0
  134. package/dist/agent/channels/notion-work.js +126 -0
  135. package/dist/agent/channels/notion-work.js.map +1 -0
  136. package/dist/agent/channels/notion.d.ts +95 -0
  137. package/dist/agent/channels/notion.d.ts.map +1 -0
  138. package/dist/agent/channels/notion.js +1081 -0
  139. package/dist/agent/channels/notion.js.map +1 -0
  140. package/dist/agent/contributions.d.ts +32 -0
  141. package/dist/agent/contributions.d.ts.map +1 -0
  142. package/dist/agent/contributions.js +37 -0
  143. package/dist/agent/contributions.js.map +1 -0
  144. package/dist/agent/docs-generated.d.ts +3 -0
  145. package/dist/agent/docs-generated.d.ts.map +1 -0
  146. package/dist/agent/docs-generated.js +13 -0
  147. package/dist/agent/docs-generated.js.map +1 -0
  148. package/dist/agent/memory/blocks.d.ts +58 -0
  149. package/dist/agent/memory/blocks.d.ts.map +1 -0
  150. package/dist/agent/memory/blocks.js +264 -0
  151. package/dist/agent/memory/blocks.js.map +1 -0
  152. package/dist/agent/memory/memory.d.ts +157 -0
  153. package/dist/agent/memory/memory.d.ts.map +1 -0
  154. package/dist/agent/memory/memory.js +471 -0
  155. package/dist/agent/memory/memory.js.map +1 -0
  156. package/dist/agent/provider-events.d.ts +3 -0
  157. package/dist/agent/provider-events.d.ts.map +1 -0
  158. package/dist/agent/provider-events.js +196 -0
  159. package/dist/agent/provider-events.js.map +1 -0
  160. package/dist/agent/register.d.ts +5 -0
  161. package/dist/agent/register.d.ts.map +1 -0
  162. package/dist/agent/register.js +128 -0
  163. package/dist/agent/register.js.map +1 -0
  164. package/dist/agent/schedules/notion-ingress.d.ts +6 -0
  165. package/dist/agent/schedules/notion-ingress.d.ts.map +1 -0
  166. package/dist/agent/schedules/notion-ingress.js +16 -0
  167. package/dist/agent/schedules/notion-ingress.js.map +1 -0
  168. package/dist/agent/surface-parity.d.ts +3 -0
  169. package/dist/agent/surface-parity.d.ts.map +1 -0
  170. package/dist/agent/surface-parity.js +99 -0
  171. package/dist/agent/surface-parity.js.map +1 -0
  172. package/dist/agent/tools/notion_search.d.ts +56 -0
  173. package/dist/agent/tools/notion_search.d.ts.map +1 -0
  174. package/dist/agent/tools/notion_search.js +100 -0
  175. package/dist/agent/tools/notion_search.js.map +1 -0
  176. package/dist/agent/tools/read_notion_comments.d.ts +57 -0
  177. package/dist/agent/tools/read_notion_comments.d.ts.map +1 -0
  178. package/dist/agent/tools/read_notion_comments.js +236 -0
  179. package/dist/agent/tools/read_notion_comments.js.map +1 -0
  180. package/dist/agent/tools/read_notion_page.d.ts +53 -0
  181. package/dist/agent/tools/read_notion_page.d.ts.map +1 -0
  182. package/dist/agent/tools/read_notion_page.js +327 -0
  183. package/dist/agent/tools/read_notion_page.js.map +1 -0
  184. package/package.json +231 -0
  185. package/tsconfig.build.json +22 -0
  186. package/tsconfig.json +15 -0
@@ -0,0 +1,131 @@
1
+ // notion-webhook-timing โ€” WHERE A NOTION WEBHOOK ACK'S SECONDS WENT (OPE-1929).
2
+ //
3
+ // Notion's comment route deliberately acknowledges only after the wake has been durably accepted.
4
+ // That makes every REST/store/queue wait on the path part of the webhook's observed latency. Keep
5
+ // this collector pure and injectable: production records one content-free sample, while a fake
6
+ // clock can prove the attribution without sleeping or calling a service.
7
+
8
+ export const NOTION_WEBHOOK_TIMING_ACTION = "notion.webhook_timing";
9
+
10
+ export type NotionWebhookTimingPhase =
11
+ | "credential"
12
+ | "botTriggerPage"
13
+ | "ancestorSiblingTrust"
14
+ | "engagementDedupe"
15
+ /** Inclusive dispatch span. The public detail subtracts the nested Eve and final-audit spans. */
16
+ | "dispatch"
17
+ | "eveSend"
18
+ | "finalAudit";
19
+
20
+ export interface NotionWebhookTimingDetail {
21
+ readonly [key: string]: unknown;
22
+ readonly outcome: string;
23
+ readonly failedPhase?: NotionWebhookTimingPhase;
24
+ readonly hmacCredentialMs?: number;
25
+ readonly botTriggerPageMs?: number;
26
+ readonly ancestorSiblingTrustMs?: number;
27
+ readonly engagementStoreDedupeMs?: number;
28
+ readonly dispatchPreflightCatchUpMs?: number;
29
+ readonly eveSendMs?: number;
30
+ readonly finalAuditMs?: number;
31
+ readonly totalMs: number;
32
+ }
33
+
34
+ export interface NotionWebhookTimingRecorder {
35
+ readonly measure: <T>(phase: NotionWebhookTimingPhase, operation: () => Promise<T>) => Promise<T>;
36
+ readonly measureSync: <T>(phase: NotionWebhookTimingPhase, operation: () => T) => T;
37
+ readonly detail: (outcome: string) => NotionWebhookTimingDetail;
38
+ }
39
+
40
+ /**
41
+ * Run one whole route request and emit exactly one final sample. The caller decides when a normal
42
+ * outcome is final; any throw before that point becomes `threw` and is rethrown unchanged. Emission
43
+ * itself is telemetry and therefore fail-soft โ€” it can never replace the HTTP result or error.
44
+ */
45
+ export async function runNotionWebhookTimed<T>(
46
+ timing: NotionWebhookTimingRecorder,
47
+ emit: (detail: NotionWebhookTimingDetail) => void,
48
+ operation: (finish: (outcome: string) => void) => Promise<T>,
49
+ ): Promise<T> {
50
+ let emitted = false;
51
+ const finish = (outcome: string): void => {
52
+ if (emitted) return;
53
+ emitted = true;
54
+ try {
55
+ emit(timing.detail(outcome));
56
+ } catch {
57
+ // Timing telemetry never changes the route's own result.
58
+ }
59
+ };
60
+ try {
61
+ return await operation(finish);
62
+ } catch (err) {
63
+ finish("threw");
64
+ throw err;
65
+ }
66
+ }
67
+
68
+ function duration(start: number, end: number): number {
69
+ const elapsed = end - start;
70
+ return Number.isFinite(elapsed) && elapsed > 0 ? elapsed : 0;
71
+ }
72
+
73
+ const millis = (elapsed: number): number => Math.round(elapsed);
74
+
75
+ /**
76
+ * Create one request-scoped recorder. Phases accumulate because some operations in a phase are
77
+ * separated by gates. `dispatch` deliberately contains `eveSend` and `finalAudit`; detail() removes
78
+ * those nested spans so the published buckets are mutually exclusive rather than double-counted.
79
+ */
80
+ export function createNotionWebhookTiming(now: () => number = () => performance.now()): NotionWebhookTimingRecorder {
81
+ const startedAt = now();
82
+ const elapsed = new Map<NotionWebhookTimingPhase, number>();
83
+ let failedPhase: NotionWebhookTimingPhase | undefined;
84
+ const add = (phase: NotionWebhookTimingPhase, start: number): void => {
85
+ elapsed.set(phase, (elapsed.get(phase) ?? 0) + duration(start, now()));
86
+ };
87
+
88
+ return {
89
+ async measure<T>(phase: NotionWebhookTimingPhase, operation: () => Promise<T>): Promise<T> {
90
+ const start = now();
91
+ try {
92
+ return await operation();
93
+ } catch (err) {
94
+ failedPhase ??= phase;
95
+ throw err;
96
+ } finally {
97
+ add(phase, start);
98
+ }
99
+ },
100
+ measureSync<T>(phase: NotionWebhookTimingPhase, operation: () => T): T {
101
+ const start = now();
102
+ try {
103
+ return operation();
104
+ } catch (err) {
105
+ failedPhase ??= phase;
106
+ throw err;
107
+ } finally {
108
+ add(phase, start);
109
+ }
110
+ },
111
+ detail(outcome: string): NotionWebhookTimingDetail {
112
+ const eveSendMs = elapsed.get("eveSend");
113
+ const finalAuditMs = elapsed.get("finalAudit");
114
+ const dispatchMs = elapsed.get("dispatch");
115
+ return {
116
+ outcome,
117
+ ...(failedPhase ? { failedPhase } : {}),
118
+ ...(elapsed.has("credential") ? { hmacCredentialMs: millis(elapsed.get("credential")!) } : {}),
119
+ ...(elapsed.has("botTriggerPage") ? { botTriggerPageMs: millis(elapsed.get("botTriggerPage")!) } : {}),
120
+ ...(elapsed.has("ancestorSiblingTrust") ? { ancestorSiblingTrustMs: millis(elapsed.get("ancestorSiblingTrust")!) } : {}),
121
+ ...(elapsed.has("engagementDedupe") ? { engagementStoreDedupeMs: millis(elapsed.get("engagementDedupe")!) } : {}),
122
+ ...(dispatchMs === undefined
123
+ ? {}
124
+ : { dispatchPreflightCatchUpMs: millis(Math.max(0, dispatchMs - (eveSendMs ?? 0) - (finalAuditMs ?? 0))) }),
125
+ ...(eveSendMs === undefined ? {} : { eveSendMs: millis(eveSendMs) }),
126
+ ...(finalAuditMs === undefined ? {} : { finalAuditMs: millis(finalAuditMs) }),
127
+ totalMs: millis(duration(startedAt, now())),
128
+ };
129
+ },
130
+ };
131
+ }
@@ -0,0 +1,55 @@
1
+ // Notion's webhook signature scheme, as a registry entry (OPE-164) โ€” what lets a GENERIC hook
2
+ // (`POST /webhooks/<id>`) declare `source: "notion"` and be verified the way Notion signs.
3
+ //
4
+ // ZERO NEW CRYPTO. The digest is this bundle's own `verifyNotionSignature` (channels/notion-api.ts),
5
+ // the same function the native notion webhook route uses โ€” one implementation of Notion's scheme in
6
+ // the tree, not two.
7
+ //
8
+ // THE SCHEME (https://developers.notion.com/reference/webhooks):
9
+ // X-Notion-Signature: sha256=<hex> HMAC-SHA256 of the RAW body, keyed by the ONE-TIME
10
+ // `verification_token` Notion hands over during subscription setup.
11
+ // Notion, verbatim, on why the raw bytes matter: *"Pass the raw request body exactly as it arrived
12
+ // (re-serialized JSON produces different bytes and fails verification)"*.
13
+ //
14
+ // ๐Ÿ”ด `signedTimestamp: false` โ€” nothing in the signature binds freshness. Notion's payloads carry a
15
+ // timestamp FIELD, but it is not covered in a way that bounds replay, so replay against a notion
16
+ // hook is undefended and is declared as such on the reviewed allowlist.
17
+ //
18
+ // ROTATION: Notion documents no overlap window โ€” *"If you ever rotate or recreate your webhook
19
+ // subscription, be sure to update your stored verification_token"* โ€” so the `keys[]` list is how an
20
+ // operator gets one on our side (new token at index 0, outgoing at index 1 until the switch).
21
+
22
+ import { verifyNotionSignature } from "./notion-api.js";
23
+ import type { WebhookVerifier } from "@open-everywhere/core/agent/adapter-api.js";
24
+
25
+ /** The registry key โ€” what a generic hook declares as its `source` to be verified as Notion. */
26
+ export const NOTION_WEBHOOK_SOURCE = "notion";
27
+
28
+ const NOTION_SIGNATURE_HEADER = "x-notion-signature";
29
+
30
+ export const notionWebhookVerifier: WebhookVerifier = {
31
+ source: NOTION_WEBHOOK_SOURCE,
32
+ provider: "notion",
33
+ keyKind: "shared-secret",
34
+ // ๐Ÿ”ด `keyOrigin: "vendor-issued"` (OPE-186) โ€” the key is the `verification_token` NOTION mints and
35
+ // posts to us once during subscription setup. We cannot choose it, so a secret WE minted can never
36
+ // verify a Notion delivery. This is what keeps `notion` OUT of the chat-mintable source vocabulary
37
+ // (`webhooks/source-vocabulary.ts` โ†’ `acceptsMintedSecret`). ๐Ÿ”ด THAT CONDITIONAL IS NO LONGER
38
+ // HYPOTHETICAL: OPE-189 added `notion-event-normalizer.ts`, so `notion` now has BOTH halves the
39
+ // vocabulary derivation looks for and `keyOrigin` is the ONLY thing excluding it. The exclusion is
40
+ // pinned live rather than asserted here โ€” `scripts/webhook-source-vocabulary-live-test.ts` ยง6a
41
+ // fails any mintable source whose normalizer declares no `deliveryIdHeader`, and Notion's declares
42
+ // none (its delivery id is in the body). Flip this field and that test goes red. Only an
43
+ // OPERATOR-DECLARED hook (`loadWebhooks`, whose secret ref the operator points at the stored token)
44
+ // can use this verifier.
45
+ keyOrigin: "vendor-issued",
46
+ signedTimestamp: false,
47
+ verify({ rawBody, headers, keys }) {
48
+ const signature = headers.get(NOTION_SIGNATURE_HEADER);
49
+ if (!signature) return { ok: false, failure: "missing-signature" };
50
+ for (const [i, key] of keys.entries()) {
51
+ if (verifyNotionSignature(key, rawBody, signature)) return { ok: true, keyIndex: i };
52
+ }
53
+ return { ok: false, failure: "signature-mismatch" };
54
+ },
55
+ };