@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,327 @@
1
+ // The doc-body read tool for the ambient Notion surface (notion-ambient-surface-design.md
2
+ // Step 0). The ambient supervisor judges + replies off the COMMENT context (a comment usually
3
+ // anchors to specific text); when a reply needs the doc's own prose — to answer a question about
4
+ // the page, or to catch a factual problem in it — the agent pulls the body with this tool. Body
5
+ // BY REFERENCE (chat-vs-ticketing-and-tool-uniformity.md rec #3): the doc is fetched on demand,
6
+ // NEVER inlined into the wake, so a big page can't blow the backscroll budget.
7
+ //
8
+ // SURFACE GATE: this reads a *Notion* page, so it only makes sense on a Notion session. Authored
9
+ // as a per-session DYNAMIC resolver (defineDynamic) that returns the real tool only when the
10
+ // session platform is `notion`, and `null` (the tool is absent, not merely throwing) elsewhere —
11
+ // the exact discipline access/source-scoped-mount.ts states, so a Slack/Discord agent never sees it.
12
+ //
13
+ // TWO PLANES SINCE OPE-913, and they differ in mount, fence and CREDENTIAL:
14
+ // • CHAT plane — a Notion-origin turn reads its own wake container's page on the chat credential
15
+ // (`channel-key`). Narrowed to the DELEGATED-WORKER grain by OPE-1014 — see the note below.
16
+ // • WORK plane — any session whose DOMAIN declares a Notion work credential reads that domain's
17
+ // pages, from any surface, on the WORK credential. This is what makes a Slack-woken delegated
18
+ // worker able to ground itself in the spec it was told to build from (the OPE-913 soak
19
+ // finding). A domain that declares no work credential holds no grant and sees no change.
20
+ // channels/notion-work.ts owns the grant decision; both planes fence the target page to the
21
+ // caller's own domain, and neither can read a page the domain does not own.
22
+ //
23
+ // 🔴 THE CHAT ARM IS THE DELEGATED WORKER'S (OPE-1014), AND THE OPE-988 RULING IS WHY IT IS SAFE TO
24
+ // NARROW. Tommy's ruling in channels/notion-read.ts states that PAGE CONTENT STAYS OUT OF THE CHAT
25
+ // PLANE, and it names the seat's own route in the same breath: "a seat that needs the document
26
+ // itself uses the GRANT-MOUNTED `read_notion_page` or delegates to a worker". Both of those survive
27
+ // this change untouched — the WORK arm above is grain-blind, and a worker holds the chat arm. What
28
+ // goes is the third route the ruling never named: an UNGRANTED conversational Notion seat reading
29
+ // page prose off the chat credential. That seat now delegates, exactly as the ruling says.
30
+ //
31
+ // The worker keeps the arm for now, though it is no longer the ONLY route (OPE-1064): `WORK_ROLES`
32
+ // gives Notion a CONTAINER row too (the same "team-key" role its entity row already names), so a
33
+ // work-granted domain's delegated worker also holds the generic `read_work_channel` /
34
+ // `read_work_thread` pair — access/worker-seat.ts still withholds `read_channel` / `read_thread`
35
+ // from it, but the neutral pair now covers what only this vendor arm covered before. The two are
36
+ // NOT identical: this tool returns the whole page (title + body) in one read, the generic pair
37
+ // splits root/thread the Slack way. Retiring this arm in favour of the generic pair entirely is a
38
+ // follow-up (filed on OPE-1064), not this change — it changes a shipped tool's shape and deserves
39
+ // its own review.
40
+ //
41
+ // DOMAIN FENCE: the `page` argument is a caller-supplied id, and the one deployment-wide Notion
42
+ // credential cannot tell one domain's pages from another's — so which pages this session may read is
43
+ // OURS to decide, in channels/notion-scope.ts (OPE-236). Fail closed, and every refusal is audited.
44
+ //
45
+ // Output is wrapped untrusted (toModelOutput → ingestUntrusted): a page body is written by
46
+ // arbitrary workspace members, so the model must treat it as DATA, not instructions.
47
+ import { defineDynamic, defineTool } from "eve/tools";
48
+ import { z } from "zod";
49
+ import { readNotionPage, readNotionPageIdentity, resolveBlockOwningPage, resolveNotionCredential } from "../channels/notion-api.js";
50
+ import { notionCallerCredential } from "../channels/notion-credentials.js";
51
+ import { assertNotionActingIdentity } from "../channels/notion-acting-identity.js";
52
+ import { assertNotionPageAllowed, assertNotionPageAllowedOrResolveBlock, assertNotionWorkPageAllowed } from "../channels/notion-scope.js";
53
+ import { notionWorkGrant, notionWorkMount, notionWorkspacesOf } from "../channels/notion-work.js";
54
+ import { ingestUntrustedModelOutput, assertTurnSurface, TurnOriginFenceError, auditOriginFenceRefusal, bindFrom, workerChatArmMount, getConfig, preRegisterDynamicEntry, resolvePartition, declaredWorkspaceTrust, noteAuthoredRecordRead, } from "@open-everywhere/core/agent/adapter-api.js";
55
+ const MAX_CHARS = 20_000;
56
+ const MAX_BLOCKS = 200;
57
+ // FACTORED (see access/window-gated.ts's REPLAY DOCTRINE): a top-level defineTool const, never an
58
+ // inline literal in the handler — eve's bundler transform hoists an inline one under a
59
+ // build-order-counter name that shifts across deploys. eve derives a session-scoped step id for a
60
+ // factored, unstamped entry and self-heals it on cold resume (OPE-867). Exported for the read-scope
61
+ // test, which drives execute() to prove the DOMAIN FENCE is actually
62
+ // WIRED here — a fence the tool forgets to call is worth nothing, and the module test alone would
63
+ // not notice its removal.
64
+ export const readNotionPageEntry = defineTool({
65
+ description: "Read the text of a Notion page (this doc, or another page shared with the integration). " +
66
+ "Use it when a reply needs the doc's own content — to answer a question about the page or " +
67
+ "catch a problem in it. Returns the page's top-level text (truncated if very long); the " +
68
+ "comment thread you were woken on is already in your context. It reads under the shared " +
69
+ "team integration, never under the asker's own Notion account, so it sees what the team " +
70
+ "integration was shared and not what they can see.",
71
+ inputSchema: z.object({
72
+ page: z
73
+ .string()
74
+ .min(1)
75
+ .optional()
76
+ .describe("The Notion page id to read (e.g. a UUID). Defaults to THIS doc — the page the comment you're on lives in."),
77
+ }),
78
+ outputSchema: z.object({
79
+ page: z.string(),
80
+ // OPE-1714 — the page's own title and Notion url, from a separate GET on the page object
81
+ // (the body read returns blocks alone). The model gets a name and a link it can hand a
82
+ // human instead of a bare uuid. Optional both ways: a stored output from before these
83
+ // fields existed still renders, and the identity fetch is fail-soft, so a page whose
84
+ // object could not be read renders exactly as it did before. UNTRUSTED — a workspace
85
+ // member chooses the title, and toModelOutput wraps the whole render accordingly.
86
+ title: z.string().optional(),
87
+ url: z.string().optional(),
88
+ blocks: z.number(),
89
+ truncated: z.boolean(),
90
+ text: z.string(),
91
+ // OPE-1026 — the moment the fetch below returned, stamped by execute and carried to
92
+ // toModelOutput. Optional for the same reason `read_linear_issue.ts`'s `readAt` is: a
93
+ // stored output from before this field existed still renders, with no invented read time.
94
+ readAt: z.string().optional(),
95
+ // OPE-331 — the BOUND WORKSPACE's declared tier, carried from execute (which has a ctx) to
96
+ // toModelOutput (which does not). It is the input the source-tier table's `notion` row needs, and
97
+ // the reason every row this seam produced read `assumed`. Model-INVISIBLE: an authored
98
+ // toModelOutput means the projection is what the model sees, never this object. Absent when
99
+ // the operator has not vouched the workspace — see declaredWorkspaceTrust for why silence
100
+ // stays absent rather than becoming `"public"`.
101
+ workspaceTrust: z.enum(["internal", "public"]).optional(),
102
+ }),
103
+ // NO `approval:` — a dynamic-tool approval fn only registers at session.started resolve
104
+ // time, so a resumed session's replay lookup fails and eve kills the turn (the same pin
105
+ // access/window-gated.ts documents). Read-only, so absent approval === never() is correct.
106
+ async execute(input, ctx2) {
107
+ const domains = getConfig().loadDomains();
108
+ // Per-turn surface fence (finding 1b): on a unified session a non-Notion-origin turn
109
+ // must not read Notion pages on the CHAT plane, even though the tool is armed for the
110
+ // session. OPE-732: the fence's refusal is audited here — assertTurnSurface itself stays
111
+ // a pure, synchronous, store-free check (see identity/binding.ts).
112
+ //
113
+ // OPE-913 — THE WORK PLANE IS THE ONE WAY PAST IT. A non-Notion-origin turn is admitted
114
+ // only when the caller's DOMAIN holds the Notion work grant (channels/notion-work.ts).
115
+ // That is not a hole in the fence: the grant is a reviewed config declaration naming the
116
+ // credential the read runs under, and the target page is still fenced to the caller's own
117
+ // domain below (assertNotionWorkPageAllowed). With no grant the refusal is byte-identical
118
+ // to before — same audit row, same thrown error.
119
+ let workGrant;
120
+ try {
121
+ assertTurnSurface(ctx2, "notion");
122
+ }
123
+ catch (err) {
124
+ if (!(err instanceof TurnOriginFenceError))
125
+ throw err;
126
+ workGrant = notionWorkGrant(ctx2, domains);
127
+ if (!workGrant) {
128
+ await auditOriginFenceRefusal(ctx2, "notion");
129
+ throw err;
130
+ }
131
+ }
132
+ // Default to THIS session's page (bindFrom(ctx).surface = the page id for Notion — see
133
+ // identity/binding.ts bindNotion). On the WORK plane there is no such default to take:
134
+ // the caller's surface is a Slack channel / a repo / a Linear team, and `bindFrom().surface`
135
+ // would hand a non-Notion id to the Notion API. Require the argument instead of guessing.
136
+ const requestedPage = input.page ?? (workGrant ? undefined : bindFrom(ctx2).surface);
137
+ if (!requestedPage) {
138
+ throw new Error(workGrant
139
+ ? "Pass `page` explicitly — this session was not woken on a Notion page, so there is no page to default to."
140
+ : "No Notion page id available for this session — pass `page` explicitly.");
141
+ }
142
+ // Credential resolved BEFORE the fence (OPE-352): resolving a block/discussion id to its
143
+ // owning page needs a working Notion API call, so the resolver has to be built first. This
144
+ // does NOT reintroduce a network dependency into a plain refusal — when there is no key, the
145
+ // resolver below is a no-op stub (never calls fetch), so an out-of-scope id still refuses
146
+ // with zero network and zero key, exactly as before (pinned in notion-read-scope-test.ts §6).
147
+ // OPE-467 / OPE-499 — a delivery-woken session reads under the credential that VERIFIED
148
+ // the delivery (carried on the auth attributes); an agent-initiated session (no delivery)
149
+ // falls back to the CALLING domain's own binding, or the org default when it binds none.
150
+ // Resolved off the caller's OWN surface/context, known before the fence runs (the
151
+ // block-resolution walk below needs a working key to run at all). That is not a weaker
152
+ // check: notion-scope.ts only ever admits the caller's own page or a page in the caller's
153
+ // own domain, so caller and target resolve to the same domain by construction.
154
+ // OPE-505: the key AND its endpoint rules, resolved together and spread together, so the
155
+ // credential's fence reaches every REST call below.
156
+ //
157
+ // OPE-913 — THE WORK PLANE RUNS ON A DIFFERENT CREDENTIAL, and that is the point of the
158
+ // split. `notionCallerCredential` resolves the CHAT key (`channel-key`, the comment
159
+ // surface's plumbing). A work-granted read runs on the credential the DOMAIN declared for
160
+ // its work role, so an organisation can share its chat integration with the comment pages
161
+ // only and its work integration with the page trees the agent may build from.
162
+ //
163
+ // 🔴 ONE VARIABLE FEEDS BOTH THE RESOLVE AND THE FAILURE MESSAGE, deliberately. Naming the
164
+ // credential in a literal string that a second expression resolves is how a test comes to
165
+ // assert a plane it is not actually exercising: the message says "work credential" while
166
+ // the call resolves the chat one, and nothing notices. Interpolating `credentialId` makes
167
+ // the message an OBSERVABLE of the resolution instead of a claim about it.
168
+ //
169
+ // 🔴 OPE-1525 — THE OWN-ACCOUNT GATE RUNS FIRST, ON THE LINE BELOW'S BEHALF. Both arms of
170
+ // the choice below resolve a TEAM key, so a member who asked to act with THEIR OWN Notion
171
+ // account has no answer here — and answering anyway is the defect the ticket traced. The
172
+ // gate refuses that turn (naming the consent route, or the granted tool, or saying plainly
173
+ // that neither exists) and writes the `connector.acting_identity` row either way.
174
+ await assertNotionActingIdentity(ctx2, "read_notion_page");
175
+ const credentialId = workGrant ? workGrant.credential : notionCallerCredential(ctx2, domains);
176
+ const cred = await resolveNotionCredential(credentialId);
177
+ // DOMAIN FENCE (OPE-236 + OPE-352). Notion's own enforcement bounds the WORKSPACE — one
178
+ // deployment-wide integration key, identical for every domain — so it cannot tell domain A's
179
+ // pages from domain B's. channels/notion-scope.ts is the allowlist on top: own page always,
180
+ // otherwise caller and target must land in the SAME domain (roster, then a container entry
181
+ // claim), and an unresolvable page is REFUSED, never defaulted readable. The refusal is
182
+ // audited there — the gap this closes was silent.
183
+ //
184
+ // OPE-352: the Notion worker intermittently hands this tool a DISCUSSION BLOCK id instead of
185
+ // the page it lives on, and a block id fails the plain page fence every time (it is never
186
+ // itself rostered/claimed) — a silent-looking `target-unassigned` refusal even when the
187
+ // block's owning page was perfectly in scope. With a working credential, fall back to
188
+ // resolving `requestedPage` as a block id to its parent page (bounded walk, see
189
+ // resolveBlockOwningPage) and fence THAT page through the exact same decision — never a
190
+ // separate, looser check. `page` below is the id actually authorized (the original id, or
191
+ // the resolved parent page when only resolution admitted it) — the content fetch and the
192
+ // reported `page` field both use it, never the raw `requestedPage`.
193
+ //
194
+ // OPE-913 — THE WORK PLANE TAKES THE PLAIN FENCE, deliberately. `notionReadScope` reads the
195
+ // caller's domain out of its own Notion surface key, which a Slack-woken caller does not
196
+ // have, so the work plane fences through `notionWorkScope` instead: the caller's domain
197
+ // comes from whatever surface woke it, and the target must still land in THAT domain. No
198
+ // block-parent resolution hop here — the OPE-352 hop exists because the Notion COMMENT
199
+ // worker is handed discussion-block ids by its own wake, which a cross-surface caller never
200
+ // is. A block id from the work plane refuses, and the refusal says how to pass a page id.
201
+ let page;
202
+ if (workGrant) {
203
+ await assertNotionWorkPageAllowed(ctx2, requestedPage, workGrant.domain, domains, notionWorkspacesOf(workGrant.domain));
204
+ page = requestedPage;
205
+ }
206
+ else if (cred) {
207
+ page = await assertNotionPageAllowedOrResolveBlock(ctx2, requestedPage, domains, (id) => resolveBlockOwningPage(id, { ...cred }));
208
+ }
209
+ else {
210
+ await assertNotionPageAllowed(ctx2, requestedPage, domains);
211
+ page = requestedPage;
212
+ }
213
+ if (!cred) {
214
+ throw new Error(workGrant
215
+ ? `no value stored for this domain's Notion work credential "${credentialId}" — cannot read the page.`
216
+ : "no value stored under db:notion:channel-key — cannot read the page.");
217
+ }
218
+ const res = await readNotionPage(page, { ...cred, maxChars: MAX_CHARS, maxBlocks: MAX_BLOCKS });
219
+ // OPE-1026 — the moment the fetch above returned. Stamped AFTER the fetch, so it never
220
+ // claims a read is fresher than it is (see read_linear_issue.ts's `readAt`).
221
+ const readAt = new Date().toISOString();
222
+ // OPE-331 — resolve the workspace tier HERE, where a ctx exists, and carry it on the
223
+ // output. Read-only and fail-soft: an unresolvable partition leaves it absent, which is the
224
+ // fail-closed answer the table already had.
225
+ //
226
+ // 🔴 OPE-913 — THE WORK PLANE LEAVES IT ABSENT, AND THAT IS THE WHOLE POINT. This resolves
227
+ // the tier of the CALLER'S OWN partition, which on the chat plane IS the Notion workspace
228
+ // the page lives in. On the work plane the caller is a Slack channel, a repo, a Linear
229
+ // team — so the same line would hand this Notion page the tier the operator vouched for
230
+ // SLACK, and a vouched Slack workspace would silently un-taint a page read from an
231
+ // unvouched Notion workspace. Absent means the read TAINTS (see toModelOutput below), which
232
+ // is the correct answer for a tier we genuinely cannot resolve here.
233
+ let workspaceTrust;
234
+ if (!workGrant) {
235
+ try {
236
+ workspaceTrust = declaredWorkspaceTrust(resolvePartition(ctx2).workspaceKey);
237
+ }
238
+ catch {
239
+ workspaceTrust = undefined;
240
+ }
241
+ }
242
+ // OPE-1714 — the page's own title and url, on the SAME credential and the SAME fenced page
243
+ // id the body read used. Separate call because the block-children endpoint carries neither;
244
+ // fail-soft inside the helper, so a page whose object cannot be read still returns its body.
245
+ const identity = await readNotionPageIdentity(page, { ...cred });
246
+ // OPE-1714 — THE REPLY FOOTER'S RECORD HALF, recorded HERE because here is where the host
247
+ // knows what it fetched. The door writes one reply-provenance entry (`notion`,
248
+ // `read_notion_page`) and decides on its own, from this turn's `page_id` stamp, whether the
249
+ // room already holds this page's id — a page the room never named is recorded WITHOUT its
250
+ // id, so it can never be footed. The title is turn-local and never stored. Never throws.
251
+ noteAuthoredRecordRead(ctx2, {
252
+ kind: "notion-page",
253
+ id: page,
254
+ // The id the CALLER asked for as well as the one the fence authorized: OPE-352's hop
255
+ // resolves a discussion-block id to its owning page, and the room's stamp names the
256
+ // block. Both name the record this read pulled.
257
+ alsoKnownAs: [requestedPage],
258
+ name: identity.title,
259
+ });
260
+ return {
261
+ page,
262
+ ...(identity.title !== undefined ? { title: identity.title } : {}),
263
+ ...(identity.url !== undefined ? { url: identity.url } : {}),
264
+ blocks: res.blockCount,
265
+ truncated: res.truncated,
266
+ text: res.text,
267
+ readAt,
268
+ ...(workspaceTrust !== undefined ? { workspaceTrust } : {}),
269
+ };
270
+ },
271
+ toModelOutput(output) {
272
+ // OPE-1714 — name the page and link it where the identity fetch answered. `ref` degrades to
273
+ // the bare id, which is exactly what every render said before these fields existed.
274
+ const ref = [output.title, output.url].filter((v) => typeof v === "string" && v.length > 0).join(" — ");
275
+ const named = ref.length > 0 ? `${output.page} (${ref})` : output.page;
276
+ const header = output.text.length === 0 ? `Notion page ${named} has no readable text.` : `Notion page ${named}:`;
277
+ const suffix = output.truncated ? "\n\n[truncated — the page has more content than shown]" : "";
278
+ // OPE-1026 — THE PROVENANCE LINE (the pattern OPE-1012 shipped for Linear, §asOf). Only
279
+ // when there is text to date: this is the page's CURRENT content, as of this read — the
280
+ // counterpart to read_notion_comments, whose comments are HISTORY against the same clock.
281
+ const asOf = output.text.length > 0 && output.readAt ? `\n\nRead live at ${output.readAt}: the text above is the page's current content, as of that moment.` : "";
282
+ const body = output.text.length === 0 ? header : `${header}\n${output.text}${suffix}${asOf}`;
283
+ // 🔴 OPE-324 — `workspaceTrust` (plumbed OPE-331) IS NOW THE DECISION. A page read from a
284
+ // workspace the operator VOUCHED as internal fences without tainting; an unvouched or
285
+ // public workspace still taints. Tommy ratified that relaxation on 2026-08-09 — a vouched
286
+ // internal Notion workspace is org property with org membership, the same trust basis as a
287
+ // private channel, and the instance-binary rule says so.
288
+ //
289
+ // `toModelOutput` has no ctx to resolve config from, so execute resolves the bound
290
+ // workspace's declared tier and carries it on the output. Absent ⇒ the table falls closed
291
+ // and the read TAINTS, and the `assumed` flag says why: the operator has never vouched this
292
+ // workspace (agent/identity/surfaces.ts), not that OE could not look.
293
+ return ingestUntrustedModelOutput(body, {
294
+ source: `notion page ${output.page}`,
295
+ ingestion: {
296
+ kind: "notion",
297
+ label: "read_notion_page",
298
+ ...(output.workspaceTrust !== undefined ? { workspaceTrust: output.workspaceTrust } : {}),
299
+ },
300
+ });
301
+ },
302
+ });
303
+ preRegisterDynamicEntry("read_notion_page", "_single", readNotionPageEntry);
304
+ export default defineDynamic({
305
+ events: {
306
+ // session.started ONLY: a session runs on exactly one surface, so the platform can't change
307
+ // mid-session — resolving once is correct and cheaper (see access/source-scoped-mount.ts).
308
+ "session.started": (_event, ctx) => {
309
+ // THE CHAT ARM (§A5, see access/source-scoped-mount.ts), narrowed to the DELEGATED-WORKER grain by
310
+ // OPE-1014: mount iff `notion ∈` this window's sources AND the grain is `task`. The per-turn
311
+ // origin fence in execute (assertTurnSurface) still stops a non-Notion-origin turn reading a
312
+ // page on a multi-source window.
313
+ //
314
+ // OPE-913 — OR the caller's DOMAIN holds the Notion WORK grant. That is the second, independent
315
+ // mount path, and it is GRAIN-BLIND: a Slack-woken delegated worker in a work-granted domain
316
+ // mounts this tool even though `notion ∉ sources` for its window, and so does a granted
317
+ // conversational seat. Without the grant nothing widens — a domain that declares no Notion work
318
+ // credential mounts this tool on its delegated workers only.
319
+ const ok = workerChatArmMount(ctx, "notion", () => getConfig().loadDomains()) ||
320
+ notionWorkMount(ctx, () => getConfig().loadDomains());
321
+ if (!ok)
322
+ return null; // hide unless notion ∈ sources, or the domain holds the work grant
323
+ return readNotionPageEntry;
324
+ },
325
+ },
326
+ });
327
+ //# sourceMappingURL=read_notion_page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read_notion_page.js","sourceRoot":"","sources":["../../../agent/tools/read_notion_page.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,231 @@
1
+ {
2
+ "name": "@open-everywhere/provider-notion",
3
+ "version": "0.1.0",
4
+ "description": "Open Everywhere Notion provider — Notion as a wake surface plus page and database tools.",
5
+ "keywords": [
6
+ "agent",
7
+ "notion",
8
+ "notion-api",
9
+ "eve"
10
+ ],
11
+ "license": "Apache-2.0",
12
+ "type": "module",
13
+ "homepage": "https://github.com/open-everywhere/open-everywhere/tree/main/packages/providers/notion",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/open-everywhere/open-everywhere.git",
17
+ "directory": "packages/providers/notion"
18
+ },
19
+ "bugs": {
20
+ "url": "https://github.com/open-everywhere/open-everywhere/issues"
21
+ },
22
+ "engines": {
23
+ "node": ">=24"
24
+ },
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "files": [
29
+ "agent",
30
+ "dist",
31
+ "tsconfig.json",
32
+ "tsconfig.build.json",
33
+ "LICENSE",
34
+ "NOTICE"
35
+ ],
36
+ "exports": {
37
+ "./package.json": "./package.json",
38
+ "./agent/channels/notion-acting-identity.js": {
39
+ "types": "./dist/agent/channels/notion-acting-identity.d.ts",
40
+ "default": "./dist/agent/channels/notion-acting-identity.js"
41
+ },
42
+ "./agent/channels/notion-ancestry-trust.js": {
43
+ "types": "./dist/agent/channels/notion-ancestry-trust.d.ts",
44
+ "default": "./dist/agent/channels/notion-ancestry-trust.js"
45
+ },
46
+ "./agent/channels/notion-api.js": {
47
+ "types": "./dist/agent/channels/notion-api.d.ts",
48
+ "default": "./dist/agent/channels/notion-api.js"
49
+ },
50
+ "./agent/channels/notion-bot-user.js": {
51
+ "types": "./dist/agent/channels/notion-bot-user.d.ts",
52
+ "default": "./dist/agent/channels/notion-bot-user.js"
53
+ },
54
+ "./agent/channels/notion-catch-up.js": {
55
+ "types": "./dist/agent/channels/notion-catch-up.d.ts",
56
+ "default": "./dist/agent/channels/notion-catch-up.js"
57
+ },
58
+ "./agent/channels/notion-credentials.js": {
59
+ "types": "./dist/agent/channels/notion-credentials.d.ts",
60
+ "default": "./dist/agent/channels/notion-credentials.js"
61
+ },
62
+ "./agent/channels/notion-engagement.js": {
63
+ "types": "./dist/agent/channels/notion-engagement.d.ts",
64
+ "default": "./dist/agent/channels/notion-engagement.js"
65
+ },
66
+ "./agent/channels/notion-event-normalizer.js": {
67
+ "types": "./dist/agent/channels/notion-event-normalizer.d.ts",
68
+ "default": "./dist/agent/channels/notion-event-normalizer.js"
69
+ },
70
+ "./agent/channels/notion-identity.js": {
71
+ "types": "./dist/agent/channels/notion-identity.d.ts",
72
+ "default": "./dist/agent/channels/notion-identity.js"
73
+ },
74
+ "./agent/channels/notion-ingress-drain.js": {
75
+ "types": "./dist/agent/channels/notion-ingress-drain.d.ts",
76
+ "default": "./dist/agent/channels/notion-ingress-drain.js"
77
+ },
78
+ "./agent/channels/notion-ingress.js": {
79
+ "types": "./dist/agent/channels/notion-ingress.d.ts",
80
+ "default": "./dist/agent/channels/notion-ingress.js"
81
+ },
82
+ "./agent/channels/notion-message-ops.js": {
83
+ "types": "./dist/agent/channels/notion-message-ops.d.ts",
84
+ "default": "./dist/agent/channels/notion-message-ops.js"
85
+ },
86
+ "./agent/channels/notion-origin-codec.js": {
87
+ "types": "./dist/agent/channels/notion-origin-codec.d.ts",
88
+ "default": "./dist/agent/channels/notion-origin-codec.js"
89
+ },
90
+ "./agent/channels/notion-post-arm.js": {
91
+ "types": "./dist/agent/channels/notion-post-arm.d.ts",
92
+ "default": "./dist/agent/channels/notion-post-arm.js"
93
+ },
94
+ "./agent/channels/notion-question-return.js": {
95
+ "types": "./dist/agent/channels/notion-question-return.d.ts",
96
+ "default": "./dist/agent/channels/notion-question-return.js"
97
+ },
98
+ "./agent/channels/notion-read.js": {
99
+ "types": "./dist/agent/channels/notion-read.d.ts",
100
+ "default": "./dist/agent/channels/notion-read.js"
101
+ },
102
+ "./agent/channels/notion-scope.js": {
103
+ "types": "./dist/agent/channels/notion-scope.d.ts",
104
+ "default": "./dist/agent/channels/notion-scope.js"
105
+ },
106
+ "./agent/channels/notion-stop.js": {
107
+ "types": "./dist/agent/channels/notion-stop.d.ts",
108
+ "default": "./dist/agent/channels/notion-stop.js"
109
+ },
110
+ "./agent/channels/notion-supervisor.js": {
111
+ "types": "./dist/agent/channels/notion-supervisor.d.ts",
112
+ "default": "./dist/agent/channels/notion-supervisor.js"
113
+ },
114
+ "./agent/channels/notion-webhook-timing.js": {
115
+ "types": "./dist/agent/channels/notion-webhook-timing.d.ts",
116
+ "default": "./dist/agent/channels/notion-webhook-timing.js"
117
+ },
118
+ "./agent/channels/notion-webhook-verifier.js": {
119
+ "types": "./dist/agent/channels/notion-webhook-verifier.d.ts",
120
+ "default": "./dist/agent/channels/notion-webhook-verifier.js"
121
+ },
122
+ "./agent/channels/notion-webhook.js": {
123
+ "types": "./dist/agent/channels/notion-webhook.d.ts",
124
+ "default": "./dist/agent/channels/notion-webhook.js"
125
+ },
126
+ "./agent/channels/notion-work-search.js": {
127
+ "types": "./dist/agent/channels/notion-work-search.d.ts",
128
+ "default": "./dist/agent/channels/notion-work-search.js"
129
+ },
130
+ "./agent/channels/notion-work.js": {
131
+ "types": "./dist/agent/channels/notion-work.d.ts",
132
+ "default": "./dist/agent/channels/notion-work.js"
133
+ },
134
+ "./agent/channels/notion.js": {
135
+ "types": "./dist/agent/channels/notion.d.ts",
136
+ "default": "./dist/agent/channels/notion.js"
137
+ },
138
+ "./agent/contributions.js": {
139
+ "types": "./dist/agent/contributions.d.ts",
140
+ "default": "./dist/agent/contributions.js"
141
+ },
142
+ "./agent/docs-generated.js": {
143
+ "types": "./dist/agent/docs-generated.d.ts",
144
+ "default": "./dist/agent/docs-generated.js"
145
+ },
146
+ "./agent/memory/blocks.js": {
147
+ "types": "./dist/agent/memory/blocks.d.ts",
148
+ "default": "./dist/agent/memory/blocks.js"
149
+ },
150
+ "./agent/memory/memory.js": {
151
+ "types": "./dist/agent/memory/memory.d.ts",
152
+ "default": "./dist/agent/memory/memory.js"
153
+ },
154
+ "./agent/provider-events.js": {
155
+ "types": "./dist/agent/provider-events.d.ts",
156
+ "default": "./dist/agent/provider-events.js"
157
+ },
158
+ "./agent/register.js": {
159
+ "types": "./dist/agent/register.d.ts",
160
+ "default": "./dist/agent/register.js"
161
+ },
162
+ "./agent/schedules/notion-ingress.js": {
163
+ "types": "./dist/agent/schedules/notion-ingress.d.ts",
164
+ "default": "./dist/agent/schedules/notion-ingress.js"
165
+ },
166
+ "./agent/surface-parity.js": {
167
+ "types": "./dist/agent/surface-parity.d.ts",
168
+ "default": "./dist/agent/surface-parity.js"
169
+ },
170
+ "./agent/tools/notion_search.js": {
171
+ "types": "./dist/agent/tools/notion_search.d.ts",
172
+ "default": "./dist/agent/tools/notion_search.js"
173
+ },
174
+ "./agent/tools/read_notion_comments.js": {
175
+ "types": "./dist/agent/tools/read_notion_comments.d.ts",
176
+ "default": "./dist/agent/tools/read_notion_comments.js"
177
+ },
178
+ "./agent/tools/read_notion_page.js": {
179
+ "types": "./dist/agent/tools/read_notion_page.d.ts",
180
+ "default": "./dist/agent/tools/read_notion_page.js"
181
+ }
182
+ },
183
+ "scripts": {
184
+ "build": "tsc -p tsconfig.build.json",
185
+ "typecheck": "tsc --noEmit -p tsconfig.json",
186
+ "test": "npm run test:chain",
187
+ "test:chain": "npm run test:notion && npm run test:notion-ingress && npm run test:notion-ingress-drain && npm run test:notion-supervisor && npm run test:notion-identity && npm run test:notion-memory && npm run test:notion-memory-credential && npm run test:notion-memory-fence && npm run test:notion-tool-gate && npm run test:notion-read-scope && npm run test:notion-work-search && npm run test:notion-per-domain-credential && npm run test:notion-webhook-verifier && npm run test:notion-event-normalizer && npm run test:dynamic-replay && npm run test:notion-ancestry-trust && npm run test:notion-credential-fence && npm run test:notion-route-taint && npm run test:notion-post-arm && npm run test:notion-read && npm run test:notion-stop && npm run test:notion-engagement && npm run test:notion-reaction-parity && npm run test:notion-message-ops && npm run test:notion-file-upload && npm run test:notion-wake-heal && npm run test:notion-rate-limit && npm run test:notion-write-seam && npm run test:notion-question-return && npm run test:notion-turn-policy",
188
+ "test:notion": "NODE_NO_WARNINGS=1 tsx scripts/notion-test.ts",
189
+ "test:notion-ingress": "NODE_NO_WARNINGS=1 tsx scripts/notion-ingress-test.ts",
190
+ "test:notion-turn-policy": "NODE_NO_WARNINGS=1 tsx scripts/notion-turn-policy-test.ts",
191
+ "test:notion-rate-limit": "NODE_NO_WARNINGS=1 tsx scripts/notion-rate-limit-test.ts",
192
+ "test:notion-write-seam": "NODE_NO_WARNINGS=1 tsx scripts/notion-write-seam-test.ts",
193
+ "test:notion-wake-heal": "NODE_NO_WARNINGS=1 tsx scripts/notion-wake-heal-test.ts",
194
+ "test:notion-question-return": "NODE_NO_WARNINGS=1 tsx scripts/notion-question-return-test.ts",
195
+ "test:notion-read": "NODE_NO_WARNINGS=1 tsx scripts/notion-read-test.ts",
196
+ "test:notion-post-arm": "NODE_NO_WARNINGS=1 tsx scripts/notion-post-arm-test.ts",
197
+ "test:notion-supervisor": "NODE_NO_WARNINGS=1 tsx scripts/notion-supervisor-test.ts",
198
+ "test:notion-identity": "NODE_NO_WARNINGS=1 tsx scripts/notion-identity-test.ts",
199
+ "test:notion-memory": "NODE_NO_WARNINGS=1 tsx scripts/notion-memory-test.ts",
200
+ "test:notion-memory-credential": "NODE_NO_WARNINGS=1 tsx scripts/notion-memory-credential-test.ts",
201
+ "test:notion-memory-fence": "NODE_NO_WARNINGS=1 tsx scripts/notion-memory-fence-test.ts",
202
+ "test:notion-tool-gate": "NODE_NO_WARNINGS=1 tsx scripts/notion-tool-gate-test.ts",
203
+ "test:notion-per-domain-credential": "tsx scripts/notion-per-domain-credential-test.ts",
204
+ "test:notion-read-scope": "NODE_NO_WARNINGS=1 tsx scripts/notion-read-scope-test.ts",
205
+ "test:notion-work-search": "NODE_NO_WARNINGS=1 tsx scripts/notion-work-search-test.ts",
206
+ "test:notion-ancestry-trust": "NODE_NO_WARNINGS=1 tsx scripts/notion-ancestry-trust-test.ts",
207
+ "test:dynamic-replay": "NODE_NO_WARNINGS=1 tsx scripts/dynamic-replay-test.ts",
208
+ "test:notion-webhook-verifier": "NODE_NO_WARNINGS=1 tsx scripts/notion-webhook-verifier-test.ts",
209
+ "test:notion-event-normalizer": "NODE_NO_WARNINGS=1 tsx scripts/notion-event-normalizer-test.ts",
210
+ "test:notion-credential-fence": "NODE_NO_WARNINGS=1 tsx scripts/notion-credential-fence-test.ts",
211
+ "test:notion-route-taint": "NODE_NO_WARNINGS=1 tsx scripts/notion-route-taint-test.ts",
212
+ "test:notion-stop": "NODE_NO_WARNINGS=1 tsx scripts/notion-stop-test.ts",
213
+ "test:notion-reaction-parity": "NODE_NO_WARNINGS=1 tsx scripts/notion-reaction-parity-test.ts",
214
+ "test:notion-engagement": "NODE_NO_WARNINGS=1 tsx scripts/notion-engagement-test.ts",
215
+ "test:notion-message-ops": "NODE_NO_WARNINGS=1 tsx scripts/notion-message-ops-test.ts",
216
+ "test:notion-file-upload": "NODE_NO_WARNINGS=1 tsx scripts/notion-file-upload-test.ts",
217
+ "test:notion-ingress-drain": "NODE_NO_WARNINGS=1 tsx scripts/notion-ingress-drain-test.ts"
218
+ },
219
+ "dependencies": {
220
+ "@open-everywhere/core": "0.1.0",
221
+ "zod": "4.5.4"
222
+ },
223
+ "peerDependencies": {
224
+ "eve": "0.54.3"
225
+ },
226
+ "devDependencies": {
227
+ "@types/node": "latest",
228
+ "tsx": "latest",
229
+ "typescript": "latest"
230
+ }
231
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ // PUBLISH BUILD (OPE-406). See packages/core/tsconfig.build.json for the full rationale: the
3
+ // sibling tsconfig.json stays noEmit (it is the typecheck gate and covers scripts/** too); this
4
+ // config emits ONLY the published surface (agent/**) into dist/, because a registry install
5
+ // cannot execute raw TypeScript and "eve build" evaluates the authored agent modules.
6
+ "extends": "./tsconfig.json",
7
+ "compilerOptions": {
8
+ "noEmit": false,
9
+ // Incremental: these run from every pre-hook (pretypecheck/pretest/prebuild), so a no-op
10
+ // rebuild has to be near-free or the gate chain pays a full tsc on every invocation.
11
+ "incremental": true,
12
+ // OUTSIDE outDir on purpose: a tsbuildinfo left in dist/ gets swept up by the files
13
+ // allowlist and PUBLISHED (caught by the tarball listing, 2026-08-12).
14
+ "tsBuildInfoFile": "./.tsbuildinfo",
15
+ "declaration": true,
16
+ "declarationMap": true,
17
+ "sourceMap": true,
18
+ "outDir": "dist",
19
+ "rootDir": "."
20
+ },
21
+ "include": ["agent/**/*.ts"]
22
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2023",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "lib": ["ES2023"],
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "noEmit": true,
11
+ "resolveJsonModule": true,
12
+ "types": ["node"]
13
+ },
14
+ "include": ["agent/**/*.ts", "scripts/**/*.ts"]
15
+ }