@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.
- package/LICENSE +201 -0
- package/NOTICE +21 -0
- package/README.md +48 -0
- package/agent/channels/notion-acting-identity.ts +88 -0
- package/agent/channels/notion-ancestry-trust.ts +458 -0
- package/agent/channels/notion-api.ts +1290 -0
- package/agent/channels/notion-bot-user.ts +250 -0
- package/agent/channels/notion-catch-up.ts +339 -0
- package/agent/channels/notion-credentials.ts +417 -0
- package/agent/channels/notion-engagement.ts +372 -0
- package/agent/channels/notion-event-normalizer.ts +587 -0
- package/agent/channels/notion-identity.ts +271 -0
- package/agent/channels/notion-ingress-drain.ts +289 -0
- package/agent/channels/notion-ingress.ts +182 -0
- package/agent/channels/notion-message-ops.ts +140 -0
- package/agent/channels/notion-origin-codec.ts +38 -0
- package/agent/channels/notion-post-arm.ts +189 -0
- package/agent/channels/notion-question-return.ts +223 -0
- package/agent/channels/notion-read.ts +283 -0
- package/agent/channels/notion-scope.ts +560 -0
- package/agent/channels/notion-stop.ts +169 -0
- package/agent/channels/notion-supervisor.ts +856 -0
- package/agent/channels/notion-webhook-timing.ts +131 -0
- package/agent/channels/notion-webhook-verifier.ts +55 -0
- package/agent/channels/notion-webhook.ts +963 -0
- package/agent/channels/notion-work-search.ts +74 -0
- package/agent/channels/notion-work.ts +144 -0
- package/agent/channels/notion.ts +1242 -0
- package/agent/contributions.ts +39 -0
- package/agent/docs-generated.ts +15 -0
- package/agent/memory/blocks.ts +295 -0
- package/agent/memory/memory.ts +622 -0
- package/agent/provider-events.ts +198 -0
- package/agent/register.ts +144 -0
- package/agent/schedules/notion-ingress.ts +20 -0
- package/agent/surface-parity.ts +105 -0
- package/agent/tools/notion_search.ts +110 -0
- package/agent/tools/read_notion_comments.ts +250 -0
- package/agent/tools/read_notion_page.ts +351 -0
- package/dist/agent/channels/notion-acting-identity.d.ts +23 -0
- package/dist/agent/channels/notion-acting-identity.d.ts.map +1 -0
- package/dist/agent/channels/notion-acting-identity.js +85 -0
- package/dist/agent/channels/notion-acting-identity.js.map +1 -0
- package/dist/agent/channels/notion-ancestry-trust.d.ts +208 -0
- package/dist/agent/channels/notion-ancestry-trust.d.ts.map +1 -0
- package/dist/agent/channels/notion-ancestry-trust.js +331 -0
- package/dist/agent/channels/notion-ancestry-trust.js.map +1 -0
- package/dist/agent/channels/notion-api.d.ts +655 -0
- package/dist/agent/channels/notion-api.d.ts.map +1 -0
- package/dist/agent/channels/notion-api.js +938 -0
- package/dist/agent/channels/notion-api.js.map +1 -0
- package/dist/agent/channels/notion-bot-user.d.ts +107 -0
- package/dist/agent/channels/notion-bot-user.d.ts.map +1 -0
- package/dist/agent/channels/notion-bot-user.js +192 -0
- package/dist/agent/channels/notion-bot-user.js.map +1 -0
- package/dist/agent/channels/notion-catch-up.d.ts +64 -0
- package/dist/agent/channels/notion-catch-up.d.ts.map +1 -0
- package/dist/agent/channels/notion-catch-up.js +289 -0
- package/dist/agent/channels/notion-catch-up.js.map +1 -0
- package/dist/agent/channels/notion-credentials.d.ts +210 -0
- package/dist/agent/channels/notion-credentials.d.ts.map +1 -0
- package/dist/agent/channels/notion-credentials.js +360 -0
- package/dist/agent/channels/notion-credentials.js.map +1 -0
- package/dist/agent/channels/notion-engagement.d.ts +219 -0
- package/dist/agent/channels/notion-engagement.d.ts.map +1 -0
- package/dist/agent/channels/notion-engagement.js +283 -0
- package/dist/agent/channels/notion-engagement.js.map +1 -0
- package/dist/agent/channels/notion-event-normalizer.d.ts +194 -0
- package/dist/agent/channels/notion-event-normalizer.d.ts.map +1 -0
- package/dist/agent/channels/notion-event-normalizer.js +542 -0
- package/dist/agent/channels/notion-event-normalizer.js.map +1 -0
- package/dist/agent/channels/notion-identity.d.ts +119 -0
- package/dist/agent/channels/notion-identity.d.ts.map +1 -0
- package/dist/agent/channels/notion-identity.js +205 -0
- package/dist/agent/channels/notion-identity.js.map +1 -0
- package/dist/agent/channels/notion-ingress-drain.d.ts +66 -0
- package/dist/agent/channels/notion-ingress-drain.d.ts.map +1 -0
- package/dist/agent/channels/notion-ingress-drain.js +185 -0
- package/dist/agent/channels/notion-ingress-drain.js.map +1 -0
- package/dist/agent/channels/notion-ingress.d.ts +64 -0
- package/dist/agent/channels/notion-ingress.d.ts.map +1 -0
- package/dist/agent/channels/notion-ingress.js +130 -0
- package/dist/agent/channels/notion-ingress.js.map +1 -0
- package/dist/agent/channels/notion-message-ops.d.ts +3 -0
- package/dist/agent/channels/notion-message-ops.d.ts.map +1 -0
- package/dist/agent/channels/notion-message-ops.js +126 -0
- package/dist/agent/channels/notion-message-ops.js.map +1 -0
- package/dist/agent/channels/notion-origin-codec.d.ts +7 -0
- package/dist/agent/channels/notion-origin-codec.d.ts.map +1 -0
- package/dist/agent/channels/notion-origin-codec.js +32 -0
- package/dist/agent/channels/notion-origin-codec.js.map +1 -0
- package/dist/agent/channels/notion-post-arm.d.ts +3 -0
- package/dist/agent/channels/notion-post-arm.d.ts.map +1 -0
- package/dist/agent/channels/notion-post-arm.js +185 -0
- package/dist/agent/channels/notion-post-arm.js.map +1 -0
- package/dist/agent/channels/notion-question-return.d.ts +77 -0
- package/dist/agent/channels/notion-question-return.d.ts.map +1 -0
- package/dist/agent/channels/notion-question-return.js +183 -0
- package/dist/agent/channels/notion-question-return.js.map +1 -0
- package/dist/agent/channels/notion-read.d.ts +62 -0
- package/dist/agent/channels/notion-read.d.ts.map +1 -0
- package/dist/agent/channels/notion-read.js +257 -0
- package/dist/agent/channels/notion-read.js.map +1 -0
- package/dist/agent/channels/notion-scope.d.ts +188 -0
- package/dist/agent/channels/notion-scope.d.ts.map +1 -0
- package/dist/agent/channels/notion-scope.js +433 -0
- package/dist/agent/channels/notion-scope.js.map +1 -0
- package/dist/agent/channels/notion-stop.d.ts +48 -0
- package/dist/agent/channels/notion-stop.d.ts.map +1 -0
- package/dist/agent/channels/notion-stop.js +141 -0
- package/dist/agent/channels/notion-stop.js.map +1 -0
- package/dist/agent/channels/notion-supervisor.d.ts +462 -0
- package/dist/agent/channels/notion-supervisor.d.ts.map +1 -0
- package/dist/agent/channels/notion-supervisor.js +482 -0
- package/dist/agent/channels/notion-supervisor.js.map +1 -0
- package/dist/agent/channels/notion-webhook-timing.d.ts +35 -0
- package/dist/agent/channels/notion-webhook-timing.d.ts.map +1 -0
- package/dist/agent/channels/notion-webhook-timing.js +99 -0
- package/dist/agent/channels/notion-webhook-timing.js.map +1 -0
- package/dist/agent/channels/notion-webhook-verifier.d.ts +5 -0
- package/dist/agent/channels/notion-webhook-verifier.d.ts.map +1 -0
- package/dist/agent/channels/notion-webhook-verifier.js +53 -0
- package/dist/agent/channels/notion-webhook-verifier.js.map +1 -0
- package/dist/agent/channels/notion-webhook.d.ts +242 -0
- package/dist/agent/channels/notion-webhook.d.ts.map +1 -0
- package/dist/agent/channels/notion-webhook.js +675 -0
- package/dist/agent/channels/notion-webhook.js.map +1 -0
- package/dist/agent/channels/notion-work-search.d.ts +9 -0
- package/dist/agent/channels/notion-work-search.d.ts.map +1 -0
- package/dist/agent/channels/notion-work-search.js +69 -0
- package/dist/agent/channels/notion-work-search.js.map +1 -0
- package/dist/agent/channels/notion-work.d.ts +68 -0
- package/dist/agent/channels/notion-work.d.ts.map +1 -0
- package/dist/agent/channels/notion-work.js +126 -0
- package/dist/agent/channels/notion-work.js.map +1 -0
- package/dist/agent/channels/notion.d.ts +95 -0
- package/dist/agent/channels/notion.d.ts.map +1 -0
- package/dist/agent/channels/notion.js +1081 -0
- package/dist/agent/channels/notion.js.map +1 -0
- package/dist/agent/contributions.d.ts +32 -0
- package/dist/agent/contributions.d.ts.map +1 -0
- package/dist/agent/contributions.js +37 -0
- package/dist/agent/contributions.js.map +1 -0
- package/dist/agent/docs-generated.d.ts +3 -0
- package/dist/agent/docs-generated.d.ts.map +1 -0
- package/dist/agent/docs-generated.js +13 -0
- package/dist/agent/docs-generated.js.map +1 -0
- package/dist/agent/memory/blocks.d.ts +58 -0
- package/dist/agent/memory/blocks.d.ts.map +1 -0
- package/dist/agent/memory/blocks.js +264 -0
- package/dist/agent/memory/blocks.js.map +1 -0
- package/dist/agent/memory/memory.d.ts +157 -0
- package/dist/agent/memory/memory.d.ts.map +1 -0
- package/dist/agent/memory/memory.js +471 -0
- package/dist/agent/memory/memory.js.map +1 -0
- package/dist/agent/provider-events.d.ts +3 -0
- package/dist/agent/provider-events.d.ts.map +1 -0
- package/dist/agent/provider-events.js +196 -0
- package/dist/agent/provider-events.js.map +1 -0
- package/dist/agent/register.d.ts +5 -0
- package/dist/agent/register.d.ts.map +1 -0
- package/dist/agent/register.js +128 -0
- package/dist/agent/register.js.map +1 -0
- package/dist/agent/schedules/notion-ingress.d.ts +6 -0
- package/dist/agent/schedules/notion-ingress.d.ts.map +1 -0
- package/dist/agent/schedules/notion-ingress.js +16 -0
- package/dist/agent/schedules/notion-ingress.js.map +1 -0
- package/dist/agent/surface-parity.d.ts +3 -0
- package/dist/agent/surface-parity.d.ts.map +1 -0
- package/dist/agent/surface-parity.js +99 -0
- package/dist/agent/surface-parity.js.map +1 -0
- package/dist/agent/tools/notion_search.d.ts +56 -0
- package/dist/agent/tools/notion_search.d.ts.map +1 -0
- package/dist/agent/tools/notion_search.js +100 -0
- package/dist/agent/tools/notion_search.js.map +1 -0
- package/dist/agent/tools/read_notion_comments.d.ts +57 -0
- package/dist/agent/tools/read_notion_comments.d.ts.map +1 -0
- package/dist/agent/tools/read_notion_comments.js +236 -0
- package/dist/agent/tools/read_notion_comments.js.map +1 -0
- package/dist/agent/tools/read_notion_page.d.ts +53 -0
- package/dist/agent/tools/read_notion_page.d.ts.map +1 -0
- package/dist/agent/tools/read_notion_page.js +327 -0
- package/dist/agent/tools/read_notion_page.js.map +1 -0
- package/package.json +231 -0
- package/tsconfig.build.json +22 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/** The auth shape both halves read — the delivery's attributes, where `buildNotionAuth` stows the
|
|
2
|
+
* discussion, page and workspace ids. Structural so nothing here needs the provider's own type. */
|
|
3
|
+
export interface NotionAnswerAuth {
|
|
4
|
+
readonly attributes?: Record<string, unknown>;
|
|
5
|
+
}
|
|
6
|
+
/** The room a question is showing in, in the vocabulary the row stores. A discussion, not a page:
|
|
7
|
+
* a page's other discussions are other conversations, and none of them saw this question. */
|
|
8
|
+
export declare function notionQuestionContainerKey(discussionId: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* The store key for one Notion session's outstanding question, from either side's spelling of the
|
|
11
|
+
* token. `namespaced` says which side is calling — see the header's keying paragraph, which is the
|
|
12
|
+
* whole reason this function exists instead of a bare token argument at two call sites.
|
|
13
|
+
*
|
|
14
|
+
* Returns `undefined` for an unusable token, which every caller treats as "no leg": the question is
|
|
15
|
+
* posted and simply not answerable, exactly as it was before this module existed.
|
|
16
|
+
*/
|
|
17
|
+
export declare function notionQuestionKey(token: unknown, namespaced: boolean): string | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Record that this session is parked on a question the discussion can see.
|
|
20
|
+
*
|
|
21
|
+
* NEVER THROWS — it runs inside a channel event handler on an ALREADY-PARKED turn, and a store blip
|
|
22
|
+
* must not break the event dispatch on top of it. Returns whether the row was written, so the caller
|
|
23
|
+
* can log the difference between "this discussion can answer" and "it cannot".
|
|
24
|
+
*/
|
|
25
|
+
export declare function rememberNotionQuestion(input: {
|
|
26
|
+
readonly org: string;
|
|
27
|
+
/** eve's NAMESPACED continuation token, from `channel.continuation.token`. */
|
|
28
|
+
readonly namespacedToken: unknown;
|
|
29
|
+
readonly requestId: string;
|
|
30
|
+
readonly discussionId: string;
|
|
31
|
+
readonly wakeId?: string;
|
|
32
|
+
}): Promise<boolean>;
|
|
33
|
+
/** What one inbound comment resolves to, when it resolves to an answer at all. */
|
|
34
|
+
export interface NotionInboundAnswer {
|
|
35
|
+
readonly requestId: string;
|
|
36
|
+
readonly floor: string;
|
|
37
|
+
readonly wakeId?: string;
|
|
38
|
+
readonly taskId?: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Is this inbound comment the answer to the session's outstanding question, and may THIS discussion
|
|
42
|
+
* give it?
|
|
43
|
+
*
|
|
44
|
+
* FAIL-SOFT TO AN ORDINARY WAKE, in every direction. No row, an unreadable store, a comment on
|
|
45
|
+
* another discussion, a refused answerer, a thrown floor — all answer `undefined`, which is the
|
|
46
|
+
* behaviour every comment had before this leg existed. The comment is delivered, the seat hears it,
|
|
47
|
+
* and the question stays open for someone who may answer it. Nothing here may drop a comment and
|
|
48
|
+
* nothing here may throw.
|
|
49
|
+
*
|
|
50
|
+
* 🔴 THE ADMISSION IS THE SAME ONE EVERY OTHER ARM RUNS (OPE-574). Five call sites run
|
|
51
|
+
* {@link admitInputResponse} — three WIDGET arms and two ROOM-MESSAGE arms, which core
|
|
52
|
+
* approvals/input-response-floor.ts lists by file. This is the second ROOM-MESSAGE arm. A comment
|
|
53
|
+
* that resolves a worker's question is the same act as a click on that question's widget over a
|
|
54
|
+
* different transport, so it must meet the same floor. Letting the transport decide would make the
|
|
55
|
+
* gate cosmetic.
|
|
56
|
+
*
|
|
57
|
+
* 🔴 BOTH LEGS ARE INERT ON THIS ARM, so the admission refuses nothing here. Leg B
|
|
58
|
+
* (`answererIsGuest`) is not passed: Notion carries no per-member membership probe, and `undefined`
|
|
59
|
+
* is always inert. Leg A, the room floor, needs a governance member supplier for the room, and the
|
|
60
|
+
* Slack bundle registers the only production one (its channels/slack-events.ts), so no roster
|
|
61
|
+
* resolves for a Notion discussion. The unified window's own room-message arm is NOT the
|
|
62
|
+
* same case: it takes the ORIGIN platform for the partition, so a Slack-origin room message
|
|
63
|
+
* resolves a `slack/...` key and leg A does have teeth there.
|
|
64
|
+
*
|
|
65
|
+
* What carries THIS path instead is the question-to-room binding inside
|
|
66
|
+
* `resolveInboundAnswerRouting`: only the discussion the question was posted into may answer it,
|
|
67
|
+
* and core channels/question-bridge.ts answers `foreign-room` on any mismatch.
|
|
68
|
+
*/
|
|
69
|
+
export declare function resolveNotionInboundAnswer(input: {
|
|
70
|
+
readonly org: string;
|
|
71
|
+
/** The BARE token the dispatch minted — NOT `channel.continuation.token`. */
|
|
72
|
+
readonly bareToken: string;
|
|
73
|
+
readonly auth: NotionAnswerAuth;
|
|
74
|
+
}): Promise<NotionInboundAnswer | undefined>;
|
|
75
|
+
/** Consume the row. Called ONLY after eve has accepted the answer — see the header's rule 3. */
|
|
76
|
+
export declare function forgetNotionQuestion(org: string, bareToken: string): Promise<boolean>;
|
|
77
|
+
//# sourceMappingURL=notion-question-return.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notion-question-return.d.ts","sourceRoot":"","sources":["../../../agent/channels/notion-question-return.ts"],"names":[],"mappings":"AAwEA;oGACoG;AACpG,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/C;AAED;8FAC8F;AAC9F,wBAAgB,0BAA0B,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAEvE;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAGzF;AAQD;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAAC,KAAK,EAAE;IAClD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,8EAA8E;IAC9E,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;IAClC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B,GAAG,OAAO,CAAC,OAAO,CAAC,CAQnB;AAED,kFAAkF;AAClF,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAsB,0BAA0B,CAAC,KAAK,EAAE;IACtD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,6EAA6E;IAC7E,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;CACjC,GAAG,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC,CA8C3C;AAED,gGAAgG;AAChG,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAI3F"}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// THE NATIVE ROUTE'S RETURN LEG (OPE-1305) — how a discussion's next comment reaches a DELEGATED
|
|
2
|
+
// WORKER that asked a question, instead of being eaten by the seat that proxied it.
|
|
3
|
+
//
|
|
4
|
+
// WHAT WAS BROKEN, AND WHY IT WAS NOT FIXED BY THE HANDLER THAT POSTS THE QUESTION. OPE-1275 gave
|
|
5
|
+
// Notion's channel a real `input.requested` handler: an `ask_question` now lands in the discussion
|
|
6
|
+
// thread where a person can read it. It records nothing. For a question the SEAT ITSELF raised that
|
|
7
|
+
// is complete — eve consumes the thread's next inbound message as the answer. For a DELEGATED WORKER
|
|
8
|
+
// it is not, and core states the mechanism measured (core channels/question-bridge.ts, the OPE-565
|
|
9
|
+
// header): eve proxies the child's request up through the PARENT's channel adapter, so the prompt
|
|
10
|
+
// reaches the handler and lands in the thread, but the answer reaches the child ONLY through an
|
|
11
|
+
// `inputResponses` entry whose `requestId` sits in the parent's proxy map. A plain message falls to
|
|
12
|
+
// `forSelf`, the parent eats it, and the worker stays parked.
|
|
13
|
+
//
|
|
14
|
+
// So OPE-1275 traded an INVISIBLE unanswerable question for a VISIBLE one. That is a net improvement
|
|
15
|
+
// — a human now sees the turn is waiting — but the false promise is new: the human answers in the
|
|
16
|
+
// thread and believes they answered. This module is the leg that makes the belief true.
|
|
17
|
+
//
|
|
18
|
+
// 🔴 THE GAP IS THE NATIVE ROUTE'S, NOT NOTION'S. `rememberBridgedQuestion` /
|
|
19
|
+
// `resolveInboundAnswerRouting` are core's and are surface-agnostic; what was unified-only was the
|
|
20
|
+
// WIRING (`channels/unified.ts` `divertToUnifiedWindow` reads the row). Every native surface posts
|
|
21
|
+
// eve's own default prompt and had no return leg either. Notion is the surface that needs it first
|
|
22
|
+
// because it is the only one with an OE-authored handler, so this module is written as the pattern
|
|
23
|
+
// the next native surface copies: nothing below knows anything about Notion except the two shapes at
|
|
24
|
+
// the top, and the decision itself is core's.
|
|
25
|
+
//
|
|
26
|
+
// ── THE THREE THINGS A CALLER OWES, ALL OF THEM ─────────────────────────────────────────────────
|
|
27
|
+
// 1. {@link rememberNotionQuestion} on a question that was actually POSTED. A dropped question must
|
|
28
|
+
// never eat the room's next comment: nobody was shown anything to answer.
|
|
29
|
+
// 2. {@link resolveNotionInboundAnswer} on every inbound comment, BEFORE the ordinary send.
|
|
30
|
+
// 3. {@link forgetNotionQuestion} the moment an answer is delivered. CONSUME-ONCE: without it the
|
|
31
|
+
// comment AFTER the answer is delivered as an answer too, to a request eve has already settled.
|
|
32
|
+
//
|
|
33
|
+
// ── KEYING: THE TOKEN, AND THE TWO SIDES THAT SPELL IT DIFFERENTLY ──────────────────────────────
|
|
34
|
+
// 🔴 The row is keyed by the session token, exactly as the unified window keys its own by the window
|
|
35
|
+
// token. The two call sites hold that token in DIFFERENT spellings, and getting this wrong is a
|
|
36
|
+
// silent no-op rather than an error:
|
|
37
|
+
// • the `input.requested` handler reads `channel.continuation.token`, which eve NAMESPACES with the
|
|
38
|
+
// channel definition name — `notion:notion:<pageId>:oe-sup:g<n>`;
|
|
39
|
+
// • the dispatch holds the BARE token it minted — `notion:<pageId>:oe-sup:g<n>`.
|
|
40
|
+
// `bareTokenFromEveContext` strips ONE leading segment, so it must be applied to the namespaced side
|
|
41
|
+
// ONLY. Applied to the bare side it would strip the real `notion:` head and key a different row —
|
|
42
|
+
// core's own header states that ambiguity and resolves it toward the strip, because that helper is
|
|
43
|
+
// for eve-context tokens. {@link notionQuestionKey} is the one place either side goes through, so
|
|
44
|
+
// they cannot drift apart.
|
|
45
|
+
//
|
|
46
|
+
// ── THE ROOM IS PART OF THE DECISION ────────────────────────────────────────────────────────────
|
|
47
|
+
// 🔴 The row carries the DISCUSSION the question was posted into, and an inbound comment from any
|
|
48
|
+
// other discussion is an ordinary wake. Without it, a comment on a different discussion of the same
|
|
49
|
+
// page would be consumed as the answer: that person is never heard, and the worker resumes on text
|
|
50
|
+
// from a conversation that never saw its question. This is the same containerKey comparison the
|
|
51
|
+
// unified window runs, and it fails soft in the one safe direction — an absent or mismatched key is
|
|
52
|
+
// a WAKE with the row LEFT IN PLACE, so the question stays open for the right room.
|
|
53
|
+
//
|
|
54
|
+
// ── SELF-AUTHORED COMMENTS CANNOT REACH HERE ────────────────────────────────────────────────────
|
|
55
|
+
// `internalDispatch` is passed `false`, and that is a measured fact rather than an assumption:
|
|
56
|
+
// `classifyNotionComment` (notion-supervisor.ts) DROPS a comment whose author is the bot user before
|
|
57
|
+
// any dispatch happens, so the agent's own reply into the discussion never becomes a wake and can
|
|
58
|
+
// never be consumed as an answer to its own question.
|
|
59
|
+
import { admitInputResponse, bareTokenFromEveContext, bindFrom, forgetBridgedQuestion, getOperationalStore, readBridgedQuestion, rememberBridgedQuestion, resolvePartition, resolveInboundAnswerRouting, } from "@open-everywhere/core/agent/adapter-api.js";
|
|
60
|
+
/** The room a question is showing in, in the vocabulary the row stores. A discussion, not a page:
|
|
61
|
+
* a page's other discussions are other conversations, and none of them saw this question. */
|
|
62
|
+
export function notionQuestionContainerKey(discussionId) {
|
|
63
|
+
return `notion:${discussionId}`;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The store key for one Notion session's outstanding question, from either side's spelling of the
|
|
67
|
+
* token. `namespaced` says which side is calling — see the header's keying paragraph, which is the
|
|
68
|
+
* whole reason this function exists instead of a bare token argument at two call sites.
|
|
69
|
+
*
|
|
70
|
+
* Returns `undefined` for an unusable token, which every caller treats as "no leg": the question is
|
|
71
|
+
* posted and simply not answerable, exactly as it was before this module existed.
|
|
72
|
+
*/
|
|
73
|
+
export function notionQuestionKey(token, namespaced) {
|
|
74
|
+
if (typeof token !== "string" || token.length === 0)
|
|
75
|
+
return undefined;
|
|
76
|
+
return namespaced ? bareTokenFromEveContext(token) : token;
|
|
77
|
+
}
|
|
78
|
+
/** The discussion id on a delivery's auth attributes, or `undefined`. */
|
|
79
|
+
function notionDiscussionIdOf(auth) {
|
|
80
|
+
const v = auth?.attributes?.["discussion_id"];
|
|
81
|
+
return typeof v === "string" && v.length > 0 ? v : undefined;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Record that this session is parked on a question the discussion can see.
|
|
85
|
+
*
|
|
86
|
+
* NEVER THROWS — it runs inside a channel event handler on an ALREADY-PARKED turn, and a store blip
|
|
87
|
+
* must not break the event dispatch on top of it. Returns whether the row was written, so the caller
|
|
88
|
+
* can log the difference between "this discussion can answer" and "it cannot".
|
|
89
|
+
*/
|
|
90
|
+
export async function rememberNotionQuestion(input) {
|
|
91
|
+
const key = notionQuestionKey(input.namespacedToken, true);
|
|
92
|
+
if (key === undefined)
|
|
93
|
+
return false;
|
|
94
|
+
return rememberBridgedQuestion(getOperationalStore(), input.org, key, {
|
|
95
|
+
requestId: input.requestId,
|
|
96
|
+
containerKey: notionQuestionContainerKey(input.discussionId),
|
|
97
|
+
...(input.wakeId ? { wakeId: input.wakeId } : {}),
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Is this inbound comment the answer to the session's outstanding question, and may THIS discussion
|
|
102
|
+
* give it?
|
|
103
|
+
*
|
|
104
|
+
* FAIL-SOFT TO AN ORDINARY WAKE, in every direction. No row, an unreadable store, a comment on
|
|
105
|
+
* another discussion, a refused answerer, a thrown floor — all answer `undefined`, which is the
|
|
106
|
+
* behaviour every comment had before this leg existed. The comment is delivered, the seat hears it,
|
|
107
|
+
* and the question stays open for someone who may answer it. Nothing here may drop a comment and
|
|
108
|
+
* nothing here may throw.
|
|
109
|
+
*
|
|
110
|
+
* 🔴 THE ADMISSION IS THE SAME ONE EVERY OTHER ARM RUNS (OPE-574). Five call sites run
|
|
111
|
+
* {@link admitInputResponse} — three WIDGET arms and two ROOM-MESSAGE arms, which core
|
|
112
|
+
* approvals/input-response-floor.ts lists by file. This is the second ROOM-MESSAGE arm. A comment
|
|
113
|
+
* that resolves a worker's question is the same act as a click on that question's widget over a
|
|
114
|
+
* different transport, so it must meet the same floor. Letting the transport decide would make the
|
|
115
|
+
* gate cosmetic.
|
|
116
|
+
*
|
|
117
|
+
* 🔴 BOTH LEGS ARE INERT ON THIS ARM, so the admission refuses nothing here. Leg B
|
|
118
|
+
* (`answererIsGuest`) is not passed: Notion carries no per-member membership probe, and `undefined`
|
|
119
|
+
* is always inert. Leg A, the room floor, needs a governance member supplier for the room, and the
|
|
120
|
+
* Slack bundle registers the only production one (its channels/slack-events.ts), so no roster
|
|
121
|
+
* resolves for a Notion discussion. The unified window's own room-message arm is NOT the
|
|
122
|
+
* same case: it takes the ORIGIN platform for the partition, so a Slack-origin room message
|
|
123
|
+
* resolves a `slack/...` key and leg A does have teeth there.
|
|
124
|
+
*
|
|
125
|
+
* What carries THIS path instead is the question-to-room binding inside
|
|
126
|
+
* `resolveInboundAnswerRouting`: only the discussion the question was posted into may answer it,
|
|
127
|
+
* and core channels/question-bridge.ts answers `foreign-room` on any mismatch.
|
|
128
|
+
*/
|
|
129
|
+
export async function resolveNotionInboundAnswer(input) {
|
|
130
|
+
try {
|
|
131
|
+
const key = notionQuestionKey(input.bareToken, false);
|
|
132
|
+
if (key === undefined)
|
|
133
|
+
return undefined;
|
|
134
|
+
const discussionId = notionDiscussionIdOf(input.auth);
|
|
135
|
+
const routing = await resolveInboundAnswerRouting({
|
|
136
|
+
// See the header: a self-authored comment is dropped before dispatch, so nothing that
|
|
137
|
+
// reaches this function is a host-authored notification.
|
|
138
|
+
internalDispatch: false,
|
|
139
|
+
...(discussionId ? { containerKey: notionQuestionContainerKey(discussionId) } : {}),
|
|
140
|
+
}, {
|
|
141
|
+
read: () => readBridgedQuestion(getOperationalStore(), input.org, key),
|
|
142
|
+
admit: async () => {
|
|
143
|
+
// The partition the PLATFORM names for this comment, read through Notion's own registered
|
|
144
|
+
// identity mapper — the room the floor must judge is the one the comment was really made
|
|
145
|
+
// in, not a label the caller passes.
|
|
146
|
+
const bindable = {
|
|
147
|
+
channel: { kind: "notion" },
|
|
148
|
+
session: { auth: { current: input.auth } },
|
|
149
|
+
};
|
|
150
|
+
const p = resolvePartition(bindable);
|
|
151
|
+
return admitInputResponse({
|
|
152
|
+
org: p.org,
|
|
153
|
+
platform: p.platform,
|
|
154
|
+
workspace: p.workspace,
|
|
155
|
+
...(p.surface === undefined ? {} : { surface: p.surface }),
|
|
156
|
+
surfaceKey: p.surfaceKey,
|
|
157
|
+
principalId: bindFrom(bindable).principalId ?? "",
|
|
158
|
+
responses: 1,
|
|
159
|
+
});
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
if (routing.kind !== "answer")
|
|
163
|
+
return undefined;
|
|
164
|
+
return {
|
|
165
|
+
requestId: routing.requestId,
|
|
166
|
+
floor: routing.floor,
|
|
167
|
+
...(routing.wakeId !== undefined ? { wakeId: routing.wakeId } : {}),
|
|
168
|
+
...(routing.taskId !== undefined ? { taskId: routing.taskId } : {}),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
// A decision that threw is a decision we do not have. One ordinary wake, never a lost comment.
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/** Consume the row. Called ONLY after eve has accepted the answer — see the header's rule 3. */
|
|
177
|
+
export async function forgetNotionQuestion(org, bareToken) {
|
|
178
|
+
const key = notionQuestionKey(bareToken, false);
|
|
179
|
+
if (key === undefined)
|
|
180
|
+
return false;
|
|
181
|
+
return forgetBridgedQuestion(getOperationalStore(), org, key);
|
|
182
|
+
}
|
|
183
|
+
//# sourceMappingURL=notion-question-return.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notion-question-return.js","sourceRoot":"","sources":["../../../agent/channels/notion-question-return.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type BindableContext, type RenderedLine, type WakeReadAddress } from "@open-everywhere/core/agent/adapter-api.js";
|
|
2
|
+
import { type NotionCallOpts, type NotionCommentSummary } from "./notion-api.js";
|
|
3
|
+
/** How much of a discussion's anchor block the header quotes. A cap, not a preference: `read_channel`
|
|
4
|
+
* is the WIDER view, so a page of long anchor blocks must not become the page's prose by another
|
|
5
|
+
* route (see the header's page-content rule). */
|
|
6
|
+
export declare const ANCHOR_SNIPPET_MAX = 120;
|
|
7
|
+
/** Test/transport seam — the same DI shape every notion tool takes. `call` supplies the resolved
|
|
8
|
+
* credential (and an injectable fetch); absent ⇒ this session's own Notion credential. */
|
|
9
|
+
export interface NotionReadDeps {
|
|
10
|
+
readonly call?: NotionCallOpts;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The header line that OPENS a discussion — its id and what it is anchored to. PURE and exported for
|
|
14
|
+
* the render test.
|
|
15
|
+
*
|
|
16
|
+
* It is the rendering half of the thread-first ruling: it names the discussion, so a reader can never
|
|
17
|
+
* take the next top-level comment for a reply to the previous one. The anchor falls back to the block
|
|
18
|
+
* id when the sweep read no block text, and to "the page" when the comment is page-level — three
|
|
19
|
+
* honest answers rather than one invented snippet.
|
|
20
|
+
*/
|
|
21
|
+
export declare function notionDiscussionHeader(c: NotionCommentSummary, pageId: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* The CHANNEL projection — every discussion on the page, each opened by its header. PURE and exported
|
|
24
|
+
* for the render test.
|
|
25
|
+
*
|
|
26
|
+
* 🔴 THE HEADER ON EVERY DISCUSSION'S FIRST LINE IS THE POINT, not decoration. Without it two sibling
|
|
27
|
+
* discussions render as consecutive `[author] text` lines, which is exactly the channel-style
|
|
28
|
+
* continuation the ruling forbids — the model would read the second commenter as answering the first
|
|
29
|
+
* when the two never addressed each other.
|
|
30
|
+
*/
|
|
31
|
+
export declare function notionChannelLines(comments: readonly NotionCommentSummary[], pageId: string): RenderedLine[];
|
|
32
|
+
export declare const notionReadAdapter: {
|
|
33
|
+
platform: "notion";
|
|
34
|
+
/**
|
|
35
|
+
* OPE-929 — Notion's own spelling of "which container and which thread is this turn in".
|
|
36
|
+
*
|
|
37
|
+
* Both ids come off the binding's post-back target, which `bindNotion` fills from the wake's
|
|
38
|
+
* `page_id` and `discussion_id` attributes. The CONTAINER is the PAGE and the THREAD ROOT is the
|
|
39
|
+
* DISCUSSION.
|
|
40
|
+
*
|
|
41
|
+
* 🔴 THE PAGE IS READ FROM `target.pageId`, NOT from the binding's `surface`. They usually agree,
|
|
42
|
+
* but `bindNotion`'s surface falls back through discussion id and then comment id when no page id
|
|
43
|
+
* arrived — so on an under-identified wake `surface` holds a discussion id, and a read against it
|
|
44
|
+
* would list a discussion id's comments as if it were a page. Naming the page id directly means an
|
|
45
|
+
* under-identified turn resolves NO container and the caller refuses loudly.
|
|
46
|
+
*
|
|
47
|
+
* 🔴 THE THREAD SLOT IS ALWAYS POPULATED WHEN THE CONTAINER IS. `bindNotion` builds `target` only
|
|
48
|
+
* when a discussion id exists, so `pageId` present implies `discussionId` present. That is the
|
|
49
|
+
* thread-first ruling made structural rather than conventional — see the header.
|
|
50
|
+
*/
|
|
51
|
+
wakeAddress(ctx: BindableContext): WakeReadAddress | undefined;
|
|
52
|
+
/** The page's discussions — the deliberate WIDER view. `channel` is the page id. */
|
|
53
|
+
readChannel(ctx: BindableContext, channel: string, limit?: number, deps?: NotionReadDeps): Promise<readonly RenderedLine[]>;
|
|
54
|
+
/**
|
|
55
|
+
* ONE discussion — the conversation this turn is in. `threadTs` is the `discussion_id`.
|
|
56
|
+
*
|
|
57
|
+
* Two moves, cheapest first (see the header): the page's own comments, which serve every page-level
|
|
58
|
+
* discussion in a single call, then the bounded block sweep for an inline one.
|
|
59
|
+
*/
|
|
60
|
+
readThread(ctx: BindableContext, channel: string, threadTs: string, limit?: number, deps?: NotionReadDeps): Promise<readonly RenderedLine[]>;
|
|
61
|
+
};
|
|
62
|
+
//# sourceMappingURL=notion-read.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notion-read.d.ts","sourceRoot":"","sources":["../../../agent/channels/notion-read.ts"],"names":[],"mappings":"AAwDA,OAAO,EAGL,KAAK,eAAe,EAEpB,KAAK,YAAY,EACjB,KAAK,eAAe,EACrB,MAAM,4CAA4C,CAAC;AACpD,OAAO,EAKL,KAAK,cAAc,EACnB,KAAK,oBAAoB,EAC1B,MAAM,iBAAiB,CAAC;AAKzB;;kDAEkD;AAClD,eAAO,MAAM,kBAAkB,MAAM,CAAC;AAUtC;2FAC2F;AAC3F,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,CAAC,EAAE,cAAc,CAAC;CAChC;AAwBD;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAStF;AAuBD;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,SAAS,oBAAoB,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,YAAY,EAAE,CAS5G;AAmCD,eAAO,MAAM,iBAAiB;IAC5B,QAAQ,EAAE,QAAQ;IAClB;;;;;;;;;;;;;;;;OAgBG;IACH,WAAW,MAAM,eAAe,GAAG,eAAe,GAAG,SAAS;IAO9D,oFAAoF;IAC9E,WAAW,MAAM,eAAe,WAAW,MAAM,UAAU,MAAM,SAAS,cAAc,GAAG,OAAO,CAAC,SAAS,YAAY,EAAE,CAAC;IAcjI;;;;;OAKG;IACG,UAAU,MAAM,eAAe,WAAW,MAAM,YAAY,MAAM,UAAU,MAAM,SAAS,cAAc,GAAG,OAAO,CAAC,SAAS,YAAY,EAAE,CAAC;CAwBnJ,CAAC"}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
// The NOTION read adapter (OPE-988) — the sixth surface's arm of the uniform chat read, and the one
|
|
2
|
+
// whose container model is NOT Slack's.
|
|
3
|
+
//
|
|
4
|
+
// WHAT IT CLOSES. `read_channel` / `read_thread` mount whenever a window has sources and the generic
|
|
5
|
+
// read flag is armed, so on a UNIFIED window they were OFFERED to a Notion-origin turn and then threw
|
|
6
|
+
// "reading a notion channel is not supported yet" at execute time (core channels/channel-read.ts
|
|
7
|
+
// `resolveReadAdapter`). That is the OPE-927 mount-honesty class. A Notion seat read through
|
|
8
|
+
// `read_notion_page` / `read_notion_comments`, vendor verbs, so a Slack seat and a Notion seat held
|
|
9
|
+
// different verbs for one conceptual act.
|
|
10
|
+
//
|
|
11
|
+
// 🔴 NOTION IS THREAD-FIRST, AND THAT IS TOMMY'S RULING (2026-08-22, on the ticket). It is the
|
|
12
|
+
// INVERSE of Slack, and the difference is in the platform, not in our modelling of it:
|
|
13
|
+
// • A Slack channel message has NO thread id until somebody replies — the root's `ts` becomes the
|
|
14
|
+
// thread id at that moment. Top-level messages in a Slack channel ARE one conversation.
|
|
15
|
+
// • A Notion comment carries `discussion_id` FROM BIRTH. A new top-level comment MINTS a new
|
|
16
|
+
// discussion. There is no bare-channel state on Notion, and sibling discussions are UNRELATED —
|
|
17
|
+
// each one is anchored to its own block and is about that block.
|
|
18
|
+
// The ruling that follows: a Notion wake always arrives THREAD-SCOPED, and a new top-level comment
|
|
19
|
+
// must NEVER render as a channel-style continuation of the previous top-level comment.
|
|
20
|
+
//
|
|
21
|
+
// 🔴 THE RULING IS STRUCTURAL HERE, NOT A CONVENTION. {@link notionReadAdapter.wakeAddress} reads
|
|
22
|
+
// both ids off the binding's post-back target, and `bindNotion` (channels/notion-identity.ts) builds
|
|
23
|
+
// that target ONLY when a discussion id is present. So a Notion turn either names a container AND a
|
|
24
|
+
// thread root, or it names neither. There is no turn shape that resolves a Notion container with an
|
|
25
|
+
// empty thread slot, which is what "always thread-scoped" has to mean if it is to survive an edit.
|
|
26
|
+
//
|
|
27
|
+
// 🔴 THE PROJECTION, THEN, IS TWO HONEST VIEWS OF ONE CONTRACT:
|
|
28
|
+
// • `read_thread` = ONE discussion — the conversation this turn is actually in. The DEFAULT view.
|
|
29
|
+
// • `read_channel` = the page's discussions, each opened by a header naming its id and quoting its
|
|
30
|
+
// anchor block. The deliberate WIDER view: what else is being discussed in this document.
|
|
31
|
+
// Every discussion's FIRST line carries that header, so two sibling discussions can never read as
|
|
32
|
+
// one conversation — the second one visibly starts a new discussion. That is the rendering half of
|
|
33
|
+
// the ruling, and {@link notionChannelLines} is where it lives.
|
|
34
|
+
//
|
|
35
|
+
// 🔴 PAGE CONTENT STAYS OUT OF THE CHAT PLANE. The page is the WORK object; the COMMENT LAYER is
|
|
36
|
+
// Notion's chat surface. So neither verb reads block prose — only the anchor snippet a discussion
|
|
37
|
+
// needs to be intelligible, capped at {@link ANCHOR_SNIPPET_MAX}. A seat that needs the document
|
|
38
|
+
// itself uses the grant-mounted `read_notion_page` or delegates to a worker.
|
|
39
|
+
//
|
|
40
|
+
// THE FENCE is the shipped `assertNotionPageAllowed` (channels/notion-scope.ts): it fails closed with
|
|
41
|
+
// a `ChannelScopeError` and audits its refusals, exactly as every other surface's read fence does.
|
|
42
|
+
// The one deployment-wide integration key bounds the WORKSPACE and never the domain, which is why the
|
|
43
|
+
// domain allowlist is ours to enforce and why it runs BEFORE any fetch.
|
|
44
|
+
//
|
|
45
|
+
// THE THREAD READ'S COST, STATED PLAINLY. Notion exposes no "list a discussion's comments" endpoint —
|
|
46
|
+
// comments are listed per BLOCK id (`GET /v1/comments?block_id=…`). So a discussion is found by
|
|
47
|
+
// listing and filtering: the page's own comments first (one call, which serves every page-level
|
|
48
|
+
// discussion), then the bounded block sweep `readNotionComments` already performs. That is the same
|
|
49
|
+
// cost the shipped `read_notion_comments` pays on every call, so this verb is no more expensive than
|
|
50
|
+
// the vendor verb it replaces.
|
|
51
|
+
//
|
|
52
|
+
// NO `refutedAuthorIds`. The core contract calls its absence a real answer, and channel-read.ts names
|
|
53
|
+
// this surface: Notion reads are classified by their own taint-table rows. Notion also refutes no
|
|
54
|
+
// author — its trust is an operator declaration (a page under a declared trust root, or a per-surface
|
|
55
|
+
// vouch), never a platform verdict — so there is no per-author evidence to contribute.
|
|
56
|
+
import { bindFrom, getConfig, } from "@open-everywhere/core/agent/adapter-api.js";
|
|
57
|
+
import { listBlockComments, readNotionComments, resolveNotionCredential, toNotionCommentSummary, } from "./notion-api.js";
|
|
58
|
+
import { notionCallerCredential } from "./notion-credentials.js";
|
|
59
|
+
import { assertNotionActingIdentity } from "./notion-acting-identity.js";
|
|
60
|
+
import { assertNotionPageAllowed } from "./notion-scope.js";
|
|
61
|
+
/** How much of a discussion's anchor block the header quotes. A cap, not a preference: `read_channel`
|
|
62
|
+
* is the WIDER view, so a page of long anchor blocks must not become the page's prose by another
|
|
63
|
+
* route (see the header's page-content rule). */
|
|
64
|
+
export const ANCHOR_SNIPPET_MAX = 120;
|
|
65
|
+
const NOTION_CHANNEL_DEFAULT_LIMIT = 50;
|
|
66
|
+
const NOTION_THREAD_DEFAULT_LIMIT = 100;
|
|
67
|
+
/** Blocks one sweep will scan — `readNotionComments`' own default, restated so both verbs share it. */
|
|
68
|
+
const NOTION_MAX_BLOCK_SCANS = 80;
|
|
69
|
+
/** Comments one THREAD sweep may collect before the filter runs. Higher than the thread limit on
|
|
70
|
+
* purpose: the cap applies to the WHOLE page, and a cap that bit before reaching the target
|
|
71
|
+
* discussion would render an existing thread as an empty one. */
|
|
72
|
+
const NOTION_THREAD_SWEEP_COMMENTS = 300;
|
|
73
|
+
function safeDomains() {
|
|
74
|
+
try {
|
|
75
|
+
return getConfig().loadDomains();
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/** One Notion author rendered into the neutral line's fields. A BOT is marked a machine for the same
|
|
82
|
+
* reason every other adapter marks one: a bot sits below peer standing and is never an outside
|
|
83
|
+
* person. An unattributed comment carries neither id nor mark. */
|
|
84
|
+
function authorFields(c) {
|
|
85
|
+
if (!c.authorId)
|
|
86
|
+
return { author: "(unknown)" };
|
|
87
|
+
const author = `${c.authorKind ?? "user"} ${c.authorId}`;
|
|
88
|
+
return c.authorKind === "bot" ? { author, bot: true } : { author, authorId: c.authorId };
|
|
89
|
+
}
|
|
90
|
+
function clip(text, max) {
|
|
91
|
+
const flat = text.replace(/\s+/gu, " ").trim();
|
|
92
|
+
return flat.length <= max ? flat : `${flat.slice(0, max - 1)}…`;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The header line that OPENS a discussion — its id and what it is anchored to. PURE and exported for
|
|
96
|
+
* the render test.
|
|
97
|
+
*
|
|
98
|
+
* It is the rendering half of the thread-first ruling: it names the discussion, so a reader can never
|
|
99
|
+
* take the next top-level comment for a reply to the previous one. The anchor falls back to the block
|
|
100
|
+
* id when the sweep read no block text, and to "the page" when the comment is page-level — three
|
|
101
|
+
* honest answers rather than one invented snippet.
|
|
102
|
+
*/
|
|
103
|
+
export function notionDiscussionHeader(c, pageId) {
|
|
104
|
+
const id = c.discussionId ?? "(unknown)";
|
|
105
|
+
const anchored = c.anchorText && c.anchorText.trim().length > 0
|
|
106
|
+
? `on "${clip(c.anchorText, ANCHOR_SNIPPET_MAX)}"`
|
|
107
|
+
: c.anchorId && c.anchorId !== pageId
|
|
108
|
+
? `on block ${c.anchorId}`
|
|
109
|
+
: "on the page";
|
|
110
|
+
return `[discussion ${id} — ${anchored}]`;
|
|
111
|
+
}
|
|
112
|
+
/** Group a flat comment page into discussions, PRESERVING the sweep's order (the page's own comments
|
|
113
|
+
* first, then its blocks in document order). Document order is the meaningful one on Notion: a
|
|
114
|
+
* discussion is anchored to a place in the page, not to a moment in a conversation. */
|
|
115
|
+
function byDiscussion(comments) {
|
|
116
|
+
const groups = new Map();
|
|
117
|
+
const order = [];
|
|
118
|
+
for (const c of comments) {
|
|
119
|
+
// A comment with no discussion id is its own group keyed by anchor — it can still be rendered
|
|
120
|
+
// honestly, and it must never be merged into a real discussion it is not part of.
|
|
121
|
+
const key = c.discussionId ?? `anchor:${c.anchorId ?? "unknown"}`;
|
|
122
|
+
let group = groups.get(key);
|
|
123
|
+
if (!group) {
|
|
124
|
+
group = [];
|
|
125
|
+
groups.set(key, group);
|
|
126
|
+
order.push(key);
|
|
127
|
+
}
|
|
128
|
+
group.push(c);
|
|
129
|
+
}
|
|
130
|
+
return order.map((key) => groups.get(key) ?? []);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* The CHANNEL projection — every discussion on the page, each opened by its header. PURE and exported
|
|
134
|
+
* for the render test.
|
|
135
|
+
*
|
|
136
|
+
* 🔴 THE HEADER ON EVERY DISCUSSION'S FIRST LINE IS THE POINT, not decoration. Without it two sibling
|
|
137
|
+
* discussions render as consecutive `[author] text` lines, which is exactly the channel-style
|
|
138
|
+
* continuation the ruling forbids — the model would read the second commenter as answering the first
|
|
139
|
+
* when the two never addressed each other.
|
|
140
|
+
*/
|
|
141
|
+
export function notionChannelLines(comments, pageId) {
|
|
142
|
+
const lines = [];
|
|
143
|
+
for (const group of byDiscussion(comments)) {
|
|
144
|
+
group.forEach((c, index) => {
|
|
145
|
+
const body = index === 0 ? `${notionDiscussionHeader(c, pageId)}\n${c.text}` : c.text;
|
|
146
|
+
lines.push({ ...authorFields(c), ts: c.createdTime ?? "", text: body });
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
return lines;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* The THREAD projection — one discussion, opened by the same header so its anchor block is quoted as
|
|
153
|
+
* the topic. PURE — the render tests reach it through this module's read entry points.
|
|
154
|
+
*/
|
|
155
|
+
function notionThreadLines(comments, discussionId, pageId) {
|
|
156
|
+
const mine = comments.filter((c) => c.discussionId === discussionId);
|
|
157
|
+
return mine.map((c, index) => ({
|
|
158
|
+
...authorFields(c),
|
|
159
|
+
ts: c.createdTime ?? "",
|
|
160
|
+
text: index === 0 ? `${notionDiscussionHeader(c, pageId)}\n${c.text}` : c.text,
|
|
161
|
+
}));
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Fence the page, then resolve the credential to read it with. The FIRST steps of both reads, in one
|
|
165
|
+
* place so they can never come apart. The fence runs BEFORE the credential resolves, so a refused
|
|
166
|
+
* page never reaches a call that could fetch it.
|
|
167
|
+
*/
|
|
168
|
+
async function admit(ctx, page, deps) {
|
|
169
|
+
const domains = safeDomains();
|
|
170
|
+
await assertNotionPageAllowed(ctx, page, domains);
|
|
171
|
+
// 🔴 OPE-1525 — THE OWN-ACCOUNT GATE, and it runs BEFORE the injected-call short circuit below.
|
|
172
|
+
// This function backs the generic `read_channel` / `read_thread` pair, which read_notion_page.ts's
|
|
173
|
+
// own header calls its SUPERSET — so gating that tool and leaving this one would hand a worker the
|
|
174
|
+
// same pages one verb along. Every credential resolved below is `install`-family, so a member who
|
|
175
|
+
// asked to act as themselves has no answer here. See channels/notion-acting-identity.ts.
|
|
176
|
+
await assertNotionActingIdentity(ctx, "notion_read");
|
|
177
|
+
if (deps?.call)
|
|
178
|
+
return deps.call;
|
|
179
|
+
const cred = await resolveNotionCredential(notionCallerCredential(ctx, domains));
|
|
180
|
+
if (!cred)
|
|
181
|
+
throw new Error("read: no Notion credential is stored for this session, so nothing can be read.");
|
|
182
|
+
return cred;
|
|
183
|
+
}
|
|
184
|
+
export const notionReadAdapter = {
|
|
185
|
+
platform: "notion",
|
|
186
|
+
/**
|
|
187
|
+
* OPE-929 — Notion's own spelling of "which container and which thread is this turn in".
|
|
188
|
+
*
|
|
189
|
+
* Both ids come off the binding's post-back target, which `bindNotion` fills from the wake's
|
|
190
|
+
* `page_id` and `discussion_id` attributes. The CONTAINER is the PAGE and the THREAD ROOT is the
|
|
191
|
+
* DISCUSSION.
|
|
192
|
+
*
|
|
193
|
+
* 🔴 THE PAGE IS READ FROM `target.pageId`, NOT from the binding's `surface`. They usually agree,
|
|
194
|
+
* but `bindNotion`'s surface falls back through discussion id and then comment id when no page id
|
|
195
|
+
* arrived — so on an under-identified wake `surface` holds a discussion id, and a read against it
|
|
196
|
+
* would list a discussion id's comments as if it were a page. Naming the page id directly means an
|
|
197
|
+
* under-identified turn resolves NO container and the caller refuses loudly.
|
|
198
|
+
*
|
|
199
|
+
* 🔴 THE THREAD SLOT IS ALWAYS POPULATED WHEN THE CONTAINER IS. `bindNotion` builds `target` only
|
|
200
|
+
* when a discussion id exists, so `pageId` present implies `discussionId` present. That is the
|
|
201
|
+
* thread-first ruling made structural rather than conventional — see the header.
|
|
202
|
+
*/
|
|
203
|
+
wakeAddress(ctx) {
|
|
204
|
+
const target = bindFrom(ctx).target;
|
|
205
|
+
const container = typeof target?.pageId === "string" && target.pageId.length > 0 ? target.pageId : undefined;
|
|
206
|
+
if (!container)
|
|
207
|
+
return undefined;
|
|
208
|
+
const threadRoot = typeof target?.discussionId === "string" && target.discussionId.length > 0 ? target.discussionId : undefined;
|
|
209
|
+
return { container, ...(threadRoot ? { threadRoot } : {}) };
|
|
210
|
+
},
|
|
211
|
+
/** The page's discussions — the deliberate WIDER view. `channel` is the page id. */
|
|
212
|
+
async readChannel(ctx, channel, limit, deps) {
|
|
213
|
+
const call = await admit(ctx, channel, deps);
|
|
214
|
+
const effective = Math.max(1, limit ?? NOTION_CHANNEL_DEFAULT_LIMIT);
|
|
215
|
+
// The sweep's comment cap IS the caller's limit, which is what keeps core's coverage disclosure
|
|
216
|
+
// honest: a capped sweep returns exactly `effective` lines, and `possiblyIncomplete` keys on that
|
|
217
|
+
// count. A larger fetch trimmed afterwards would report a partial page as a complete one.
|
|
218
|
+
const res = await readNotionComments(channel, { ...call, maxComments: effective, maxBlockScans: NOTION_MAX_BLOCK_SCANS });
|
|
219
|
+
// OPE-1039 — stamped AFTER the fetch returns (read_notion_comments.ts's own `readAt` rule). NO
|
|
220
|
+
// `live` line: the page's own prose stays out of the chat plane by ruling (see the header), so a
|
|
221
|
+
// generic Notion read has no live field to distinguish from history — every line here is a
|
|
222
|
+
// comment, dated against this moment, and that is the whole of what this surface can offer.
|
|
223
|
+
const readAt = new Date().toISOString();
|
|
224
|
+
return notionChannelLines(res.comments, channel).map((line) => ({ ...line, readAt }));
|
|
225
|
+
},
|
|
226
|
+
/**
|
|
227
|
+
* ONE discussion — the conversation this turn is in. `threadTs` is the `discussion_id`.
|
|
228
|
+
*
|
|
229
|
+
* Two moves, cheapest first (see the header): the page's own comments, which serve every page-level
|
|
230
|
+
* discussion in a single call, then the bounded block sweep for an inline one.
|
|
231
|
+
*/
|
|
232
|
+
async readThread(ctx, channel, threadTs, limit, deps) {
|
|
233
|
+
const call = await admit(ctx, channel, deps);
|
|
234
|
+
const pageLevel = (await listBlockComments(channel, call)).filter((c) => c.discussion_id === threadTs);
|
|
235
|
+
if (pageLevel.length > 0) {
|
|
236
|
+
// No anchor text on this path: a page-level comment anchors to the PAGE, which has no block
|
|
237
|
+
// prose, so the header says "on the page" rather than quoting something.
|
|
238
|
+
const summaries = pageLevel.map((c) => toNotionCommentSummary(c, channel));
|
|
239
|
+
// OPE-1039 — see readChannel's note: dated, no live line.
|
|
240
|
+
const readAt = new Date().toISOString();
|
|
241
|
+
return notionThreadLines(summaries, threadTs, channel)
|
|
242
|
+
.map((line) => ({ ...line, readAt }))
|
|
243
|
+
.slice(-(limit ?? NOTION_THREAD_DEFAULT_LIMIT));
|
|
244
|
+
}
|
|
245
|
+
const res = await readNotionComments(channel, {
|
|
246
|
+
...call,
|
|
247
|
+
maxComments: NOTION_THREAD_SWEEP_COMMENTS,
|
|
248
|
+
maxBlockScans: NOTION_MAX_BLOCK_SCANS,
|
|
249
|
+
});
|
|
250
|
+
// OPE-1039 — see readChannel's note: dated, no live line.
|
|
251
|
+
const readAt = new Date().toISOString();
|
|
252
|
+
return notionThreadLines(res.comments, threadTs, channel)
|
|
253
|
+
.map((line) => ({ ...line, readAt }))
|
|
254
|
+
.slice(-(limit ?? NOTION_THREAD_DEFAULT_LIMIT));
|
|
255
|
+
},
|
|
256
|
+
};
|
|
257
|
+
//# sourceMappingURL=notion-read.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notion-read.js","sourceRoot":"","sources":["../../../agent/channels/notion-read.ts"],"names":[],"mappings":"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"}
|