@excitedjs/feishu-transport 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +39 -0
  2. package/dist/contract/access-store.d.ts +23 -0
  3. package/dist/contract/access-store.d.ts.map +1 -0
  4. package/dist/contract/access-store.js +16 -0
  5. package/dist/contract/access-store.js.map +1 -0
  6. package/dist/contract/outbound.d.ts +39 -0
  7. package/dist/contract/outbound.d.ts.map +1 -0
  8. package/dist/contract/outbound.js +16 -0
  9. package/dist/contract/outbound.js.map +1 -0
  10. package/dist/contract/types.d.ts +86 -0
  11. package/dist/contract/types.d.ts.map +1 -0
  12. package/dist/contract/types.js +10 -0
  13. package/dist/contract/types.js.map +1 -0
  14. package/dist/index.d.ts +29 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +31 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/json.d.ts +10 -0
  19. package/dist/json.d.ts.map +1 -0
  20. package/dist/json.js +14 -0
  21. package/dist/json.js.map +1 -0
  22. package/dist/parse/comment.d.ts +41 -0
  23. package/dist/parse/comment.d.ts.map +1 -0
  24. package/dist/parse/comment.js +51 -0
  25. package/dist/parse/comment.js.map +1 -0
  26. package/dist/parse/content.d.ts +42 -0
  27. package/dist/parse/content.d.ts.map +1 -0
  28. package/dist/parse/content.js +208 -0
  29. package/dist/parse/content.js.map +1 -0
  30. package/dist/policy/gate.d.ts +105 -0
  31. package/dist/policy/gate.d.ts.map +1 -0
  32. package/dist/policy/gate.js +276 -0
  33. package/dist/policy/gate.js.map +1 -0
  34. package/dist/policy/pairing.d.ts +12 -0
  35. package/dist/policy/pairing.d.ts.map +1 -0
  36. package/dist/policy/pairing.js +15 -0
  37. package/dist/policy/pairing.js.map +1 -0
  38. package/dist/render/render.d.ts +186 -0
  39. package/dist/render/render.d.ts.map +1 -0
  40. package/dist/render/render.js +630 -0
  41. package/dist/render/render.js.map +1 -0
  42. package/dist/transport/connection.d.ts +25 -0
  43. package/dist/transport/connection.d.ts.map +1 -0
  44. package/dist/transport/connection.js +42 -0
  45. package/dist/transport/connection.js.map +1 -0
  46. package/dist/transport/feishu.d.ts +222 -0
  47. package/dist/transport/feishu.d.ts.map +1 -0
  48. package/dist/transport/feishu.js +431 -0
  49. package/dist/transport/feishu.js.map +1 -0
  50. package/package.json +39 -0
package/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # @excitedjs/feishu-transport
2
+
3
+ Shared **Feishu platform-I/O core** for the dreamux + claudemux channel layers.
4
+ The single place that imports the Feishu SDK.
5
+
6
+ > **Status: PR0 scaffold.** No business logic yet — this PR establishes the
7
+ > package skeleton, build, and rush wiring. Platform I/O and policy are ported
8
+ > from claudemux (the source of truth) in PR1. See
9
+ > [issue #25](https://github.com/excitedjs/dreamux/issues/25).
10
+
11
+ ## Scope (when filled in by PR1+)
12
+
13
+ - **transport** — connect / receive / send / `addReaction` / `removeReaction` /
14
+ `editText` / `fetchDocComment` / `fetchDocMeta` / bot open_id resolution /
15
+ auth. (react/edit/doc-fetch all bind the lark SDK, so they live here even
16
+ though dreamux leaves them dormant — see issue #25 §7.1.)
17
+ - **render** — markdown → Feishu v2 card (incl. inline `<@open_id>` parsing).
18
+ - **parse** — Feishu message content → text (incl. `interactive`).
19
+ - **policy** — stateless `gate()` / pairing-code / `pruneExpiredPending`, plus
20
+ the `AccessStore` interface (each host implements its own store).
21
+
22
+ ## Engineering rules this package honors
23
+
24
+ - Ships compiled `dist/` (`tsc`), **no `tsx` runtime dependency**.
25
+ - Consumed as a **published, version-pinned package** by both repos; the two
26
+ hosts never depend on each other.
27
+ - Built via rush in topological order (`rush build` builds this before any
28
+ dependent). Standalone `npm run build` works once its own deps are installed.
29
+
30
+ ## Build / test
31
+
32
+ ```sh
33
+ # monorepo (preferred)
34
+ node common/scripts/install-run-rush.js update
35
+ node common/scripts/install-run-rush.js build
36
+
37
+ # per-package
38
+ npm install && npm run build && npm test
39
+ ```
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The access-state persistence contract.
3
+ *
4
+ * The pure `gate()` reasons over an `Access` value but never reads or writes
5
+ * it; persistence is the host's job, injected through this interface. Each host
6
+ * backs it differently — dreamux with SQLite under `~/.codex-host` (server-owned
7
+ * runtime state), claudemux with its existing atomic-write `access.json`. Keeping
8
+ * `gate` pure with the store injected leaves the gate logic in one place (core)
9
+ * instead of copy-drifting per host. See dreamux#25 §6 (decision D).
10
+ *
11
+ * Corruption handling (e.g. an unreadable `access.json` moved aside, defaults
12
+ * restored fail-closed) is the implementing host's concern, not part of this
13
+ * interface — the host decides what a load failure means for its deployment.
14
+ */
15
+ import type { Access } from './types.js';
16
+ /** Persists the access-control state a host's gate decisions read and mutate. */
17
+ export interface AccessStore {
18
+ /** Return the current access state (defaults when none has been persisted). */
19
+ load(): Access;
20
+ /** Persist `access` durably, replacing any previous state. */
21
+ save(access: Access): void;
22
+ }
23
+ //# sourceMappingURL=access-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"access-store.d.ts","sourceRoot":"","sources":["../../src/contract/access-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAExC,iFAAiF;AACjF,MAAM,WAAW,WAAW;IAC1B,+EAA+E;IAC/E,IAAI,IAAI,MAAM,CAAA;IACd,8DAA8D;IAC9D,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CAC3B"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The access-state persistence contract.
3
+ *
4
+ * The pure `gate()` reasons over an `Access` value but never reads or writes
5
+ * it; persistence is the host's job, injected through this interface. Each host
6
+ * backs it differently — dreamux with SQLite under `~/.codex-host` (server-owned
7
+ * runtime state), claudemux with its existing atomic-write `access.json`. Keeping
8
+ * `gate` pure with the store injected leaves the gate logic in one place (core)
9
+ * instead of copy-drifting per host. See dreamux#25 §6 (decision D).
10
+ *
11
+ * Corruption handling (e.g. an unreadable `access.json` moved aside, defaults
12
+ * restored fail-closed) is the implementing host's concern, not part of this
13
+ * interface — the host decides what a load failure means for its deployment.
14
+ */
15
+ export {};
16
+ //# sourceMappingURL=access-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"access-store.js","sourceRoot":"","sources":["../../src/contract/access-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The outbound addressing contract.
3
+ *
4
+ * Replaces the old `sendText(chatId, text)` shape with a structured target, so
5
+ * the transport can thread a reply under the triggering message and @-back the
6
+ * asker — every field the target needs is already persisted on inbound
7
+ * (`source_chat_id` / `source_message_id` / `sender_id`). See dreamux#25 §4.
8
+ *
9
+ * The transport's `send` reads only the platform-addressing fields
10
+ * (`chatId` / `replyToMessageId` / `mentionUserIds`). `conversationKey` is a
11
+ * routing hint for the host's channel layer (which Codex/engine thread the
12
+ * reply belongs to); the transport ignores it. A host without an engine thread
13
+ * (claudemux) omits it entirely.
14
+ */
15
+ /** Where an outbound message goes, and how it threads back to its trigger. */
16
+ export interface OutboundTarget {
17
+ /** Destination chat_id — required. Today: the inbound `source_chat_id`. */
18
+ chatId: string;
19
+ /**
20
+ * message_id to reply under, so a group reply threads beneath the original
21
+ * question. Maps from the inbound `source_message_id`. Optional: when unset,
22
+ * the transport sends a fresh top-level message.
23
+ */
24
+ replyToMessageId?: string;
25
+ /**
26
+ * open_ids to @-mention in the reply — typically the asker, to @-back them
27
+ * in a busy group. Maps from the inbound `sender_id`. Optional and additive
28
+ * to any inline `<@open_id>` already written in the markdown body.
29
+ */
30
+ mentionUserIds?: string[];
31
+ /**
32
+ * Host engine-thread routing key (dreamux: the Codex thread for this
33
+ * conversation; fixes the shared-thread cross-talk gap). Opaque to the
34
+ * transport — a channel-layer concern — and omitted by hosts with no engine
35
+ * thread.
36
+ */
37
+ conversationKey?: string;
38
+ }
39
+ //# sourceMappingURL=outbound.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outbound.d.ts","sourceRoot":"","sources":["../../src/contract/outbound.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,8EAA8E;AAC9E,MAAM,WAAW,cAAc;IAC7B,2EAA2E;IAC3E,MAAM,EAAE,MAAM,CAAA;IACd;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAA;IACzB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The outbound addressing contract.
3
+ *
4
+ * Replaces the old `sendText(chatId, text)` shape with a structured target, so
5
+ * the transport can thread a reply under the triggering message and @-back the
6
+ * asker — every field the target needs is already persisted on inbound
7
+ * (`source_chat_id` / `source_message_id` / `sender_id`). See dreamux#25 §4.
8
+ *
9
+ * The transport's `send` reads only the platform-addressing fields
10
+ * (`chatId` / `replyToMessageId` / `mentionUserIds`). `conversationKey` is a
11
+ * routing hint for the host's channel layer (which Codex/engine thread the
12
+ * reply belongs to); the transport ignores it. A host without an engine thread
13
+ * (claudemux) omits it entirely.
14
+ */
15
+ export {};
16
+ //# sourceMappingURL=outbound.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outbound.js","sourceRoot":"","sources":["../../src/contract/outbound.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG"}
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Shared types for the Feishu channel.
3
+ *
4
+ * The access-control state defined here is what gets persisted by a host's
5
+ * AccessStore and what the pure `gate` function reasons over. Ported verbatim
6
+ * from claudemux's `feishu-channel/src/types.ts` — the source of truth — so the
7
+ * gate logic stays diffable against its origin.
8
+ */
9
+ /** Access-control policy for direct (1:1) messages. */
10
+ export type DmPolicy = 'pairing' | 'allowlist' | 'disabled';
11
+ /**
12
+ * Access-control policy for group messages — the switch that selects one of
13
+ * three group-access modes:
14
+ *
15
+ * - `block` — every group message is dropped; the bot ignores groups.
16
+ * - `allowlist` — a group is authorized as a unit, by pairing: an @-mention
17
+ * in an unconfigured group posts a code the operator
18
+ * approves, adding the group to `Access.groups`.
19
+ * - `follow-user` — no group is authorized; a group message is delivered when
20
+ * the bot is @-mentioned and the sender's open_id is on the
21
+ * top-level `allowFrom` allowlist.
22
+ */
23
+ export type GroupPolicy = 'block' | 'allowlist' | 'follow-user';
24
+ /**
25
+ * Per-group access settings, keyed in Access.groups by the group's chat_id.
26
+ * Consulted only under the `allowlist` group policy.
27
+ */
28
+ export interface GroupEntry {
29
+ /** Require the bot to be @-mentioned before a group message is delivered. */
30
+ requireMention: boolean;
31
+ /** When non-empty, only these sender open_ids may trigger the bot here. */
32
+ allowFrom: string[];
33
+ }
34
+ /**
35
+ * A pending pairing request, keyed in Access.pending by its pairing code.
36
+ *
37
+ * `kind` says what approving the code authorizes: a `dm` request adds
38
+ * `senderId` to the top-level `allowFrom`; a `group` request adds `chatId` to
39
+ * `groups`. The two kinds share this one map and the one approval gesture.
40
+ */
41
+ export interface PendingEntry {
42
+ /** What approving this code authorizes — a direct sender, or a group. */
43
+ kind: 'dm' | 'group';
44
+ /**
45
+ * open_id of the awaiting party: the sender for a `dm` request, or the
46
+ * group member whose @-mention triggered a `group` request.
47
+ */
48
+ senderId: string;
49
+ /**
50
+ * chat_id the request arrived in — the direct chat for a `dm` request, or
51
+ * the group itself for a `group` request (the id approval adds to `groups`).
52
+ */
53
+ chatId: string;
54
+ /** Epoch millis the request was created. */
55
+ createdAt: number;
56
+ /** Epoch millis the request expires. */
57
+ expiresAt: number;
58
+ /** How many pairing-code replies were sent. A `group` request sends once. */
59
+ replies: number;
60
+ }
61
+ /** The full access-control state — persisted verbatim by a host's AccessStore. */
62
+ export interface Access {
63
+ dmPolicy: DmPolicy;
64
+ /** How group messages are gated — see `GroupPolicy`. */
65
+ groupPolicy: GroupPolicy;
66
+ /** Sender open_ids allowed to reach the bot — in direct messages and groups. */
67
+ allowFrom: string[];
68
+ /** Per-group policy, keyed by chat_id. Consulted only under the `allowlist` group policy. */
69
+ groups: Record<string, GroupEntry>;
70
+ /** Pending pairing requests, keyed by pairing code. */
71
+ pending: Record<string, PendingEntry>;
72
+ }
73
+ /** One @-mention inside an inbound Feishu message. */
74
+ export interface Mention {
75
+ /** The placeholder token (e.g. `@_user_1`) used in the message text. */
76
+ key: string;
77
+ /** Resolved identity of the mentioned party. */
78
+ id?: {
79
+ open_id?: string;
80
+ union_id?: string;
81
+ user_id?: string;
82
+ };
83
+ /** Display name of the mentioned party. */
84
+ name?: string;
85
+ }
86
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/contract/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,uDAAuD;AACvD,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,WAAW,GAAG,UAAU,CAAA;AAE3D;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,WAAW,GAAG,aAAa,CAAA;AAE/D;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,6EAA6E;IAC7E,cAAc,EAAE,OAAO,CAAA;IACvB,2EAA2E;IAC3E,SAAS,EAAE,MAAM,EAAE,CAAA;CACpB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,YAAY;IAC3B,yEAAyE;IACzE,IAAI,EAAE,IAAI,GAAG,OAAO,CAAA;IACpB;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAA;IAChB;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAA;IACd,4CAA4C;IAC5C,SAAS,EAAE,MAAM,CAAA;IACjB,wCAAwC;IACxC,SAAS,EAAE,MAAM,CAAA;IACjB,6EAA6E;IAC7E,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,kFAAkF;AAClF,MAAM,WAAW,MAAM;IACrB,QAAQ,EAAE,QAAQ,CAAA;IAClB,wDAAwD;IACxD,WAAW,EAAE,WAAW,CAAA;IACxB,gFAAgF;IAChF,SAAS,EAAE,MAAM,EAAE,CAAA;IACnB,6FAA6F;IAC7F,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;IAClC,uDAAuD;IACvD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;CACtC;AAED,sDAAsD;AACtD,MAAM,WAAW,OAAO;IACtB,wEAAwE;IACxE,GAAG,EAAE,MAAM,CAAA;IACX,gDAAgD;IAChD,EAAE,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC9D,2CAA2C;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAA;CACd"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Shared types for the Feishu channel.
3
+ *
4
+ * The access-control state defined here is what gets persisted by a host's
5
+ * AccessStore and what the pure `gate` function reasons over. Ported verbatim
6
+ * from claudemux's `feishu-channel/src/types.ts` — the source of truth — so the
7
+ * gate logic stays diffable against its origin.
8
+ */
9
+ export {};
10
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/contract/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * `@excitedjs/feishu-transport` — the shared Feishu platform-I/O core.
3
+ *
4
+ * The single owner of the `@larksuiteoapi/node-sdk` import: connect / receive /
5
+ * send / auth / render (md→card) / parse (Feishu content→text) + the pure,
6
+ * stateless access policy (`gate` / pairing). Stateless and routing-agnostic —
7
+ * it knows nothing about engine threads, sessions, or drop/deliver decisions;
8
+ * those live in each host's channel layer. Imported in-process by both dreamux
9
+ * (`@excitedjs/feishu-channel`) and claudemux's proxy.
10
+ *
11
+ * See dreamux#25 / claudemux#155 for the responsibility model and contract.
12
+ */
13
+ export type { Access, DmPolicy, GroupPolicy, GroupEntry, PendingEntry, Mention, } from './contract/types.js';
14
+ export type { OutboundTarget } from './contract/outbound.js';
15
+ export type { AccessStore } from './contract/access-store.js';
16
+ export { parseInbound, applyMentions, extractPostText, type InboundMessage, type ParsedInbound, } from './parse/content.js';
17
+ export { normalizeCommentEvent, DOC_COMMENT_EVENT_TYPE, type FeishuCommentEvent, } from './parse/comment.js';
18
+ export { gate, isBotMentioned, pruneExpiredPending, isBotSenderType, isGroupAuthorized, MAX_PENDING, MAX_PAIRING_REPLIES, PAIRING_TTL_MS, type GateInput, type GateResult, } from './policy/gate.js';
19
+ export { generatePairingCode, PAIRING_CODE_BYTES, PAIRING_CODE_LENGTH, } from './policy/pairing.js';
20
+ export { renderMarkdownToCards, cardToContent, cardContentBytes, splitMarkdownByBytes, FEISHU_CARD_REQUEST_LIMIT_BYTES, FEISHU_CARD_ELEMENT_HARD_CAP, CELL_MAX_BYTES, type RenderedCard, } from './render/render.js';
21
+ export { createFeishuTransport, commentFromBatchQuery, textMessageContent, FEISHU_CARD_CONTENT_SAFE_BYTES, type FeishuTransport, type FeishuCredentials, type FeishuTransportOptions, type FeishuSendResult, type FeishuDocComment, type FeishuDocCommentReply, type FeishuDocMeta, type InboundRoutes, type RouteHandler, } from './transport/feishu.js';
22
+ export { isRecord, asString } from './json.js';
23
+ /**
24
+ * Package marker — a stable export the channel layer can import to assert the
25
+ * core resolves end to end. Kept from the PR0 scaffold so the `feishu-channel`
26
+ * smoke test stays green.
27
+ */
28
+ export declare const FEISHU_TRANSPORT_PACKAGE = "@excitedjs/feishu-transport";
29
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,YAAY,EACV,MAAM,EACN,QAAQ,EACR,WAAW,EACX,UAAU,EACV,YAAY,EACZ,OAAO,GACR,MAAM,qBAAqB,CAAA;AAC5B,YAAY,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAC5D,YAAY,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAA;AAG7D,OAAO,EACL,YAAY,EACZ,aAAa,EACb,eAAe,EACf,KAAK,cAAc,EACnB,KAAK,aAAa,GACnB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACL,qBAAqB,EACrB,sBAAsB,EACtB,KAAK,kBAAkB,GACxB,MAAM,oBAAoB,CAAA;AAG3B,OAAO,EACL,IAAI,EACJ,cAAc,EACd,mBAAmB,EACnB,eAAe,EACf,iBAAiB,EACjB,WAAW,EACX,mBAAmB,EACnB,cAAc,EACd,KAAK,SAAS,EACd,KAAK,UAAU,GAChB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,qBAAqB,CAAA;AAG5B,OAAO,EACL,qBAAqB,EACrB,aAAa,EACb,gBAAgB,EAChB,oBAAoB,EACpB,+BAA+B,EAC/B,4BAA4B,EAC5B,cAAc,EACd,KAAK,YAAY,GAClB,MAAM,oBAAoB,CAAA;AAG3B,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,kBAAkB,EAClB,8BAA8B,EAC9B,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,EAC3B,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,EAC1B,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,YAAY,GAClB,MAAM,uBAAuB,CAAA;AAG9B,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAE9C;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,gCAAgC,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,31 @@
1
+ /**
2
+ * `@excitedjs/feishu-transport` — the shared Feishu platform-I/O core.
3
+ *
4
+ * The single owner of the `@larksuiteoapi/node-sdk` import: connect / receive /
5
+ * send / auth / render (md→card) / parse (Feishu content→text) + the pure,
6
+ * stateless access policy (`gate` / pairing). Stateless and routing-agnostic —
7
+ * it knows nothing about engine threads, sessions, or drop/deliver decisions;
8
+ * those live in each host's channel layer. Imported in-process by both dreamux
9
+ * (`@excitedjs/feishu-channel`) and claudemux's proxy.
10
+ *
11
+ * See dreamux#25 / claudemux#155 for the responsibility model and contract.
12
+ */
13
+ // ── parse/ — Feishu content → forwardable text + comment-event decode ──
14
+ export { parseInbound, applyMentions, extractPostText, } from './parse/content.js';
15
+ export { normalizeCommentEvent, DOC_COMMENT_EVENT_TYPE, } from './parse/comment.js';
16
+ // ── policy/ — pure access gate + pairing ──
17
+ export { gate, isBotMentioned, pruneExpiredPending, isBotSenderType, isGroupAuthorized, MAX_PENDING, MAX_PAIRING_REPLIES, PAIRING_TTL_MS, } from './policy/gate.js';
18
+ export { generatePairingCode, PAIRING_CODE_BYTES, PAIRING_CODE_LENGTH, } from './policy/pairing.js';
19
+ // ── render/ — markdown → Feishu v2 card (incl. inline `<@open_id>` mentions) ──
20
+ export { renderMarkdownToCards, cardToContent, cardContentBytes, splitMarkdownByBytes, FEISHU_CARD_REQUEST_LIMIT_BYTES, FEISHU_CARD_ELEMENT_HARD_CAP, CELL_MAX_BYTES, } from './render/render.js';
21
+ // ── transport/ — the Feishu SDK boundary (the only lark importer) ──
22
+ export { createFeishuTransport, commentFromBatchQuery, textMessageContent, FEISHU_CARD_CONTENT_SAFE_BYTES, } from './transport/feishu.js';
23
+ // ── small shared util ──
24
+ export { isRecord, asString } from './json.js';
25
+ /**
26
+ * Package marker — a stable export the channel layer can import to assert the
27
+ * core resolves end to end. Kept from the PR0 scaffold so the `feishu-channel`
28
+ * smoke test stays green.
29
+ */
30
+ export const FEISHU_TRANSPORT_PACKAGE = '@excitedjs/feishu-transport';
31
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAeH,0EAA0E;AAC1E,OAAO,EACL,YAAY,EACZ,aAAa,EACb,eAAe,GAGhB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACL,qBAAqB,EACrB,sBAAsB,GAEvB,MAAM,oBAAoB,CAAA;AAE3B,6CAA6C;AAC7C,OAAO,EACL,IAAI,EACJ,cAAc,EACd,mBAAmB,EACnB,eAAe,EACf,iBAAiB,EACjB,WAAW,EACX,mBAAmB,EACnB,cAAc,GAGf,MAAM,kBAAkB,CAAA;AACzB,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,qBAAqB,CAAA;AAE5B,iFAAiF;AACjF,OAAO,EACL,qBAAqB,EACrB,aAAa,EACb,gBAAgB,EAChB,oBAAoB,EACpB,+BAA+B,EAC/B,4BAA4B,EAC5B,cAAc,GAEf,MAAM,oBAAoB,CAAA;AAE3B,sEAAsE;AACtE,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,kBAAkB,EAClB,8BAA8B,GAU/B,MAAM,uBAAuB,CAAA;AAE9B,0BAA0B;AAC1B,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAE9C;;;;GAIG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,6BAA6B,CAAA"}
package/dist/json.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Tiny structural helpers for reading untyped JSON — the shape every raw
3
+ * Feishu event payload arrives in. Used by the comment decoder, which decodes
4
+ * its event_type's payload defensively. Ported verbatim from claudemux.
5
+ */
6
+ /** True when `v` is a non-null object, and therefore safe to index. */
7
+ export declare function isRecord(v: unknown): v is Record<string, unknown>;
8
+ /** `v` when it is a string, otherwise the empty string. */
9
+ export declare function asString(v: unknown): string;
10
+ //# sourceMappingURL=json.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../src/json.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,uEAAuE;AACvE,wBAAgB,QAAQ,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEjE;AAED,2DAA2D;AAC3D,wBAAgB,QAAQ,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,CAE3C"}
package/dist/json.js ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Tiny structural helpers for reading untyped JSON — the shape every raw
3
+ * Feishu event payload arrives in. Used by the comment decoder, which decodes
4
+ * its event_type's payload defensively. Ported verbatim from claudemux.
5
+ */
6
+ /** True when `v` is a non-null object, and therefore safe to index. */
7
+ export function isRecord(v) {
8
+ return v !== null && typeof v === 'object';
9
+ }
10
+ /** `v` when it is a string, otherwise the empty string. */
11
+ export function asString(v) {
12
+ return typeof v === 'string' ? v : '';
13
+ }
14
+ //# sourceMappingURL=json.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json.js","sourceRoot":"","sources":["../src/json.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,uEAAuE;AACvE,MAAM,UAAU,QAAQ,CAAC,CAAU;IACjC,OAAO,CAAC,KAAK,IAAI,IAAI,OAAO,CAAC,KAAK,QAAQ,CAAA;AAC5C,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,QAAQ,CAAC,CAAU;IACjC,OAAO,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;AACvC,CAAC"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Decoding the `drive.notice.comment_add_v1` event payload.
3
+ *
4
+ * The Feishu SDK's own `normalizeComment` is the authoritative decoder for this
5
+ * event — it tolerates both the flat and the `notice_meta`-nested payload
6
+ * variants Feishu sends, which a hand-written path table is bound to drift from.
7
+ * Because the SDK is the only Feishu-platform import in this package, this
8
+ * decoder lives in core; a host's comment handler calls it instead of importing
9
+ * the lark SDK itself (claudemux#155 §六.2 / dreamux#25 §7.1).
10
+ *
11
+ * The decode is pure: no I/O, never throws. Enriching the comment (fetching its
12
+ * text and the document title) is the transport's `fetchDocComment` /
13
+ * `fetchDocMeta`; shaping the notification body is the host handler's job.
14
+ */
15
+ /** The Feishu event_type this decoder is for. */
16
+ export declare const DOC_COMMENT_EVENT_TYPE = "drive.notice.comment_add_v1";
17
+ /** A normalized document-comment event — the identifying fields the payload carries. */
18
+ export interface FeishuCommentEvent {
19
+ /** Token of the document the comment is on. */
20
+ fileToken: string;
21
+ /** Document type — `doc` / `docx` / `sheet` / `bitable` / ... */
22
+ fileType: string;
23
+ /** Comment id. */
24
+ commentId: string;
25
+ /** Reply id — set only when the event is a reply within a thread, `''` otherwise. */
26
+ replyId: string;
27
+ /** open_id of the commenter. */
28
+ commenterId: string;
29
+ /** True when the comment @-mentions the bot. */
30
+ mentionedBot: boolean;
31
+ }
32
+ /**
33
+ * Reshape a raw `drive.notice.comment_add_v1` payload into a
34
+ * `FeishuCommentEvent`, using the Feishu SDK's `normalizeComment` as the
35
+ * decoder. Returns `null` for a non-object input or a payload the SDK cannot
36
+ * resolve a file token, file type, comment id, and commenter from. Pure: no
37
+ * I/O, never throws. Tolerates either the event body alone (what the SDK's
38
+ * `EventDispatcher` delivers) or a full `{ event: ... }` envelope.
39
+ */
40
+ export declare function normalizeCommentEvent(raw: unknown): FeishuCommentEvent | null;
41
+ //# sourceMappingURL=comment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comment.d.ts","sourceRoot":"","sources":["../../src/parse/comment.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAMH,iDAAiD;AACjD,eAAO,MAAM,sBAAsB,gCAAgC,CAAA;AAEnE,wFAAwF;AACxF,MAAM,WAAW,kBAAkB;IACjC,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAA;IACjB,iEAAiE;IACjE,QAAQ,EAAE,MAAM,CAAA;IAChB,kBAAkB;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,qFAAqF;IACrF,OAAO,EAAE,MAAM,CAAA;IACf,gCAAgC;IAChC,WAAW,EAAE,MAAM,CAAA;IACnB,gDAAgD;IAChD,YAAY,EAAE,OAAO,CAAA;CACtB;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,GAAG,kBAAkB,GAAG,IAAI,CAsB7E"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Decoding the `drive.notice.comment_add_v1` event payload.
3
+ *
4
+ * The Feishu SDK's own `normalizeComment` is the authoritative decoder for this
5
+ * event — it tolerates both the flat and the `notice_meta`-nested payload
6
+ * variants Feishu sends, which a hand-written path table is bound to drift from.
7
+ * Because the SDK is the only Feishu-platform import in this package, this
8
+ * decoder lives in core; a host's comment handler calls it instead of importing
9
+ * the lark SDK itself (claudemux#155 §六.2 / dreamux#25 §7.1).
10
+ *
11
+ * The decode is pure: no I/O, never throws. Enriching the comment (fetching its
12
+ * text and the document title) is the transport's `fetchDocComment` /
13
+ * `fetchDocMeta`; shaping the notification body is the host handler's job.
14
+ */
15
+ import * as lark from '@larksuiteoapi/node-sdk';
16
+ import { isRecord } from '../json.js';
17
+ /** The Feishu event_type this decoder is for. */
18
+ export const DOC_COMMENT_EVENT_TYPE = 'drive.notice.comment_add_v1';
19
+ /**
20
+ * Reshape a raw `drive.notice.comment_add_v1` payload into a
21
+ * `FeishuCommentEvent`, using the Feishu SDK's `normalizeComment` as the
22
+ * decoder. Returns `null` for a non-object input or a payload the SDK cannot
23
+ * resolve a file token, file type, comment id, and commenter from. Pure: no
24
+ * I/O, never throws. Tolerates either the event body alone (what the SDK's
25
+ * `EventDispatcher` delivers) or a full `{ event: ... }` envelope.
26
+ */
27
+ export function normalizeCommentEvent(raw) {
28
+ if (!isRecord(raw))
29
+ return null;
30
+ const event = isRecord(raw.event) ? raw.event : raw;
31
+ let decoded;
32
+ try {
33
+ decoded = lark.normalizeComment(event);
34
+ }
35
+ catch {
36
+ // `normalizeComment` is pure but not contractually total — guard it so a
37
+ // surprising input shape is a dropped event, not a thrown one.
38
+ return null;
39
+ }
40
+ if (!decoded)
41
+ return null;
42
+ return {
43
+ fileToken: decoded.fileToken,
44
+ fileType: decoded.fileType,
45
+ commentId: decoded.commentId,
46
+ replyId: decoded.replyId ?? '',
47
+ commenterId: decoded.operator.openId,
48
+ mentionedBot: decoded.mentionedBot,
49
+ };
50
+ }
51
+ //# sourceMappingURL=comment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comment.js","sourceRoot":"","sources":["../../src/parse/comment.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,IAAI,MAAM,yBAAyB,CAAA;AAE/C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAErC,iDAAiD;AACjD,MAAM,CAAC,MAAM,sBAAsB,GAAG,6BAA6B,CAAA;AAkBnE;;;;;;;GAOG;AACH,MAAM,UAAU,qBAAqB,CAAC,GAAY;IAChD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAA;IAC/B,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAA;IAEnD,IAAI,OAAiC,CAAA;IACrC,IAAI,CAAC;QACH,OAAO,GAAG,IAAI,CAAC,gBAAgB,CAAC,KAA6B,CAAC,CAAA;IAChE,CAAC;IAAC,MAAM,CAAC;QACP,yEAAyE;QACzE,+DAA+D;QAC/D,OAAO,IAAI,CAAA;IACb,CAAC;IACD,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAA;IAEzB,OAAO;QACL,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,EAAE;QAC9B,WAAW,EAAE,OAAO,CAAC,QAAQ,CAAC,MAAM;QACpC,YAAY,EAAE,OAAO,CAAC,YAAY;KACnC,CAAA;AACH,CAAC"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Parsing inbound Feishu message content.
3
+ *
4
+ * Feishu delivers `message.content` as a JSON-encoded string whose shape
5
+ * depends on `message_type`. This module turns that into the plain text the
6
+ * channel forwards to the engine. Attachment message types (image, file) are
7
+ * summarized as a short text marker — the channel forwards text, not binaries.
8
+ *
9
+ * Ported verbatim from claudemux's `feishu-channel/src/content.ts` (the source
10
+ * of truth — it carries the `interactive`-card parse dreamux's drifted copy had
11
+ * lost); only the `./types` import was repointed to `../contract/types`.
12
+ */
13
+ import type { Mention } from '../contract/types.js';
14
+ /** The subset of an inbound Feishu message this module reads. */
15
+ export interface InboundMessage {
16
+ message_type?: string;
17
+ /** JSON-encoded content string, as delivered by Feishu. */
18
+ content?: string;
19
+ mentions?: Mention[];
20
+ }
21
+ export interface ParsedInbound {
22
+ /** Human-readable text to forward to the engine. */
23
+ text: string;
24
+ }
25
+ /**
26
+ * Parse one inbound Feishu message into forwardable text. Never throws —
27
+ * malformed content falls back to a best-effort string so a weird message
28
+ * still reaches the engine.
29
+ */
30
+ export declare function parseInbound(message: InboundMessage): ParsedInbound;
31
+ /**
32
+ * Replace Feishu's `@_user_N` placeholders in text with the mentioned display
33
+ * names, so the forwarded message reads naturally.
34
+ */
35
+ export declare function applyMentions(text: string, mentions: Mention[] | undefined): string;
36
+ /**
37
+ * Flatten a Feishu rich-text "post" payload into plain text. A post is
38
+ * locale-wrapped (`{ zh_cn: { title, content } }`) and its body is an array of
39
+ * paragraphs, each an array of tagged inline elements.
40
+ */
41
+ export declare function extractPostText(content: Record<string, unknown>): string;
42
+ //# sourceMappingURL=content.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content.d.ts","sourceRoot":"","sources":["../../src/parse/content.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAA;AAEnD,iEAAiE;AACjE,MAAM,WAAW,cAAc;IAC7B,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,2DAA2D;IAC3D,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAA;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,oDAAoD;IACpD,IAAI,EAAE,MAAM,CAAA;CACb;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,cAAc,GAAG,aAAa,CA6BnE;AAiGD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,SAAS,GAAG,MAAM,CASnF;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAexE"}