@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,64 @@
|
|
|
1
|
+
import { type NotionSigningKey } from "./notion-api.js";
|
|
2
|
+
export declare const NOTION_INGRESS_SOURCE = "notion";
|
|
3
|
+
declare const NOTION_INGRESS_SCHEMA_VERSION: 1;
|
|
4
|
+
export declare const NOTION_INGRESS_MAX_BODY_BYTES: number;
|
|
5
|
+
export interface ProjectedNotionEvent {
|
|
6
|
+
readonly id: string;
|
|
7
|
+
readonly timestamp?: string;
|
|
8
|
+
readonly type?: string;
|
|
9
|
+
readonly event_type?: string;
|
|
10
|
+
readonly workspace_id?: string;
|
|
11
|
+
readonly entity?: {
|
|
12
|
+
readonly id?: string;
|
|
13
|
+
readonly type?: string;
|
|
14
|
+
};
|
|
15
|
+
readonly data?: {
|
|
16
|
+
readonly page_id?: string;
|
|
17
|
+
readonly parent?: {
|
|
18
|
+
readonly id?: string;
|
|
19
|
+
readonly type?: string;
|
|
20
|
+
};
|
|
21
|
+
readonly updated_blocks?: readonly {
|
|
22
|
+
readonly id?: string;
|
|
23
|
+
}[];
|
|
24
|
+
};
|
|
25
|
+
readonly authors?: readonly {
|
|
26
|
+
readonly id?: string;
|
|
27
|
+
readonly type?: string;
|
|
28
|
+
}[];
|
|
29
|
+
}
|
|
30
|
+
export interface NotionIngressPayload {
|
|
31
|
+
readonly schemaVersion: typeof NOTION_INGRESS_SCHEMA_VERSION;
|
|
32
|
+
readonly event: ProjectedNotionEvent;
|
|
33
|
+
}
|
|
34
|
+
export interface NotionIngressKey {
|
|
35
|
+
readonly source: typeof NOTION_INGRESS_SOURCE;
|
|
36
|
+
readonly authorityId: string;
|
|
37
|
+
readonly deliveryId: string;
|
|
38
|
+
}
|
|
39
|
+
export interface NotionIngressWrite extends NotionIngressKey {
|
|
40
|
+
readonly payloadJson: string;
|
|
41
|
+
readonly receivedAt: string;
|
|
42
|
+
}
|
|
43
|
+
export interface AcknowledgeNotionIngressDeps {
|
|
44
|
+
readonly org: string;
|
|
45
|
+
readonly signingKeys: readonly NotionSigningKey[];
|
|
46
|
+
readonly allowUnverified: boolean;
|
|
47
|
+
readonly enqueue: (org: string, write: NotionIngressWrite) => Promise<boolean>;
|
|
48
|
+
/** The internal recovery route has both durable secret custody and a resolvable own URL. */
|
|
49
|
+
readonly drainAvailable: boolean;
|
|
50
|
+
readonly drain: (key: NotionIngressKey) => Promise<void>;
|
|
51
|
+
readonly waitUntil: (promise: Promise<unknown>) => void;
|
|
52
|
+
readonly now?: () => Date;
|
|
53
|
+
}
|
|
54
|
+
export declare function decodeNotionIngressPayload(payloadJson: string): NotionIngressPayload | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* Authenticate, project and durably accept one public Notion delivery.
|
|
57
|
+
*
|
|
58
|
+
* `409 + x-oe-notion-ingress:synchronous` is an internal hand-off to the existing route for the
|
|
59
|
+
* subscription handshake and the explicit unsigned development hatch. The live route consumes it;
|
|
60
|
+
* it is never returned to Notion.
|
|
61
|
+
*/
|
|
62
|
+
export declare function acknowledgeNotionIngress(rawBody: string, signatureHeader: string | null, deps: AcknowledgeNotionIngressDeps): Promise<Response>;
|
|
63
|
+
export {};
|
|
64
|
+
//# sourceMappingURL=notion-ingress.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notion-ingress.d.ts","sourceRoot":"","sources":["../../../agent/channels/notion-ingress.ts"],"names":[],"mappings":"AAKA,OAAO,EAAwD,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAE9G,eAAO,MAAM,qBAAqB,WAAW,CAAC;AAC9C,QAAA,MAAM,6BAA6B,EAAG,CAAU,CAAC;AACjD,eAAO,MAAM,6BAA6B,QAAa,CAAC;AAExD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,MAAM,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACnE,QAAQ,CAAC,IAAI,CAAC,EAAE;QACd,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE;YAAE,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QACnE,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS;YAAE,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;KAC/D,CAAC;IACF,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS;QAAE,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAChF;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,aAAa,EAAE,OAAO,6BAA6B,CAAC;IAC7D,QAAQ,CAAC,KAAK,EAAE,oBAAoB,CAAC;CACtC;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,MAAM,EAAE,OAAO,qBAAqB,CAAC;IAC9C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,kBAAmB,SAAQ,gBAAgB;IAC1D,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,WAAW,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAClD,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;IAClC,QAAQ,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/E,4FAA4F;IAC5F,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,KAAK,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACzD,QAAQ,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC;IACxD,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAC3B;AAgED,wBAAgB,0BAA0B,CAAC,WAAW,EAAE,MAAM,GAAG,oBAAoB,GAAG,SAAS,CAShG;AAMD;;;;;;GAMG;AACH,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,MAAM,EACf,eAAe,EAAE,MAAM,GAAG,IAAI,EAC9B,IAAI,EAAE,4BAA4B,GACjC,OAAO,CAAC,QAAQ,CAAC,CAuCnB"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// DURABLE NOTION INGRESS — the authenticated acceptance boundary in front of the expensive
|
|
2
|
+
// comment enrichment and Eve dispatch path. A 200 from this module means the verified, projected
|
|
3
|
+
// event is in the operational store. `waitUntil` is only a low-latency nudge; recovery comes from
|
|
4
|
+
// the persisted row and the minute drain schedule, never from the lifetime of this invocation.
|
|
5
|
+
import { gateNotionDeliveryAcross, parseVerificationHandshake } from "./notion-api.js";
|
|
6
|
+
export const NOTION_INGRESS_SOURCE = "notion";
|
|
7
|
+
const NOTION_INGRESS_SCHEMA_VERSION = 1;
|
|
8
|
+
export const NOTION_INGRESS_MAX_BODY_BYTES = 256 * 1024;
|
|
9
|
+
function text(value) {
|
|
10
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
11
|
+
}
|
|
12
|
+
function record(value) {
|
|
13
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
14
|
+
? value
|
|
15
|
+
: undefined;
|
|
16
|
+
}
|
|
17
|
+
/** Project exactly the fields consumed by the conversational and structural processors. */
|
|
18
|
+
function projectNotionIngressEvent(value) {
|
|
19
|
+
const input = record(value);
|
|
20
|
+
const id = text(input?.id);
|
|
21
|
+
if (!input || !id)
|
|
22
|
+
return undefined;
|
|
23
|
+
const entityIn = record(input.entity);
|
|
24
|
+
const entityId = text(entityIn?.id);
|
|
25
|
+
const entityType = text(entityIn?.type);
|
|
26
|
+
const entity = entityIn ? { ...(entityId ? { id: entityId } : {}), ...(entityType ? { type: entityType } : {}) } : undefined;
|
|
27
|
+
const dataIn = record(input.data);
|
|
28
|
+
const parentIn = record(dataIn?.parent);
|
|
29
|
+
const parentId = text(parentIn?.id);
|
|
30
|
+
const parentType = text(parentIn?.type);
|
|
31
|
+
const parent = parentIn ? { ...(parentId ? { id: parentId } : {}), ...(parentType ? { type: parentType } : {}) } : undefined;
|
|
32
|
+
const updatedBlocks = Array.isArray(dataIn?.updated_blocks)
|
|
33
|
+
? dataIn.updated_blocks.map((item) => {
|
|
34
|
+
const id = text(record(item)?.id);
|
|
35
|
+
return id ? { id } : {};
|
|
36
|
+
})
|
|
37
|
+
: undefined;
|
|
38
|
+
const pageId = text(dataIn?.page_id);
|
|
39
|
+
const data = dataIn
|
|
40
|
+
? {
|
|
41
|
+
...(pageId ? { page_id: pageId } : {}),
|
|
42
|
+
...(parent ? { parent } : {}),
|
|
43
|
+
...(updatedBlocks ? { updated_blocks: updatedBlocks } : {}),
|
|
44
|
+
}
|
|
45
|
+
: undefined;
|
|
46
|
+
const authors = Array.isArray(input.authors)
|
|
47
|
+
? input.authors.map((item) => {
|
|
48
|
+
const author = record(item);
|
|
49
|
+
const authorId = text(author?.id);
|
|
50
|
+
const authorType = text(author?.type);
|
|
51
|
+
return { ...(authorId ? { id: authorId } : {}), ...(authorType ? { type: authorType } : {}) };
|
|
52
|
+
})
|
|
53
|
+
: undefined;
|
|
54
|
+
return {
|
|
55
|
+
id,
|
|
56
|
+
...(text(input.timestamp) ? { timestamp: text(input.timestamp) } : {}),
|
|
57
|
+
...(text(input.type) ? { type: text(input.type) } : {}),
|
|
58
|
+
...(text(input.event_type) ? { event_type: text(input.event_type) } : {}),
|
|
59
|
+
...(text(input.workspace_id) ? { workspace_id: text(input.workspace_id) } : {}),
|
|
60
|
+
...(entity ? { entity } : {}),
|
|
61
|
+
...(data ? { data } : {}),
|
|
62
|
+
...(authors ? { authors } : {}),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export function decodeNotionIngressPayload(payloadJson) {
|
|
66
|
+
try {
|
|
67
|
+
const parsed = record(JSON.parse(payloadJson));
|
|
68
|
+
if (parsed?.schemaVersion !== NOTION_INGRESS_SCHEMA_VERSION)
|
|
69
|
+
return undefined;
|
|
70
|
+
const event = projectNotionIngressEvent(parsed.event);
|
|
71
|
+
return event ? { schemaVersion: NOTION_INGRESS_SCHEMA_VERSION, event } : undefined;
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function synchronousResponse() {
|
|
78
|
+
return new Response("synchronous", { status: 409, headers: { "x-oe-notion-ingress": "synchronous" } });
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Authenticate, project and durably accept one public Notion delivery.
|
|
82
|
+
*
|
|
83
|
+
* `409 + x-oe-notion-ingress:synchronous` is an internal hand-off to the existing route for the
|
|
84
|
+
* subscription handshake and the explicit unsigned development hatch. The live route consumes it;
|
|
85
|
+
* it is never returned to Notion.
|
|
86
|
+
*/
|
|
87
|
+
export async function acknowledgeNotionIngress(rawBody, signatureHeader, deps) {
|
|
88
|
+
if (Buffer.byteLength(rawBody, "utf8") > NOTION_INGRESS_MAX_BODY_BYTES) {
|
|
89
|
+
return new Response("payload too large", { status: 413 });
|
|
90
|
+
}
|
|
91
|
+
if (parseVerificationHandshake(rawBody))
|
|
92
|
+
return synchronousResponse();
|
|
93
|
+
const gate = gateNotionDeliveryAcross(deps.signingKeys, rawBody, signatureHeader, deps.allowUnverified);
|
|
94
|
+
if (gate.rejection)
|
|
95
|
+
return new Response(gate.rejection, { status: 401 });
|
|
96
|
+
if (!gate.verified || !gate.credentialId)
|
|
97
|
+
return synchronousResponse();
|
|
98
|
+
if (!deps.drainAvailable)
|
|
99
|
+
return new Response("durable ingress drain unavailable", { status: 503 });
|
|
100
|
+
let parsed;
|
|
101
|
+
try {
|
|
102
|
+
parsed = JSON.parse(rawBody);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return new Response("bad json", { status: 400 });
|
|
106
|
+
}
|
|
107
|
+
const event = projectNotionIngressEvent(parsed);
|
|
108
|
+
if (!event)
|
|
109
|
+
return new Response("missing event id", { status: 400 });
|
|
110
|
+
const key = {
|
|
111
|
+
source: NOTION_INGRESS_SOURCE,
|
|
112
|
+
authorityId: gate.credentialId,
|
|
113
|
+
deliveryId: event.id,
|
|
114
|
+
};
|
|
115
|
+
const write = {
|
|
116
|
+
...key,
|
|
117
|
+
payloadJson: JSON.stringify({ schemaVersion: NOTION_INGRESS_SCHEMA_VERSION, event }),
|
|
118
|
+
receivedAt: (deps.now ?? (() => new Date()))().toISOString(),
|
|
119
|
+
};
|
|
120
|
+
try {
|
|
121
|
+
await deps.enqueue(deps.org, write);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return new Response("durable ingress unavailable", { status: 503 });
|
|
125
|
+
}
|
|
126
|
+
const drain = Promise.resolve().then(() => deps.drain(key));
|
|
127
|
+
deps.waitUntil(drain);
|
|
128
|
+
return new Response("ok", { status: 200 });
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=notion-ingress.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notion-ingress.js","sourceRoot":"","sources":["../../../agent/channels/notion-ingress.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notion-message-ops.d.ts","sourceRoot":"","sources":["../../../agent/channels/notion-message-ops.ts"],"names":[],"mappings":"AA0CA,OAAO,KAAK,EAAE,iBAAiB,EAAgB,MAAM,4CAA4C,CAAC;AAsClG,eAAO,MAAM,gBAAgB,EAAE,iBA2D9B,CAAC"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// notion-message-ops — Notion's SurfaceMessageOps (OPE-1043, the remaining slice of OPE-899's
|
|
2
|
+
// class): the ancillary message ops core features dispatch through channels/surface-message-ops.ts.
|
|
3
|
+
// Before this file, Notion registered none, so the stop acknowledgement (hooks/turn-anchor.ts's
|
|
4
|
+
// `postSurfaceText`) resolved nothing on a Notion turn — OPE-989 shipped the stop CONTROL itself
|
|
5
|
+
// (notion-stop.ts, a bare `stop` comment), it was just invisible: a person who used it saw the agent
|
|
6
|
+
// go quiet and read nothing telling them it had stood down.
|
|
7
|
+
//
|
|
8
|
+
// - postText → a FRESH top-level comment on the page (`createPageComment`, notion-api.ts) when
|
|
9
|
+
// `opts?.replyToMessageId` is absent — the shape every call on this ticket's own
|
|
10
|
+
// path takes (turn-anchor.ts never passes a reply anchor for a stop note). When
|
|
11
|
+
// present, this arm resolves the NAMED comment's own `discussion_id` (one extra
|
|
12
|
+
// fetch, `retrieveComment`) and replies into that same discussion instead
|
|
13
|
+
// (`createCommentReply`) — falling back to a fresh page comment only when that
|
|
14
|
+
// comment carries no discussion (never silently drops the anchor request).
|
|
15
|
+
// - editMessage → `updateComment` (`PATCH /v1/comments/{id}`), confirmed live 2026-08-23
|
|
16
|
+
// (developers.notion.com/reference/update-a-comment). `edit_reply`'s own seam
|
|
17
|
+
// (`PostArm.editMessage`, a model-facing tool capability with its own ownership/
|
|
18
|
+
// locus rules) now wires to the same endpoint too — notion-post-arm.ts's
|
|
19
|
+
// `liveNotionEditComment` (OPE-1046) — a separate, reviewable change from this one.
|
|
20
|
+
// - postFiles → uploads each file through Notion's two-step flow (`uploadNotionFile`,
|
|
21
|
+
// notion-api.ts: create-file-upload, then send the bytes) and attaches the
|
|
22
|
+
// resulting `file_upload` ids to a FRESH top-level page comment
|
|
23
|
+
// (`createPageComment`'s `attachments`) — OPE-1047. Notion caps a single comment
|
|
24
|
+
// at 3 attachments; a larger batch is refused up front, before any byte uploads,
|
|
25
|
+
// so a caller never pays for uploads it then has to discard.
|
|
26
|
+
// - NO `setReaction`: confirmed (2026-08-23, developers.notion.com) — Notion's public API has no
|
|
27
|
+
// reaction resource for comments at all, the same finding OPE-989 made for the stop trigger
|
|
28
|
+
// itself. Matches the manifest's existing `reactions: false` (register.ts).
|
|
29
|
+
//
|
|
30
|
+
// `channelId` IS THE STOP REGISTRY'S CONVERSATION KEY — `<workspace>/<page>`, not a
|
|
31
|
+
// notion-message-ops-specific pair invented here. `turn-anchor.ts`'s `turnAnchorAddress` reads it off
|
|
32
|
+
// `binding.anchorChannelId`, which `bindNotion` (notion-identity.ts) mints through
|
|
33
|
+
// `notionConversationKey` — the SAME key `notion-stop.ts`'s `routeNotionStopComment` parses to
|
|
34
|
+
// resolve the cancel. This file reuses `parseNotionConversationKey`, the composer's own documented
|
|
35
|
+
// inverse, rather than writing a second parser: two derivations of the same address is exactly the
|
|
36
|
+
// "silently miss" trap notion-identity.ts's own header warns about.
|
|
37
|
+
//
|
|
38
|
+
// CREDENTIAL RESOLUTION mirrors notion-post-arm.ts's `notionReplyCredential` (a page's domain binding
|
|
39
|
+
// picks its own Notion integration; no binding falls through to the org default) — copied rather than
|
|
40
|
+
// imported, for the same reason notion-identity.ts's `notionAttr`/`notionAuthOf` are local copies: a
|
|
41
|
+
// three-line structural read a caller outside the post-arm's own module is expected to make itself.
|
|
42
|
+
import { getConfig } from "@open-everywhere/core/agent/adapter-api.js";
|
|
43
|
+
import { createPageComment, createCommentReply, updateComment, resolveNotionCredential, retrieveComment, uploadNotionFile, NOTION_MAX_ATTACHMENTS, } from "./notion-api.js";
|
|
44
|
+
import { notionChannelCredentialFor } from "./notion-credentials.js";
|
|
45
|
+
import { parseNotionConversationKey } from "./notion-identity.js";
|
|
46
|
+
function notionDomains() {
|
|
47
|
+
try {
|
|
48
|
+
return getConfig().loadDomains();
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** Parse the `<workspace>/<page>` conversation id, or throw the same honest, naming refusal every
|
|
55
|
+
* other surface-message-ops call gives on a malformed address — never a guessed target. */
|
|
56
|
+
function requireConversation(channelId) {
|
|
57
|
+
const parsed = parseNotionConversationKey(channelId);
|
|
58
|
+
if (!parsed)
|
|
59
|
+
throw new Error(`Notion surface message ops: "${channelId}" is not a conversation id (expected "<workspace>/<page>").`);
|
|
60
|
+
return parsed;
|
|
61
|
+
}
|
|
62
|
+
async function requireCredential(workspaceId, pageId) {
|
|
63
|
+
const credentialId = notionChannelCredentialFor(workspaceId, pageId, notionDomains());
|
|
64
|
+
const cred = await resolveNotionCredential(credentialId);
|
|
65
|
+
if (!cred)
|
|
66
|
+
throw new Error(`Notion surface message ops: no value stored for notion credential "${credentialId ?? "channel-key"}".`);
|
|
67
|
+
return cred;
|
|
68
|
+
}
|
|
69
|
+
export const notionMessageOps = {
|
|
70
|
+
platform: "notion",
|
|
71
|
+
/**
|
|
72
|
+
* A fresh top-level comment on the page, or — when `opts?.replyToMessageId` names a comment that
|
|
73
|
+
* carries a `discussion_id` — a reply into that SAME discussion instead. Never guesses a
|
|
74
|
+
* discussion: a named comment this call cannot resolve (fetch failure, or a comment with no
|
|
75
|
+
* discussion of its own) falls back to the fresh-comment shape rather than dropping the request.
|
|
76
|
+
*/
|
|
77
|
+
async postText(channelId, text, opts) {
|
|
78
|
+
const { workspaceId, pageId } = requireConversation(channelId);
|
|
79
|
+
const cred = await requireCredential(workspaceId, pageId);
|
|
80
|
+
if (opts?.replyToMessageId) {
|
|
81
|
+
let discussionId;
|
|
82
|
+
try {
|
|
83
|
+
discussionId = (await retrieveComment(opts.replyToMessageId, cred)).discussion_id;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
discussionId = undefined;
|
|
87
|
+
}
|
|
88
|
+
if (discussionId)
|
|
89
|
+
return createCommentReply({ discussionId, text, ...cred });
|
|
90
|
+
}
|
|
91
|
+
return createPageComment({ pageId, text, ...cred });
|
|
92
|
+
},
|
|
93
|
+
/** `PATCH /v1/comments/{id}` — rewrite the comment's body in place (`updateComment`). */
|
|
94
|
+
async editMessage(channelId, messageId, text) {
|
|
95
|
+
const { workspaceId, pageId } = requireConversation(channelId);
|
|
96
|
+
const cred = await requireCredential(workspaceId, pageId);
|
|
97
|
+
await updateComment({ commentId: messageId, text, ...cred });
|
|
98
|
+
},
|
|
99
|
+
/**
|
|
100
|
+
* Upload each file through Notion's two-step flow (`uploadNotionFile`), then land them all on
|
|
101
|
+
* ONE fresh top-level page comment via `createPageComment`'s `attachments` (OPE-1047). Notion
|
|
102
|
+
* caps a single comment at {@link NOTION_MAX_ATTACHMENTS} attachments — refused up front, before
|
|
103
|
+
* any upload runs, so a too-large batch never pays for bytes it then has to discard.
|
|
104
|
+
*
|
|
105
|
+
* Always a PAGE comment, never a reply into a named discussion: `SurfaceMessageOps.postFiles`
|
|
106
|
+
* carries no `replyToMessageId` (unlike `postText`), the same "current channel" shape Discord's
|
|
107
|
+
* `postFiles` posts into.
|
|
108
|
+
*
|
|
109
|
+
* Returns the created comment's id (Notion echoes it, same as `postText`) — undefined only if
|
|
110
|
+
* the response carries none, never a guessed id.
|
|
111
|
+
*/
|
|
112
|
+
async postFiles(channelId, files, comment) {
|
|
113
|
+
if (files.length > NOTION_MAX_ATTACHMENTS) {
|
|
114
|
+
throw new Error(`Notion surface message ops: at most ${NOTION_MAX_ATTACHMENTS} files per post; got ${files.length}.`);
|
|
115
|
+
}
|
|
116
|
+
const { workspaceId, pageId } = requireConversation(channelId);
|
|
117
|
+
const cred = await requireCredential(workspaceId, pageId);
|
|
118
|
+
const fileUploadIds = [];
|
|
119
|
+
for (const file of files) {
|
|
120
|
+
fileUploadIds.push(await uploadNotionFile({ filename: file.filename, contentType: file.contentType, bytes: file.bytes, ...cred }));
|
|
121
|
+
}
|
|
122
|
+
const id = await createPageComment({ pageId, text: comment ?? "", attachments: fileUploadIds, ...cred });
|
|
123
|
+
return id.length > 0 ? id : undefined;
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
//# sourceMappingURL=notion-message-ops.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notion-message-ops.js","sourceRoot":"","sources":["../../../agent/channels/notion-message-ops.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { UnifiedWakeOrigin, WakeOriginCodec } from "@open-everywhere/core/agent/adapter-api.js";
|
|
2
|
+
type NotionOrigin = Extract<UnifiedWakeOrigin, {
|
|
3
|
+
platform: "notion";
|
|
4
|
+
}>;
|
|
5
|
+
export declare const notionWakeOriginCodec: WakeOriginCodec<NotionOrigin>;
|
|
6
|
+
export {};
|
|
7
|
+
//# sourceMappingURL=notion-origin-codec.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notion-origin-codec.d.ts","sourceRoot":"","sources":["../../../agent/channels/notion-origin-codec.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,4CAA4C,CAAC;AAErG,KAAK,YAAY,GAAG,OAAO,CAAC,iBAAiB,EAAE;IAAE,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAAC,CAAC;AAMvE,eAAO,MAAM,qBAAqB,EAAE,eAAe,CAAC,YAAY,CAgB/D,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// The Notion UnifiedWakeOrigin codec — MOVED VERBATIM out of core's
|
|
2
|
+
// channels/wake-origin-codecs.ts (OPE-114 stage 3, the first extracted arm). STORED-DATA
|
|
3
|
+
// COMPATIBILITY IS THE CONTRACT: a Notion origin is JSON-serialised into the durable
|
|
4
|
+
// operational store and fail-closed re-validated on read, so every behaviour here is frozen
|
|
5
|
+
// by core's scripts/wake-origin-fixtures-test.ts (run from the app root with this bundle's
|
|
6
|
+
// boot wiring — scripts/wake-origin-fixtures-boot.ts). Validator tolerances (typeof-only
|
|
7
|
+
// optional checks, extra unknown fields) are LOAD-BEARING for rows already on disk — never
|
|
8
|
+
// tighten them here.
|
|
9
|
+
//
|
|
10
|
+
// The `UnifiedWakeOrigin` TYPE union (including its notion arm) stays in core — it is the
|
|
11
|
+
// wire schema for stored rows; only the codec BEHAVIOUR lives in the bundle, registered by
|
|
12
|
+
// registerNotionProvider() (../register.ts).
|
|
13
|
+
/** COLON-FREE by construction (a task windowId is `[^:]+` — core channels/window-key.ts): any colon
|
|
14
|
+
* in a raw surface id is folded to `_`, deterministically, so spawn + ingress agree on identity. */
|
|
15
|
+
const noColon = (s) => s.replace(/:/g, "_");
|
|
16
|
+
export const notionWakeOriginCodec = {
|
|
17
|
+
validate: (o) => typeof o.discussionId === "string" &&
|
|
18
|
+
o.discussionId.length > 0 &&
|
|
19
|
+
(o.pageId === undefined || typeof o.pageId === "string") &&
|
|
20
|
+
(o.workspaceId === undefined || typeof o.workspaceId === "string"),
|
|
21
|
+
threadRef: (origin) => ({ channelKey: noColon(`notion/${origin.pageId ?? origin.discussionId}`), threadKey: noColon(origin.discussionId) }),
|
|
22
|
+
isThreadScoped: () => true,
|
|
23
|
+
containerId: (origin) => origin.discussionId,
|
|
24
|
+
// NO `placementRef` AND NO `carryPlacement`, AND THAT ABSENCE IS THE DECLARATION (OPE-1826). Notion
|
|
25
|
+
// keeps its placement INSIDE the conversation identity: a reply targets `discussionId`, which is
|
|
26
|
+
// exactly what `threadRef` keys on, so two origins that pass `sameUnifiedConversation` name the same
|
|
27
|
+
// discussion and cannot disagree about where the answer lands. `credentialId` is not a
|
|
28
|
+
// counter-example: it selects the integration the post is made under, never where the post lands.
|
|
29
|
+
// The position is recorded, and held to account against the live registry, by the
|
|
30
|
+
// `origin-placement-carry` row in core's identity/surface-parity.ts.
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=notion-origin-codec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notion-origin-codec.js","sourceRoot":"","sources":["../../../agent/channels/notion-origin-codec.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,yFAAyF;AACzF,qFAAqF;AACrF,4FAA4F;AAC5F,2FAA2F;AAC3F,yFAAyF;AACzF,2FAA2F;AAC3F,qBAAqB;AACrB,EAAE;AACF,0FAA0F;AAC1F,2FAA2F;AAC3F,6CAA6C;AAM7C;qGACqG;AACrG,MAAM,OAAO,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;AAE5D,MAAM,CAAC,MAAM,qBAAqB,GAAkC;IAClE,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CACd,OAAO,CAAC,CAAC,YAAY,KAAK,QAAQ;QAClC,CAAC,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC;QACzB,CAAC,CAAC,CAAC,MAAM,KAAK,SAAS,IAAI,OAAO,CAAC,CAAC,MAAM,KAAK,QAAQ,CAAC;QACxD,CAAC,CAAC,CAAC,WAAW,KAAK,SAAS,IAAI,OAAO,CAAC,CAAC,WAAW,KAAK,QAAQ,CAAC;IACpE,SAAS,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,YAAY,EAAE,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,CAAC;IAC3I,cAAc,EAAE,GAAG,EAAE,CAAC,IAAI;IAC1B,WAAW,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,YAAY;IAC5C,oGAAoG;IACpG,iGAAiG;IACjG,qGAAqG;IACrG,uFAAuF;IACvF,kGAAkG;IAClG,kFAAkF;IAClF,qEAAqE;CACtE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notion-post-arm.d.ts","sourceRoot":"","sources":["../../../agent/channels/notion-post-arm.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,OAAO,EAAiF,MAAM,4CAA4C,CAAC;AAoGzJ,eAAO,MAAM,aAAa,EAAE,OAoE3B,CAAC"}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
// notion-post-arm — Notion's POST arm for the post-arms registry (OPE-114 stage 1c). Absorbs,
|
|
2
|
+
// VERBATIM, Notion's arms out of the three reply/delivery spines: reply-post.ts (resolveReplyTarget's
|
|
3
|
+
// notion case + dispatchReply's createCommentReply branch), unified-reply.ts (liveNotionPost), and
|
|
4
|
+
// unified-redrive.ts (the notion describe/container cases). Every Notion reply funnels through
|
|
5
|
+
// notion-api.ts createCommentReply — a NEW comment in the triggering discussion thread — under the
|
|
6
|
+
// integration key resolved by resolveNotionCredential, which carries that credential's endpoint
|
|
7
|
+
// rules alongside the key so the guarded fetch can refuse a call outside them.
|
|
8
|
+
//
|
|
9
|
+
// EDIT (OPE-1046). `PATCH /v1/comments/{id}` (`updateComment`, notion-api.ts) exists — confirmed live
|
|
10
|
+
// 2026-08-23, correcting this tree's earlier "no comment-update endpoint" claim. `editMessage` below
|
|
11
|
+
// resolves the credential the SAME way `notion-message-ops.ts`'s `SurfaceMessageOps.editMessage`
|
|
12
|
+
// already does (OPE-1043): parse the `<workspace>/<page>` conversation key back into its two ids
|
|
13
|
+
// (`parseNotionConversationKey`) and look up that page's own credential (`notionReplyCredential`,
|
|
14
|
+
// already defined below for the reply arms). `messageId` is the comment id itself — the one field
|
|
15
|
+
// `edit_reply`'s own-post ledger checked before this arm is ever called (channels/reply-edit.ts).
|
|
16
|
+
import { createCommentReply, updateComment, resolveNotionCredential } from "./notion-api.js";
|
|
17
|
+
import { notionChannelCredentialFor, NOTION_CHANNEL_CREDENTIAL, NOTION_PROVIDER } from "./notion-credentials.js";
|
|
18
|
+
import { parseNotionConversationKey } from "./notion-identity.js";
|
|
19
|
+
import { getConfig, resolvePartition, markNotApplied, carryFailureSignals, findConnectorCredential } from "@open-everywhere/core/agent/adapter-api.js";
|
|
20
|
+
// OPE-467 — THE CREDENTIAL A REPLY POSTS UNDER, in one sentence: the credential the DELIVERY ran
|
|
21
|
+
// under, which the wake stowed on the binding target / wake origin (notion-identity.ts's
|
|
22
|
+
// `credential_id` attribute). The credential that fetched the comment is the credential that
|
|
23
|
+
// answers it — a page shared with an integration its domain does not bind would otherwise fetch
|
|
24
|
+
// under one key and reply under another whose integration was never shared the page, and
|
|
25
|
+
// `createCommentReply` would throw with nobody watching.
|
|
26
|
+
//
|
|
27
|
+
// The page's domain binding is the FALLBACK, for a target minted before the stamp existed and for
|
|
28
|
+
// proactive posts that no delivery woke. Fail-soft on a config-load error: no domains ⇒ no binding
|
|
29
|
+
// ⇒ the org default, which is the pre-OPE-467 behaviour rather than a refusal.
|
|
30
|
+
function notionDomains() {
|
|
31
|
+
try {
|
|
32
|
+
return getConfig().loadDomains();
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function notionReplyCredential(workspaceId, pageId) {
|
|
39
|
+
return notionChannelCredentialFor(workspaceId, pageId, notionDomains());
|
|
40
|
+
}
|
|
41
|
+
/** MID-TURN: post back into the SAME discussion thread as a new comment (createCommentReply). */
|
|
42
|
+
async function dispatchNotionReply(target, text) {
|
|
43
|
+
const credentialId = target.credentialId ?? notionReplyCredential(target.workspaceId, target.pageId);
|
|
44
|
+
// OPE-505: resolve the key WITH its endpoint rules and spread both, so the credential's fence
|
|
45
|
+
// travels with it into createCommentReply's guarded fetch.
|
|
46
|
+
const cred = await resolveNotionCredential(credentialId);
|
|
47
|
+
if (!cred) {
|
|
48
|
+
// DEFINITIVE (OPE-825): the call never leaves this host — no credential, no request — so
|
|
49
|
+
// nothing reached Notion and an identical retry is legitimate.
|
|
50
|
+
throw markNotApplied(new Error(`reply couldn't post to Notion — no value stored for notion credential "${credentialId ?? "channel-key"}".`));
|
|
51
|
+
}
|
|
52
|
+
return createCommentReply({ discussionId: target.discussionId, text, ...cred });
|
|
53
|
+
}
|
|
54
|
+
/** UNIFIED: POST /v1/comments { discussion_id } — the exact helper (and Notion-Version header) every
|
|
55
|
+
* per-doc Notion reply already goes through; a supervisor session on the synthetic unified channel
|
|
56
|
+
* has no notion channel state, so the REST call under the integration token (db:notion:channel-key)
|
|
57
|
+
* is the reply route. The target discussionId is the frozen wake origin's — host-side, never
|
|
58
|
+
* model-authored. */
|
|
59
|
+
async function liveNotionPost(origin, text) {
|
|
60
|
+
const credentialId = origin.credentialId ?? notionReplyCredential(origin.workspaceId, origin.pageId);
|
|
61
|
+
// OPE-505: key + endpoint rules together (see dispatchNotionReply).
|
|
62
|
+
const cred = await resolveNotionCredential(credentialId);
|
|
63
|
+
if (!cred) {
|
|
64
|
+
// DEFINITIVE (OPE-825): no credential ⇒ no request ever left this host.
|
|
65
|
+
throw markNotApplied(new Error(`unified reply couldn't post to Notion — no value stored for notion credential "${credentialId ?? "channel-key"}" (discussion ${origin.discussionId})`));
|
|
66
|
+
}
|
|
67
|
+
let id;
|
|
68
|
+
try {
|
|
69
|
+
id = await createCommentReply({ discussionId: origin.discussionId, text, ...cred });
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
// Token-free error: never echo the transport error body (defensive parity with the Linear arm).
|
|
73
|
+
// carryFailureSignals (OPE-825) carries the numeric status createCommentReply attached, so this
|
|
74
|
+
// re-wrap doesn't silently downgrade a DEFINITIVE 4xx refusal to AMBIGUOUS.
|
|
75
|
+
throw carryFailureSignals(err, new Error(`unified reply couldn't post to Notion — createCommentReply failed on discussion ${origin.discussionId}`));
|
|
76
|
+
}
|
|
77
|
+
return id || "unknown";
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* `PATCH /v1/comments/{id}` — rewrite a comment THIS deployment already posted, in place (OPE-1046).
|
|
81
|
+
* `locus.channelId` is the `<workspace>/<page>` conversation key `replyEditLocus`/`unifiedEditLocus`
|
|
82
|
+
* (channels/reply-post.ts / unified-reply.ts) compose from the target/origin's `workspaceId`/`pageId`
|
|
83
|
+
* — parsed back apart here rather than carried as two separate `EditLocus` fields, matching the
|
|
84
|
+
* SAME composite `notion-message-ops.ts` already parses for its own `editMessage` (OPE-1043).
|
|
85
|
+
*
|
|
86
|
+
* Notion 404s a comment the calling connection did not create and 403s a connection lacking
|
|
87
|
+
* insert-comment capability — both DEFINITIVE (a 4xx `updateComment` already attaches to the thrown
|
|
88
|
+
* error's `.status`, OPE-825's classifier reads it with no help from this function); a timeout or a
|
|
89
|
+
* 5xx is AMBIGUOUS. Re-wrapped token-free, matching `liveNotionPost`'s own re-wrap discipline above.
|
|
90
|
+
*/
|
|
91
|
+
async function liveNotionEditComment(locus, messageId, text) {
|
|
92
|
+
const parsed = parseNotionConversationKey(locus.channelId);
|
|
93
|
+
if (!parsed) {
|
|
94
|
+
// DEFINITIVE (OPE-825): a malformed locus is a host refusal before any byte leaves.
|
|
95
|
+
throw markNotApplied(new Error(`edit_reply couldn't edit the Notion comment ${messageId} — "${locus.channelId}" is not a conversation id.`));
|
|
96
|
+
}
|
|
97
|
+
const credentialId = notionReplyCredential(parsed.workspaceId, parsed.pageId);
|
|
98
|
+
const cred = await resolveNotionCredential(credentialId);
|
|
99
|
+
if (!cred) {
|
|
100
|
+
// DEFINITIVE (OPE-825): no credential ⇒ no request ever left this host.
|
|
101
|
+
throw markNotApplied(new Error(`edit_reply couldn't edit the Notion comment ${messageId} — no value stored for notion credential "${credentialId ?? "channel-key"}".`));
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
await updateComment({ commentId: messageId, text, ...cred });
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
// Token-free re-wrap; carryFailureSignals keeps the numeric status (and any `applied:false`)
|
|
108
|
+
// from downgrading to AMBIGUOUS across the wrap — the same discipline dispatchNotionReply/
|
|
109
|
+
// liveNotionPost above already apply.
|
|
110
|
+
throw carryFailureSignals(err, new Error(`edit_reply couldn't edit the Notion comment ${messageId} — updateComment failed`));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
export const notionPostArm = {
|
|
114
|
+
platform: "notion",
|
|
115
|
+
resolveTarget: (bag) => {
|
|
116
|
+
// Notion posts into the triggering comment's discussion (bindNotion.target.discussionId).
|
|
117
|
+
const discussionId = bag.str("discussionId");
|
|
118
|
+
if (!discussionId)
|
|
119
|
+
return null;
|
|
120
|
+
// `pageId` rides the binding target (notion-identity.ts bindNotion); the workspace comes off the
|
|
121
|
+
// partition. Both are CONTEXT for credential selection only — the post target stays the
|
|
122
|
+
// discussion. Fail-soft: an unresolvable partition leaves the workspace absent, and the page
|
|
123
|
+
// alone still resolves a domain through the roster tier.
|
|
124
|
+
let workspaceId;
|
|
125
|
+
try {
|
|
126
|
+
workspaceId = resolvePartition(bag.ctx).workspace;
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
workspaceId = undefined;
|
|
130
|
+
}
|
|
131
|
+
const pageId = bag.str("pageId");
|
|
132
|
+
// The delivery's own integration (bindNotion carries it off the `credential_id` auth attribute)
|
|
133
|
+
// — the reply posts under the credential that fetched, never a re-derivation that agrees by luck.
|
|
134
|
+
const credentialId = bag.str("credentialId");
|
|
135
|
+
return {
|
|
136
|
+
platform: "notion",
|
|
137
|
+
discussionId,
|
|
138
|
+
...(pageId ? { pageId } : {}),
|
|
139
|
+
...(workspaceId ? { workspaceId } : {}),
|
|
140
|
+
...(credentialId ? { credentialId } : {}),
|
|
141
|
+
};
|
|
142
|
+
},
|
|
143
|
+
dispatchReply: (target, text) => dispatchNotionReply(target, text),
|
|
144
|
+
dispatchUnified: (origin, text, deps) => (deps.postNotion ?? liveNotionPost)(origin, text),
|
|
145
|
+
// EDIT (OPE-1046): `updateComment` on the address `replyEditLocus`/`unifiedEditLocus` compose from
|
|
146
|
+
// the target/origin's `workspaceId`/`pageId` — see `liveNotionEditComment` above.
|
|
147
|
+
editMessage: (locus, messageId, text) => liveNotionEditComment(locus, messageId, text),
|
|
148
|
+
// 🔴 CAN THAT EDIT ACTUALLY BE ISSUED HERE? (OPE-1753.) `editMessage` above PATCHes
|
|
149
|
+
// `/v1/comments/{id}`, and this deployment's Notion channel key declares `GET,POST` on purpose —
|
|
150
|
+
// "a DELETE or PATCH can never be issued under the channel key" is that rule's own stated point.
|
|
151
|
+
// So the endpoint fence refuses every edit before a byte leaves, and the should-be-edit policy,
|
|
152
|
+
// which could only see that `editMessage` EXISTS, went on steering the seat at a verb that cannot
|
|
153
|
+
// work: on soak `soak-2026-09-12-a` that cost a refusal loop and the duplicate post behind it.
|
|
154
|
+
// This answers the narrower question the policy actually needs — is PATCH within the resolved
|
|
155
|
+
// credential's declared methods?
|
|
156
|
+
// 🔴 IT READS THE RULES, IT DOES NOT WIDEN THEM. The endpoint declaration is security posture and
|
|
157
|
+
// stays exactly as it is; this only stops the host OFFERING an edit the fence will refuse.
|
|
158
|
+
// FAIL-OPEN in both directions that are not a positive refusal: an unresolvable locus or credential
|
|
159
|
+
// answers true and leaves the pre-OPE-1753 behaviour, and a credential with NO declared methods is
|
|
160
|
+
// unnarrowed, so every method is allowed.
|
|
161
|
+
// 🔴 IT READS THE DECLARATION, NEVER THE SECRET. `resolveNotionCredential` would answer the same
|
|
162
|
+
// question, but it calls `getSecretVia` — a broker read that fetches the live integration key,
|
|
163
|
+
// holds it in memory, and on an unstamped broker appends a `credential unresolved` audit row. This
|
|
164
|
+
// predicate runs on every fold evaluation in a Notion conversation, and the question it asks is
|
|
165
|
+
// answerable from the static catalogue alone: which methods did the operator declare? The member's
|
|
166
|
+
// own contract in `channels/post-arms.ts` says READ-ONLY and cheap, and fetching a key to read a
|
|
167
|
+
// config field is neither.
|
|
168
|
+
editReachable: async (locus) => {
|
|
169
|
+
const parsed = parseNotionConversationKey(locus.channelId);
|
|
170
|
+
if (!parsed)
|
|
171
|
+
return true;
|
|
172
|
+
// 🔴 `undefined` MEANS THE DEFAULT KEY, NOT "NO KEY". `notionChannelCredentialFor` answers
|
|
173
|
+
// undefined whenever the page's domain declares no per-domain override, which is the ORDINARY
|
|
174
|
+
// case — and `resolveNotionCredential` reads that as `NOTION_CHANNEL_CREDENTIAL`, the very key
|
|
175
|
+
// whose GET,POST rules this predicate exists to read. Treating undefined as "cannot tell" and
|
|
176
|
+
// failing open here disabled the whole fix on every default-credential page; `notion-post-arm-
|
|
177
|
+
// test.ts` §5 caught exactly that.
|
|
178
|
+
const credentialId = notionReplyCredential(parsed.workspaceId, parsed.pageId) ?? NOTION_CHANNEL_CREDENTIAL;
|
|
179
|
+
const methods = findConnectorCredential(NOTION_PROVIDER, credentialId)?.endpoints?.methods;
|
|
180
|
+
return methods === undefined || methods.includes("PATCH");
|
|
181
|
+
},
|
|
182
|
+
describeRedriveTarget: (origin) => `notion discussion ${origin.discussionId}`,
|
|
183
|
+
redriveContainerId: (origin) => origin.discussionId,
|
|
184
|
+
};
|
|
185
|
+
//# sourceMappingURL=notion-post-arm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notion-post-arm.js","sourceRoot":"","sources":["../../../agent/channels/notion-post-arm.ts"],"names":[],"mappings":"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"}
|