@excitedjs/feishu-channel 6.3.0-beta.264 → 6.3.0-beta.271

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 (316) hide show
  1. package/README.md +54 -26
  2. package/dist/access/gate.d.ts +52 -0
  3. package/dist/access/gate.d.ts.map +1 -0
  4. package/dist/{feishu-gate.js → access/gate.js} +36 -135
  5. package/dist/access/gate.js.map +1 -0
  6. package/dist/access/index.d.ts +100 -0
  7. package/dist/access/index.d.ts.map +1 -0
  8. package/dist/access/index.js +350 -0
  9. package/dist/access/index.js.map +1 -0
  10. package/dist/access/state.d.ts +43 -0
  11. package/dist/access/state.d.ts.map +1 -0
  12. package/dist/access/state.js +41 -0
  13. package/dist/access/state.js.map +1 -0
  14. package/dist/{feishu-ask-user.d.ts → ask-user/registry.d.ts} +34 -27
  15. package/dist/ask-user/registry.d.ts.map +1 -0
  16. package/dist/{feishu-ask-user.js → ask-user/registry.js} +43 -49
  17. package/dist/ask-user/registry.js.map +1 -0
  18. package/dist/bot.d.ts +21 -118
  19. package/dist/bot.d.ts.map +1 -1
  20. package/dist/bot.js +35 -276
  21. package/dist/bot.js.map +1 -1
  22. package/dist/card-actions.d.ts +34 -0
  23. package/dist/card-actions.d.ts.map +1 -0
  24. package/dist/card-actions.js +42 -0
  25. package/dist/card-actions.js.map +1 -0
  26. package/dist/{feishu-ask-user-card.d.ts → cards/ask-user.d.ts} +2 -8
  27. package/dist/cards/ask-user.d.ts.map +1 -0
  28. package/dist/{feishu-ask-user-card.js → cards/ask-user.js} +5 -14
  29. package/dist/cards/ask-user.js.map +1 -0
  30. package/dist/{feishu-binding-notification-card.d.ts → cards/binding-notification.d.ts} +3 -3
  31. package/dist/cards/binding-notification.d.ts.map +1 -0
  32. package/dist/{feishu-binding-notification-card.js → cards/binding-notification.js} +94 -101
  33. package/dist/cards/binding-notification.js.map +1 -0
  34. package/dist/cards/kit.d.ts +52 -0
  35. package/dist/cards/kit.d.ts.map +1 -0
  36. package/dist/cards/kit.js +63 -0
  37. package/dist/cards/kit.js.map +1 -0
  38. package/dist/{feishu-pairing-card.d.ts → cards/pairing.d.ts} +1 -4
  39. package/dist/cards/pairing.d.ts.map +1 -0
  40. package/dist/{feishu-pairing-card.js → cards/pairing.js} +14 -16
  41. package/dist/cards/pairing.js.map +1 -0
  42. package/dist/{feishu-running-teams-card.d.ts → cards/running-teams.d.ts} +2 -2
  43. package/dist/cards/running-teams.d.ts.map +1 -0
  44. package/dist/{feishu-running-teams-card.js → cards/running-teams.js} +16 -8
  45. package/dist/cards/running-teams.js.map +1 -0
  46. package/dist/chat-bots-store.d.ts +20 -11
  47. package/dist/chat-bots-store.d.ts.map +1 -1
  48. package/dist/chat-bots-store.js +125 -102
  49. package/dist/chat-bots-store.js.map +1 -1
  50. package/dist/{feishu-cot-adapter.d.ts → cot/adapter.d.ts} +93 -30
  51. package/dist/cot/adapter.d.ts.map +1 -0
  52. package/dist/{feishu-cot-adapter.js → cot/adapter.js} +216 -133
  53. package/dist/cot/adapter.js.map +1 -0
  54. package/dist/cot/bytes.d.ts +41 -0
  55. package/dist/cot/bytes.d.ts.map +1 -0
  56. package/dist/cot/bytes.js +144 -0
  57. package/dist/cot/bytes.js.map +1 -0
  58. package/dist/{feishu-cot-events.d.ts → cot/card.d.ts} +47 -25
  59. package/dist/cot/card.d.ts.map +1 -0
  60. package/dist/cot/card.js +468 -0
  61. package/dist/cot/card.js.map +1 -0
  62. package/dist/{feishu-cot-diagnostics.d.ts → cot/diagnostics.d.ts} +5 -6
  63. package/dist/cot/diagnostics.d.ts.map +1 -0
  64. package/dist/{feishu-cot-diagnostics.js → cot/diagnostics.js} +2 -2
  65. package/dist/cot/diagnostics.js.map +1 -0
  66. package/dist/cot/inbound-correlations.d.ts +12 -0
  67. package/dist/cot/inbound-correlations.d.ts.map +1 -0
  68. package/dist/{feishu-inbound-anchor.js → cot/inbound-correlations.js} +5 -2
  69. package/dist/cot/inbound-correlations.js.map +1 -0
  70. package/dist/{feishu-cot-io.d.ts → cot/io.d.ts} +13 -7
  71. package/dist/cot/io.d.ts.map +1 -0
  72. package/dist/{feishu-cot-io.js → cot/io.js} +5 -5
  73. package/dist/cot/io.js.map +1 -0
  74. package/dist/{feishu-cot-state.d.ts → cot/recipients.d.ts} +42 -18
  75. package/dist/cot/recipients.d.ts.map +1 -0
  76. package/dist/{feishu-cot-state.js → cot/recipients.js} +93 -36
  77. package/dist/cot/recipients.js.map +1 -0
  78. package/dist/extension.d.ts +156 -0
  79. package/dist/extension.d.ts.map +1 -0
  80. package/dist/extension.js +2 -0
  81. package/dist/extension.js.map +1 -0
  82. package/dist/feishu-bounded-operation.d.ts +1 -2
  83. package/dist/feishu-bounded-operation.d.ts.map +1 -1
  84. package/dist/feishu-bounded-operation.js +5 -5
  85. package/dist/feishu-bounded-operation.js.map +1 -1
  86. package/dist/feishu-core-commands.d.ts +30 -0
  87. package/dist/feishu-core-commands.d.ts.map +1 -0
  88. package/dist/feishu-core-commands.js +81 -0
  89. package/dist/feishu-core-commands.js.map +1 -0
  90. package/dist/feishu-document-comments.d.ts +16 -14
  91. package/dist/feishu-document-comments.d.ts.map +1 -1
  92. package/dist/feishu-document-comments.js +15 -11
  93. package/dist/feishu-document-comments.js.map +1 -1
  94. package/dist/feishu-extensions.d.ts +94 -0
  95. package/dist/feishu-extensions.d.ts.map +1 -0
  96. package/dist/feishu-extensions.js +237 -0
  97. package/dist/feishu-extensions.js.map +1 -0
  98. package/dist/feishu-provisioning.d.ts +8 -13
  99. package/dist/feishu-provisioning.d.ts.map +1 -1
  100. package/dist/feishu-provisioning.js +26 -20
  101. package/dist/feishu-provisioning.js.map +1 -1
  102. package/dist/feishu-slash-commands.d.ts +10 -6
  103. package/dist/feishu-slash-commands.d.ts.map +1 -1
  104. package/dist/feishu-slash-commands.js +33 -24
  105. package/dist/feishu-slash-commands.js.map +1 -1
  106. package/dist/feishu-submit.d.ts +36 -5
  107. package/dist/feishu-submit.d.ts.map +1 -1
  108. package/dist/feishu-submit.js +19 -2
  109. package/dist/feishu-submit.js.map +1 -1
  110. package/dist/{feishu-message.d.ts → inbound/attachments.d.ts} +4 -6
  111. package/dist/inbound/attachments.d.ts.map +1 -0
  112. package/dist/{feishu-message.js → inbound/attachments.js} +13 -8
  113. package/dist/inbound/attachments.js.map +1 -0
  114. package/dist/{feishu-inbound-enrichment.d.ts → inbound/enrich.d.ts} +4 -3
  115. package/dist/inbound/enrich.d.ts.map +1 -0
  116. package/dist/{feishu-inbound-enrichment.js → inbound/enrich.js} +4 -4
  117. package/dist/inbound/enrich.js.map +1 -0
  118. package/dist/inbound/pipeline.d.ts +48 -0
  119. package/dist/inbound/pipeline.d.ts.map +1 -0
  120. package/dist/{feishu-session-inbound.js → inbound/pipeline.js} +131 -162
  121. package/dist/inbound/pipeline.js.map +1 -0
  122. package/dist/{feishu-message-render.d.ts → inbound/render.d.ts} +4 -4
  123. package/dist/inbound/render.d.ts.map +1 -0
  124. package/dist/{feishu-message-render.js → inbound/render.js} +6 -17
  125. package/dist/inbound/render.js.map +1 -0
  126. package/dist/{feishu-reply-ancestry.d.ts → inbound/reply-ancestry.d.ts} +2 -2
  127. package/dist/inbound/reply-ancestry.d.ts.map +1 -0
  128. package/dist/{feishu-reply-ancestry.js → inbound/reply-ancestry.js} +4 -2
  129. package/dist/inbound/reply-ancestry.js.map +1 -0
  130. package/dist/inbound/router.d.ts +58 -0
  131. package/dist/inbound/router.d.ts.map +1 -0
  132. package/dist/inbound/router.js +87 -0
  133. package/dist/inbound/router.js.map +1 -0
  134. package/dist/inbound/target.d.ts +79 -0
  135. package/dist/inbound/target.d.ts.map +1 -0
  136. package/dist/{feishu-target-router.js → inbound/target.js} +36 -58
  137. package/dist/inbound/target.js.map +1 -0
  138. package/dist/{feishu-inbound-work.d.ts → inbound/work.d.ts} +3 -8
  139. package/dist/inbound/work.d.ts.map +1 -0
  140. package/dist/{feishu-inbound-work.js → inbound/work.js} +10 -16
  141. package/dist/inbound/work.js.map +1 -0
  142. package/dist/index.d.ts +17 -22
  143. package/dist/index.d.ts.map +1 -1
  144. package/dist/index.js +11 -22
  145. package/dist/index.js.map +1 -1
  146. package/dist/introduce.d.ts +1 -1
  147. package/dist/introduce.d.ts.map +1 -1
  148. package/dist/introduce.js +2 -1
  149. package/dist/introduce.js.map +1 -1
  150. package/dist/outbound/index.d.ts +172 -0
  151. package/dist/outbound/index.d.ts.map +1 -0
  152. package/dist/outbound/index.js +346 -0
  153. package/dist/outbound/index.js.map +1 -0
  154. package/dist/plugin.d.ts +20 -0
  155. package/dist/plugin.d.ts.map +1 -0
  156. package/dist/plugin.js +20 -0
  157. package/dist/plugin.js.map +1 -0
  158. package/dist/provider.d.ts +8 -17
  159. package/dist/provider.d.ts.map +1 -1
  160. package/dist/provider.js +53 -58
  161. package/dist/provider.js.map +1 -1
  162. package/dist/routing/document.d.ts +11 -12
  163. package/dist/routing/document.d.ts.map +1 -1
  164. package/dist/routing/document.js.map +1 -1
  165. package/dist/routing/index.d.ts +50 -12
  166. package/dist/routing/index.d.ts.map +1 -1
  167. package/dist/routing/index.js +129 -54
  168. package/dist/routing/index.js.map +1 -1
  169. package/dist/routing/operations.d.ts +148 -0
  170. package/dist/routing/operations.d.ts.map +1 -0
  171. package/dist/routing/operations.js +234 -0
  172. package/dist/routing/operations.js.map +1 -0
  173. package/dist/routing/store.d.ts +28 -31
  174. package/dist/routing/store.d.ts.map +1 -1
  175. package/dist/routing/store.js +124 -81
  176. package/dist/routing/store.js.map +1 -1
  177. package/dist/routing/target.d.ts.map +1 -1
  178. package/dist/routing/target.js.map +1 -1
  179. package/dist/session/card-actions.d.ts +147 -0
  180. package/dist/session/card-actions.d.ts.map +1 -0
  181. package/dist/session/card-actions.js +440 -0
  182. package/dist/session/card-actions.js.map +1 -0
  183. package/dist/session/lifecycle.d.ts +20 -0
  184. package/dist/session/lifecycle.d.ts.map +1 -0
  185. package/dist/session/lifecycle.js +48 -0
  186. package/dist/session/lifecycle.js.map +1 -0
  187. package/dist/session/session.d.ts +88 -0
  188. package/dist/session/session.d.ts.map +1 -0
  189. package/dist/session/session.js +342 -0
  190. package/dist/session/session.js.map +1 -0
  191. package/dist/session/submitter.d.ts +29 -0
  192. package/dist/session/submitter.d.ts.map +1 -0
  193. package/dist/session/submitter.js +46 -0
  194. package/dist/session/submitter.js.map +1 -0
  195. package/dist/tools/ask-user-question.d.ts +1 -1
  196. package/dist/tools/ask-user-question.d.ts.map +1 -1
  197. package/dist/tools/ask-user-question.js +4 -2
  198. package/dist/tools/ask-user-question.js.map +1 -1
  199. package/dist/tools/document-tools.d.ts.map +1 -1
  200. package/dist/tools/document-tools.js +14 -10
  201. package/dist/tools/document-tools.js.map +1 -1
  202. package/dist/tools/messaging-tools.d.ts +0 -7
  203. package/dist/tools/messaging-tools.d.ts.map +1 -1
  204. package/dist/tools/messaging-tools.js +19 -7
  205. package/dist/tools/messaging-tools.js.map +1 -1
  206. package/dist/tools/registry.d.ts +5 -0
  207. package/dist/tools/registry.d.ts.map +1 -1
  208. package/dist/tools/registry.js +10 -3
  209. package/dist/tools/registry.js.map +1 -1
  210. package/dist/tools/routing-tools.d.ts.map +1 -1
  211. package/dist/tools/routing-tools.js +12 -12
  212. package/dist/tools/routing-tools.js.map +1 -1
  213. package/dist/{feishu-session-mcp.d.ts → tools/session-mcp.d.ts} +5 -3
  214. package/dist/tools/session-mcp.d.ts.map +1 -0
  215. package/dist/tools/session-mcp.js +66 -0
  216. package/dist/tools/session-mcp.js.map +1 -0
  217. package/dist/tools/space-tools.d.ts.map +1 -1
  218. package/dist/tools/space-tools.js +5 -8
  219. package/dist/tools/space-tools.js.map +1 -1
  220. package/dist/tools/types.d.ts +18 -84
  221. package/dist/tools/types.d.ts.map +1 -1
  222. package/package.json +7 -6
  223. package/dist/feishu-ask-user-card.d.ts.map +0 -1
  224. package/dist/feishu-ask-user-card.js.map +0 -1
  225. package/dist/feishu-ask-user.d.ts.map +0 -1
  226. package/dist/feishu-ask-user.js.map +0 -1
  227. package/dist/feishu-binding-notification-card.d.ts.map +0 -1
  228. package/dist/feishu-binding-notification-card.js.map +0 -1
  229. package/dist/feishu-card.d.ts +0 -22
  230. package/dist/feishu-card.d.ts.map +0 -1
  231. package/dist/feishu-card.js +0 -44
  232. package/dist/feishu-card.js.map +0 -1
  233. package/dist/feishu-channel.d.ts +0 -139
  234. package/dist/feishu-channel.d.ts.map +0 -1
  235. package/dist/feishu-channel.js +0 -506
  236. package/dist/feishu-channel.js.map +0 -1
  237. package/dist/feishu-cot-activity.d.ts +0 -65
  238. package/dist/feishu-cot-activity.d.ts.map +0 -1
  239. package/dist/feishu-cot-activity.js +0 -127
  240. package/dist/feishu-cot-activity.js.map +0 -1
  241. package/dist/feishu-cot-adapter.d.ts.map +0 -1
  242. package/dist/feishu-cot-adapter.js.map +0 -1
  243. package/dist/feishu-cot-diagnostics.d.ts.map +0 -1
  244. package/dist/feishu-cot-diagnostics.js.map +0 -1
  245. package/dist/feishu-cot-events.d.ts.map +0 -1
  246. package/dist/feishu-cot-events.js +0 -323
  247. package/dist/feishu-cot-events.js.map +0 -1
  248. package/dist/feishu-cot-io.d.ts.map +0 -1
  249. package/dist/feishu-cot-io.js.map +0 -1
  250. package/dist/feishu-cot-outbox.d.ts +0 -18
  251. package/dist/feishu-cot-outbox.d.ts.map +0 -1
  252. package/dist/feishu-cot-outbox.js +0 -65
  253. package/dist/feishu-cot-outbox.js.map +0 -1
  254. package/dist/feishu-cot-presentation.d.ts +0 -82
  255. package/dist/feishu-cot-presentation.d.ts.map +0 -1
  256. package/dist/feishu-cot-presentation.js +0 -208
  257. package/dist/feishu-cot-presentation.js.map +0 -1
  258. package/dist/feishu-cot-session.d.ts +0 -52
  259. package/dist/feishu-cot-session.d.ts.map +0 -1
  260. package/dist/feishu-cot-session.js +0 -132
  261. package/dist/feishu-cot-session.js.map +0 -1
  262. package/dist/feishu-cot-state.d.ts.map +0 -1
  263. package/dist/feishu-cot-state.js.map +0 -1
  264. package/dist/feishu-gate-io.d.ts +0 -39
  265. package/dist/feishu-gate-io.d.ts.map +0 -1
  266. package/dist/feishu-gate-io.js +0 -111
  267. package/dist/feishu-gate-io.js.map +0 -1
  268. package/dist/feishu-gate.d.ts +0 -106
  269. package/dist/feishu-gate.d.ts.map +0 -1
  270. package/dist/feishu-gate.js.map +0 -1
  271. package/dist/feishu-inbound-anchor.d.ts +0 -21
  272. package/dist/feishu-inbound-anchor.d.ts.map +0 -1
  273. package/dist/feishu-inbound-anchor.js.map +0 -1
  274. package/dist/feishu-inbound-enrichment.d.ts.map +0 -1
  275. package/dist/feishu-inbound-enrichment.js.map +0 -1
  276. package/dist/feishu-inbound-work.d.ts.map +0 -1
  277. package/dist/feishu-inbound-work.js.map +0 -1
  278. package/dist/feishu-message-render.d.ts.map +0 -1
  279. package/dist/feishu-message-render.js.map +0 -1
  280. package/dist/feishu-message.d.ts.map +0 -1
  281. package/dist/feishu-message.js.map +0 -1
  282. package/dist/feishu-pairing-card.d.ts.map +0 -1
  283. package/dist/feishu-pairing-card.js.map +0 -1
  284. package/dist/feishu-reply-ancestry.d.ts.map +0 -1
  285. package/dist/feishu-reply-ancestry.js.map +0 -1
  286. package/dist/feishu-route-reconciliation.d.ts +0 -55
  287. package/dist/feishu-route-reconciliation.d.ts.map +0 -1
  288. package/dist/feishu-route-reconciliation.js +0 -66
  289. package/dist/feishu-route-reconciliation.js.map +0 -1
  290. package/dist/feishu-running-teams-card.d.ts.map +0 -1
  291. package/dist/feishu-running-teams-card.js.map +0 -1
  292. package/dist/feishu-session-bindings.d.ts +0 -66
  293. package/dist/feishu-session-bindings.d.ts.map +0 -1
  294. package/dist/feishu-session-bindings.js +0 -117
  295. package/dist/feishu-session-bindings.js.map +0 -1
  296. package/dist/feishu-session-inbound.d.ts +0 -16
  297. package/dist/feishu-session-inbound.d.ts.map +0 -1
  298. package/dist/feishu-session-inbound.js.map +0 -1
  299. package/dist/feishu-session-mcp.d.ts.map +0 -1
  300. package/dist/feishu-session-mcp.js +0 -50
  301. package/dist/feishu-session-mcp.js.map +0 -1
  302. package/dist/feishu-session-ops.d.ts +0 -130
  303. package/dist/feishu-session-ops.d.ts.map +0 -1
  304. package/dist/feishu-session-ops.js +0 -483
  305. package/dist/feishu-session-ops.js.map +0 -1
  306. package/dist/feishu-target-router.d.ts +0 -84
  307. package/dist/feishu-target-router.d.ts.map +0 -1
  308. package/dist/feishu-target-router.js.map +0 -1
  309. package/dist/lib/mutex.d.ts +0 -9
  310. package/dist/lib/mutex.d.ts.map +0 -1
  311. package/dist/lib/mutex.js +0 -13
  312. package/dist/lib/mutex.js.map +0 -1
  313. package/dist/provider-ref.d.ts +0 -8
  314. package/dist/provider-ref.d.ts.map +0 -1
  315. package/dist/provider-ref.js +0 -8
  316. package/dist/provider-ref.js.map +0 -1
package/README.md CHANGED
@@ -1,13 +1,14 @@
1
1
  # @excitedjs/feishu-channel
2
2
 
3
- The built-in Feishu **`ChannelProvider`** for [Dreamux](../../dreamux) — the
4
- package behind the `builtin:feishu` provider reference. It implements the
3
+ The built-in Feishu **plugin** for [Dreamux](../../dreamux): it contributes the
4
+ Feishu `ChannelProvider` behind the `builtin:feishu` provider reference and
5
+ publishes an extension api for other plugins. The provider implements the
5
6
  neutral `@excitedjs/dreamux-types` channel contract on top of
6
7
  [`@excitedjs/feishu-transport`](../feishu-transport), which stays the sole owner
7
8
  of the Lark SDK.
8
9
 
9
- `@excitedjs/dreamux` depends on this package by default and resolves
10
- `builtin:feishu` to it, so the Feishu channel ships out of the box.
10
+ `@excitedjs/dreamux` depends on this package and always loads its plugin, so
11
+ the Feishu channel ships out of the box.
11
12
 
12
13
  ## What it owns
13
14
 
@@ -17,7 +18,7 @@ of the Lark SDK.
17
18
  store, read and written under a host-supplied state directory.
18
19
  - **Inbound normalization**: turning Feishu events into agent-facing channel
19
20
  results, including the Channel-owned inner body and inline `<attachment>`
20
- blocks. Agent runtimes own the outer `<channel source="feishu" …>` envelope.
21
+ blocks. Core owns the outer `<channel source="feishu" …>` envelope.
21
22
  - **Topic routing**: verifying `chat_mode=topic`, projecting `thread_id` as a
22
23
  neutral collaboration target, and recording exact per-message targets for
23
24
  scoped TeamLeader replies.
@@ -32,34 +33,46 @@ of the Lark SDK.
32
33
  ## What it does not own
33
34
 
34
35
  - It never imports `@excitedjs/dreamux` core, and never imports the Lark SDK
35
- directly — platform calls go through `@excitedjs/feishu-transport`. Both
36
- boundaries are enforced by `tests/import-boundary.test.ts`.
37
- - Dispatcher lifecycle, agent/Codex process supervision, routing, binding state,
38
- authorization, Team lifecycle, and the Feishu MCP **server descriptor** /
39
- admin-method routing stay in `@excitedjs/dreamux`. The host supplies the bot
36
+ directly — platform calls go through `@excitedjs/feishu-transport`. The Core
37
+ import boundary is enforced by the shared lint configuration.
38
+ - Dispatcher lifecycle, agent process supervision, Team lifecycle, the neutral
39
+ command port, and MCP lease/shim routing stay in `@excitedjs/dreamux`.
40
+ Feishu routing, binding state, access policy, and tool definitions belong to
41
+ this package. The host supplies the bot
40
42
  secret / app id and the state / cache directories; the package reconstructs no
41
43
  Dreamux host layout or path contract.
42
44
 
43
45
  ## Public API
44
46
 
45
- - `createFeishuChannelProvider()` plus the default-exported provider factory —
46
- builds the neutral `ChannelProvider` the generic channel loader registers for
47
- `builtin:feishu`. Its `createSession` returns a contract-valid `ChannelSession`
48
- (`reply` / `react` / `resolveTarget` / `tools` / `handleTool` /
49
- `messageBelongsToTarget`).
50
- - The session class plus the gate, chat-bots store, message formatter, MCP tool
51
- parser, and production bot adapter helpers used by the core adapter that
52
- drives the host-shaped session path.
53
-
54
- Inbound delivery receipts are status-only (`submitted`, `duplicate`, `stopped`,
55
- `failed`, or `ambiguous`). `failed` proves pre-admission rejection; `ambiguous`
56
- means delivery may have crossed the runtime boundary and is terminal for the
57
- inbound event, so Feishu does not replay it. The Channel contract does not
58
- expose a Dreamux Turn identifier. The read-only core event stream projects
47
+ - The default export is the neutral provider factory for
48
+ `npm:@excitedjs/feishu-channel`. `createFeishuPlugin()` is the named plugin
49
+ factory. The plugin
50
+ contributes the `feishu` channel provider (`builtin:feishu`) and publishes
51
+ `FeishuApi`, through which another plugin registers Feishu extensions: extra
52
+ MCP tools, card actions, and a per-channel-instance lifecycle with an
53
+ instance api (`FeishuExtension`, `FeishuInstanceApi`).
54
+ - `createFeishuChannelProvider()` — the same provider with no extensions. Its
55
+ `createSession` returns a `ChannelInstance`: a `session` with
56
+ `initialize` / `start` / `close`, plus the optional `mcp` capability beside it.
57
+ - The public extension contracts and inbound message formatter support plugin
58
+ authors and cross-package callers. Session, gate, stores, tool parsers, and
59
+ bot adapter implementations remain package-internal.
60
+
61
+ Successful submission receipts can carry a Dreamux Turn identifier. The
62
+ channel distinguishes admitted, duplicate, stopped, failed, ambiguous, and
63
+ proven Team-rejection outcomes. A proven pre-admission Team rejection permits
64
+ the bound-route fallback; ambiguous delivery is never replayed. The read-only
65
+ core event stream projects
59
66
  sanitized submitted, activity, and settled lifecycle facts for dispatcher and
60
67
  TeamLeader conversations; Feishu anchors each presentation to the turn's
61
68
  inbound message.
62
69
 
70
+ Binding a topic retires the displaced Team's presentation even when that Team
71
+ served the topic through its parent group. Removing an exact topic binding
72
+ retires that route's presentation and permits fresh submissions through a
73
+ remaining parent binding, including one to the same Team. A standing anchor
74
+ keeps the serving-route provenance captured when it was created.
75
+
63
76
  Test doubles are deliberately test-local and are not part of the published
64
77
  package API.
65
78
 
@@ -72,7 +85,7 @@ non-empty sender id. Other chat types fail with `unsupported_chat_type`, and
72
85
  other sender shapes fail with `sender_unknown`, before bot observation,
73
86
  `/introduce`, pairing, or delivery.
74
87
 
75
- The public `dreamuxFeishuGate` input is unchanged: it still has `chat_type` and
88
+ The `dreamuxFeishuGate` input is unchanged: it still has `chat_type` and
76
89
  `is_bot_sender` and has no `sender_kind`. Callers must perform the exact
77
90
  classification above first. Passing `is_bot_sender: false` asserts a known
78
91
  human; negating `isBotSenderType(...)` alone is not sufficient because unknown
@@ -98,7 +111,7 @@ takes effect when the new server starts.
98
111
  may deliver ordinary text in a trusted chat but cannot mutate peer-bot trust;
99
112
  the command is diagnosed as `sender_not_followed` and writes no trust.
100
113
 
101
- ## Feishu topic-group permission
114
+ ## Feishu chat information permission
102
115
 
103
116
  Topic collaboration routing reads the enclosing chat through Feishu's
104
117
  `im.v1.chat.get` API. The bot must have a group information read permission
@@ -111,6 +124,21 @@ display attributes, so runtimes render it in the model-visible `<channel>`
111
124
  envelope. Displaying that identifier does not classify an ordinary group
112
125
  thread as a collaboration topic.
113
126
 
127
+ The same read can decide who may answer a card, as a last resort. A click on
128
+ an ask-user or extension card names its chat but not whether that chat is
129
+ direct or a group, so the channel applies the inbound access policy for both
130
+ kinds and establishes the kind only when the two answers differ. It takes the
131
+ kind from the chat itself, never from the access lists it is judging: the kind
132
+ reported by the last admitted inbound message this session routed in that chat
133
+ (a group message dropped for lacking a mention, an untrusted bot's message, or
134
+ a consumed `/introduce` teaches nothing). Only then does it ask the platform. A person the policy
135
+ admits in a direct chat and in a group alike, or in neither, never triggers the
136
+ read. When the answer does depend on the kind and it cannot be established, the
137
+ click is refused with an error toast rather than degrading to an ordinary group
138
+ as topic detection does, which on an install whose bot lacks the group
139
+ information read permission affects only a click in a chat this session has not
140
+ yet routed an admitted message from.
141
+
114
142
  A confirmed topic target declares its enclosing group as a less-specific
115
143
  binding fallback. An exact topic binding wins first; a bound collaboration
116
144
  space then provisions or reuses the exact topic Team; only a topic group that
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Feishu access gate v3 — the pure decision function.
3
+ *
4
+ * `dreamuxFeishuGate` decides `deliver | drop | pair` against a snapshot of
5
+ * `DispatcherAccessStateV3` (`./state.js`) and returns the next state plus
6
+ * logs. It touches no disk and sends nothing; the session owns IO, the access
7
+ * store's serialized queue, and send-before-save for pairing prompts (see
8
+ * `./index.js`'s `FeishuAccess`).
9
+ */
10
+ import { type DispatcherAccessStateV3, type PendingPairingKind } from './state.js';
11
+ export declare function generatePairingToken(): string;
12
+ export declare function generateUniquePairingToken(existing: Record<string, unknown>): string;
13
+ export interface GateInbound {
14
+ chat_type: 'p2p' | 'group';
15
+ sender_id: string;
16
+ chat_id: string;
17
+ /**
18
+ * The session must classify the sender exactly before calling the gate:
19
+ * `false` means sender_type=user with a non-empty sender id, not merely
20
+ * "isBotSenderType returned false" for an unknown sender type.
21
+ */
22
+ is_bot_sender: boolean;
23
+ trusted_bot: boolean;
24
+ bot_mentioned: boolean;
25
+ }
26
+ export type DropReason = 'dm_disabled' | 'dm_not_on_allowlist' | 'dm_pairing_slot_cap' | 'group_policy_block' | 'group_bot_not_mentioned' | 'group_not_on_allowlist_and_not_mentioned' | 'group_follow_user_stranger_not_mentioned' | 'group_pairing_slot_cap' | 'group_not_on_allowlist' | 'group_user_not_on_allowlist' | 'group_pairing_stranger_not_mentioned' | 'bot_untrusted' | 'unsupported_chat_type' | 'internal';
27
+ export type GateAction = {
28
+ action: 'deliver';
29
+ } | {
30
+ action: 'drop';
31
+ reason: DropReason;
32
+ context?: Record<string, unknown>;
33
+ } | {
34
+ action: 'pair';
35
+ kind: PendingPairingKind;
36
+ token: string;
37
+ is_resend: boolean;
38
+ ttl_left_ms: number;
39
+ prompt_message_id?: string;
40
+ };
41
+ export interface GateResult {
42
+ action: GateAction;
43
+ nextState: DispatcherAccessStateV3;
44
+ logs: Array<{
45
+ level: 'debug' | 'info' | 'warn' | 'error';
46
+ msg: string;
47
+ ctx?: Record<string, unknown>;
48
+ }>;
49
+ }
50
+ export declare function pruneExpiredPending(state: DispatcherAccessStateV3, now: number): DispatcherAccessStateV3;
51
+ export declare function dreamuxFeishuGate(state: DispatcherAccessStateV3, input: GateInbound, now?: number): GateResult;
52
+ //# sourceMappingURL=gate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../../src/access/gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,EAIL,KAAK,uBAAuB,EAE5B,KAAK,kBAAkB,EACxB,MAAM,YAAY,CAAC;AAMpB,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C;AAED,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,MAAM,CAOR;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,KAAK,GAAG,OAAO,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,aAAa,EAAE,OAAO,CAAC;IACvB,WAAW,EAAE,OAAO,CAAC;IACrB,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,MAAM,UAAU,GAClB,aAAa,GACb,qBAAqB,GACrB,qBAAqB,GACrB,oBAAoB,GACpB,yBAAyB,GAKzB,0CAA0C,GAC1C,0CAA0C,GAC1C,wBAAwB,GAExB,wBAAwB,GACxB,6BAA6B,GAC7B,sCAAsC,GACtC,eAAe,GACf,uBAAuB,GACvB,UAAU,CAAC;AAEf,MAAM,MAAM,UAAU,GAClB;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,GACrB;IACE,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,UAAU,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC,GACD;IACE,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,kBAAkB,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEN,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,UAAU,CAAC;IACnB,SAAS,EAAE,uBAAuB,CAAC;IACnC,IAAI,EAAE,KAAK,CAAC;QACV,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;QAC3C,GAAG,EAAE,MAAM,CAAC;QACZ,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KAC/B,CAAC,CAAC;CACJ;AAQD,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,uBAAuB,EAC9B,GAAG,EAAE,MAAM,GACV,uBAAuB,CAWzB;AAmJD,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,uBAAuB,EAC9B,KAAK,EAAE,WAAW,EAClB,GAAG,GAAE,MAAmB,GACvB,UAAU,CA4IZ"}
@@ -1,55 +1,16 @@
1
1
  /**
2
- * Feishu access gate v3 — pairing-token model.
2
+ * Feishu access gate v3 — the pure decision function.
3
3
  *
4
- * Pure gate (`dreamuxFeishuGate`) decides `deliver | drop | pair` against a
5
- * snapshot of `DispatcherAccessStateV3` and returns the next state plus logs.
6
- * The session owns IO, mutex, and send-before-save for pairing prompts.
7
- *
8
- * v3 replaces the v2 "everything is allowlist" model with per-sender
9
- * pairing slots. An operator (human admin out-of-band) approves a 6-hex token
10
- * to add a sender to `allow_users`. `group.allow_chats` is the operator-owned
11
- * list of trusted human group memberships, not something pairing approval
12
- * mutates.
4
+ * `dreamuxFeishuGate` decides `deliver | drop | pair` against a snapshot of
5
+ * `DispatcherAccessStateV3` (`./state.js`) and returns the next state plus
6
+ * logs. It touches no disk and sends nothing; the session owns IO, the access
7
+ * store's serialized queue, and send-before-save for pairing prompts (see
8
+ * `./index.js`'s `FeishuAccess`).
13
9
  */
14
10
  import { randomBytes } from 'node:crypto';
11
+ import { MAX_PENDING, PAIRING_TOKEN_BYTES, PAIRING_TTL_MS, } from './state.js';
15
12
  // ─────────────────────────────────────────────────────────────────────────
16
- // Constants
17
- // ─────────────────────────────────────────────────────────────────────────
18
- export const ACCESS_STATE_VERSION = 3;
19
- // Ten active dm-kind onboarding slots keeps one dispatcher useful in a busy
20
- // chat while bounding spam from many unknown senders.
21
- export const MAX_PENDING_PER_KIND = 10;
22
- export const PAIRING_TTL_MS = 60 * 60 * 1000;
23
- export const PAIRING_TOKEN_BYTES = 3;
24
- export const PAIRING_TOKEN_REGEX = /^[0-9a-fA-F]{6}$/;
25
- const MAX_WARNINGS = 200;
26
- /**
27
- * Flag emitted into `state.warnings` when a single dispatcher observes traffic
28
- * from more than one chat — the channel multiplexes chats over one runtime
29
- * context, which is supported but worth surfacing to an operator once.
30
- */
31
- export const TRUST_DOMAIN_WARNING = 'dispatcher shares one runtime context across multiple Feishu chats';
32
- // ─────────────────────────────────────────────────────────────────────────
33
- // Defaults
34
- // ─────────────────────────────────────────────────────────────────────────
35
- export function defaultDispatcherAccessState() {
36
- return {
37
- version: ACCESS_STATE_VERSION,
38
- dm_policy: 'pairing',
39
- group: {
40
- policy: 'follow-user',
41
- allow_chats: [],
42
- require_mention: true,
43
- },
44
- allow_users: [],
45
- pending: {},
46
- observed_chats: [],
47
- warnings: [],
48
- last_gate: { at: 0 },
49
- };
50
- }
51
- // ─────────────────────────────────────────────────────────────────────────
52
- // Pairing token generators
13
+ // Pairing token generators — the token-minting half of the `pair` transition
53
14
  // ─────────────────────────────────────────────────────────────────────────
54
15
  export function generatePairingToken() {
55
16
  return randomBytes(PAIRING_TOKEN_BYTES).toString('hex');
@@ -62,7 +23,7 @@ export function generateUniquePairingToken(existing) {
62
23
  }
63
24
  return token;
64
25
  }
65
- function pruneExpiredPending(state, now) {
26
+ export function pruneExpiredPending(state, now) {
66
27
  const kept = {};
67
28
  let anyRemoved = false;
68
29
  for (const [token, entry] of Object.entries(state.pending)) {
@@ -75,54 +36,26 @@ function pruneExpiredPending(state, now) {
75
36
  }
76
37
  return anyRemoved ? { ...state, pending: kept } : state;
77
38
  }
78
- function findExistingPendingByKey(pending, kind, sender_id, chat_id, now) {
39
+ // Every live pending entry is dm-kind (the C3 semantic rewrite removed group
40
+ // pairing below), so an existing slot is found by sender alone.
41
+ function findExistingPendingByKey(pending, sender_id, now) {
79
42
  for (const [token, entry] of Object.entries(pending)) {
80
43
  if (entry.expires_at <= now)
81
44
  continue;
82
- if (entry.kind !== kind)
83
- continue;
84
- if (kind === 'dm' && entry.sender_id === sender_id) {
85
- return { token, entry };
86
- }
87
- if (kind === 'group' && entry.chat_id === chat_id) {
45
+ if (entry.sender_id === sender_id) {
88
46
  return { token, entry };
89
47
  }
90
48
  }
91
49
  return null;
92
50
  }
93
- function countByKind(pending, now) {
94
- const c = { dm: 0, group: 0 };
51
+ function countActivePending(pending, now) {
52
+ let count = 0;
95
53
  for (const entry of Object.values(pending)) {
96
54
  if (entry.expires_at <= now)
97
55
  continue;
98
- c[entry.kind] += 1;
56
+ count += 1;
99
57
  }
100
- return c;
101
- }
102
- function pushWarn(warnings, at, msg, ctx) {
103
- const next = [...warnings, { at, msg, ...(ctx ? { ctx } : {}) }];
104
- // cap at MAX_WARNINGS FIFO
105
- if (next.length > MAX_WARNINGS) {
106
- return next.slice(next.length - MAX_WARNINGS);
107
- }
108
- return next;
109
- }
110
- function pushObserved(observed, chat_id) {
111
- if (observed.includes(chat_id))
112
- return observed;
113
- return [...observed, chat_id];
114
- }
115
- function withLastGate(state, input, now, action, reason) {
116
- return {
117
- ...state,
118
- last_gate: {
119
- at: now,
120
- sender_id: input.sender_id,
121
- chat_id: input.chat_id,
122
- action,
123
- ...(reason !== undefined ? { reason } : {}),
124
- },
125
- };
58
+ return count;
126
59
  }
127
60
  // NOTE: groupPairPath was removed as part of the C3 semantic rewrite.
128
61
  // group.allow_chats is operator-maintained; there is no pairing mechanism to
@@ -130,28 +63,25 @@ function withLastGate(state, input, now, action, reason) {
130
63
  // dmPairPath and end up as dm-kind entries on allow_users.
131
64
  function dmPairPath(opts) {
132
65
  const { state, input, now, logs } = opts;
133
- const existing = findExistingPendingByKey(state.pending, 'dm', input.sender_id, input.chat_id, now);
66
+ const existing = findExistingPendingByKey(state.pending, input.sender_id, now);
134
67
  if (existing !== null) {
135
68
  const { token, entry } = existing;
136
69
  const newEntry = {
137
70
  ...entry,
138
71
  expires_at: now + PAIRING_TTL_MS,
139
72
  };
140
- let nextState = {
73
+ const nextState = {
141
74
  ...state,
142
75
  pending: { ...state.pending, [token]: newEntry },
143
76
  };
144
- nextState = withLastGate(nextState, input, now, 'pair');
145
- nextState = { ...nextState, observed_chats: pushObserved(nextState.observed_chats, input.chat_id) };
146
77
  logs.push({
147
78
  level: 'info',
148
79
  msg: 'dm pairing: existing prompt',
149
80
  ctx: {
150
- token,
81
+ pairing_token_len: token.length,
151
82
  sender_id: input.sender_id,
152
- ...(entry.prompt_message_id !== undefined
153
- ? { prompt_message_id: entry.prompt_message_id }
154
- : {}),
83
+ chat_id: input.chat_id,
84
+ prompt_message_id: entry.prompt_message_id,
155
85
  },
156
86
  });
157
87
  return {
@@ -169,50 +99,42 @@ function dmPairPath(opts) {
169
99
  logs,
170
100
  };
171
101
  }
172
- const counts = countByKind(state.pending, now);
173
- if (counts.dm >= MAX_PENDING_PER_KIND) {
174
- const msg = 'dm pairing: slot cap reached';
175
- const warnings = pushWarn(state.warnings, now, msg, {
176
- count: counts.dm,
177
- sender_id: input.sender_id,
178
- });
102
+ const count = countActivePending(state.pending, now);
103
+ if (count >= MAX_PENDING) {
179
104
  logs.push({
180
105
  level: 'warn',
181
- msg,
182
- ctx: { count: counts.dm, sender_id: input.sender_id },
106
+ msg: 'dm pairing: slot cap reached',
107
+ ctx: { count, sender_id: input.sender_id, chat_id: input.chat_id },
183
108
  });
184
- let nextState = { ...state, warnings };
185
- nextState = withLastGate(nextState, input, now, 'drop', 'dm_pairing_slot_cap');
186
- nextState = { ...nextState, observed_chats: pushObserved(nextState.observed_chats, input.chat_id) };
187
109
  return {
188
110
  action: {
189
111
  action: 'drop',
190
112
  reason: 'dm_pairing_slot_cap',
191
- context: { pending: counts.dm, max: MAX_PENDING_PER_KIND },
113
+ context: { pending: count, max: MAX_PENDING },
192
114
  },
193
- nextState,
115
+ nextState: state,
194
116
  logs,
195
117
  };
196
118
  }
197
119
  const token = generateUniquePairingToken(state.pending);
198
120
  const entry = {
199
- kind: 'dm',
200
121
  sender_id: input.sender_id,
201
122
  chat_id: input.chat_id,
202
123
  created_at: now,
203
124
  expires_at: now + PAIRING_TTL_MS,
204
- replies: 1,
205
125
  };
206
- let nextState = {
126
+ const nextState = {
207
127
  ...state,
208
128
  pending: { ...state.pending, [token]: entry },
209
129
  };
210
- nextState = withLastGate(nextState, input, now, 'pair');
211
- nextState = { ...nextState, observed_chats: pushObserved(nextState.observed_chats, input.chat_id) };
212
130
  logs.push({
213
131
  level: 'info',
214
132
  msg: 'dm pairing: new slot',
215
- ctx: { token, sender_id: input.sender_id },
133
+ ctx: {
134
+ pairing_token_len: token.length,
135
+ sender_id: input.sender_id,
136
+ chat_id: input.chat_id,
137
+ },
216
138
  });
217
139
  return {
218
140
  action: {
@@ -234,25 +156,7 @@ export function dreamuxFeishuGate(state, input, now = Date.now()) {
234
156
  // Double-guard TTL — prune first, then helpers also check expires_at > now.
235
157
  let working = pruneExpiredPending(state, now);
236
158
  const finish = (action, mutate = (s) => s) => {
237
- let s = mutate(working);
238
- s = { ...s, observed_chats: pushObserved(s.observed_chats, input.chat_id) };
239
- // Attach trust-domain warning if we observe multiple chats.
240
- if (s.observed_chats.length > 1 &&
241
- !s.warnings.some((w) => w.msg === TRUST_DOMAIN_WARNING)) {
242
- s = {
243
- ...s,
244
- warnings: pushWarn(s.warnings, now, TRUST_DOMAIN_WARNING),
245
- };
246
- logs.push({ level: 'warn', msg: TRUST_DOMAIN_WARNING });
247
- }
248
- const actionStr = action.action === 'drop'
249
- ? `drop:${action.reason}`
250
- : action.action === 'pair'
251
- ? `pair:${action.kind}`
252
- : 'deliver';
253
- const reason = action.action === 'drop' ? action.reason : undefined;
254
- s = withLastGate(s, input, now, actionStr, reason);
255
- return { action, nextState: s, logs };
159
+ return { action, nextState: mutate(working), logs };
256
160
  };
257
161
  // ── DM ────────────────────────────────────────────────────────────────
258
162
  if (input.chat_type === 'p2p') {
@@ -372,7 +276,4 @@ export function dreamuxFeishuGate(state, input, now = Date.now()) {
372
276
  // Unsupported
373
277
  return finish({ action: 'drop', reason: 'unsupported_chat_type' });
374
278
  }
375
- // IO + UI helpers moved to feishu-gate-io.ts. Re-exported to preserve the
376
- // public surface so callers (feishu-channel.ts, tests) need no change.
377
- export { loadDispatcherAccess, readDispatcherAccess, saveDispatcherAccess, } from './feishu-gate-io.js';
378
- //# sourceMappingURL=feishu-gate.js.map
279
+ //# sourceMappingURL=gate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gate.js","sourceRoot":"","sources":["../../src/access/gate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Owns the session's access state and every gate, pairing, and approval
3
+ * transition. Inbound owns message IO: it asks for a gate decision, sends the
4
+ * card or reference outside the store queue, then records successful delivery.
5
+ * Access is loaded lazily, so a malformed file fails the operation that reads it.
6
+ */
7
+ import type { DreamuxLogger } from '@excitedjs/dreamux-types';
8
+ import { type GateAction, type GateInbound } from './gate.js';
9
+ type PairingAction = Extract<GateAction, {
10
+ action: 'pair';
11
+ }>;
12
+ export interface PairingApprovalResult {
13
+ status: 'ok' | 'not_found' | 'error';
14
+ message: string;
15
+ details?: Record<string, unknown>;
16
+ }
17
+ export interface FeishuAccessOptions {
18
+ /** The dispatcher's durable state directory; `access.json` sits under it. */
19
+ stateDir: string;
20
+ dispatcherId: string;
21
+ log: DreamuxLogger;
22
+ }
23
+ export declare class FeishuAccess {
24
+ private readonly store;
25
+ private readonly dispatcherId;
26
+ private readonly log;
27
+ constructor(opts: FeishuAccessOptions);
28
+ /** Snapshot the policy used before bot observation or /introduce. */
29
+ inboundPolicy(input: {
30
+ chatType: 'p2p' | 'group';
31
+ chatId: string;
32
+ senderId: string;
33
+ }): Promise<{
34
+ observeBots: boolean;
35
+ introduceDenyReason: import("../introduce.js").IntroduceDenyReason | null;
36
+ }>;
37
+ /**
38
+ * What the gate would decide for this person, against the held state,
39
+ * committing nothing. For a caller that is not answering a message and so has
40
+ * no conversation to send a pairing card into: a `pair` outcome here mints a
41
+ * token nobody stores, and it means "not admitted" like a drop does.
42
+ */
43
+ decide(inbound: GateInbound): Promise<GateAction>;
44
+ /** Commit deliver/drop; pairing waits until its message has been sent. */
45
+ gate(inbound: GateInbound): Promise<GateAction>;
46
+ /** Refresh an existing prompt only after inbound successfully references it. */
47
+ refreshPairing(action: PairingAction): Promise<void>;
48
+ /** Merge a sent card into the latest state, including any concurrent approval. */
49
+ recordPairingPrompt(inbound: GateInbound, action: PairingAction, messageId: string | undefined): Promise<void>;
50
+ /**
51
+ * Load access.json from `stateDir`. Fails LOUDLY if the file exists but the
52
+ * shape is not v3, or a `pending` entry is missing/mistyped on `sender_id`,
53
+ * `chat_id`, `expires_at`, or `created_at`. A missing file returns the
54
+ * secure default (pairing DM default, empty allowlists).
55
+ *
56
+ * Reconstructs the returned value field by field rather than casting
57
+ * `parsed` as-is, so an old file's now-dropped ledger fields (`last_gate`,
58
+ * `observed_chats`, `warnings`) or per-entry fields (`kind`, `replies`) are
59
+ * read and discarded, never round-tripped into memory or back to disk.
60
+ */
61
+ private readFromDisk;
62
+ /**
63
+ * Approve a pending pairing by its internal 6-hex token.
64
+ *
65
+ * Behavior:
66
+ * - Normalizes `token` to lowercase (the gate stores lowercased tokens).
67
+ * - Returns `not_found` when the token is unknown or expired (we reject
68
+ * expired entries explicitly, not just "not found", so the operator
69
+ * gets the right diagnostic).
70
+ * - Adds the DM sender to `allow_users`. Idempotent on allowlist
71
+ * membership (we detect duplicates via allowlist presence, not by
72
+ * whether the pending entry still has the slot), and always removes
73
+ * the pending key after a successful approval — the
74
+ * allowlist IS the source of truth, a pending entry is just a
75
+ * temporary token.
76
+ * - Details include `duplicate` flag, `ttl_left_ms`, `sender_id`,
77
+ * `chat_id`, `kind` for audit logging.
78
+ *
79
+ * The whole decide-and-write runs inside one `store.update()`, so it is
80
+ * serialized against every other access-state change this class makes.
81
+ * `not_found` and the duplicate-but-still-consumes-the-slot case are decided
82
+ * inside the `update` and returned unchanged (no write); the decide logic
83
+ * itself cannot throw. But `update()` also self-loads the store on its
84
+ * first call (never loaded at session start), and that load can throw on a
85
+ * malformed `access.json` — the `catch` below answers that case too, as
86
+ * `status: 'error'`.
87
+ */
88
+ approvePairingByToken(token: string): Promise<PairingApprovalResult>;
89
+ /**
90
+ * Whether this sender is one of the Dispatcher's trusted humans.
91
+ *
92
+ * Reads this class's own held access store — the same store every gate
93
+ * decision and pairing write goes through — rather than a fresh disk read,
94
+ * so this always sees the value the running session actually committed
95
+ * last.
96
+ */
97
+ isTrustedDispatcherUser(openId: string): Promise<boolean>;
98
+ }
99
+ export {};
100
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/access/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAY9D,OAAO,EAGL,KAAK,UAAU,EACf,KAAK,WAAW,EAEjB,MAAM,WAAW,CAAC;AAGnB,KAAK,aAAa,GAAG,OAAO,CAAC,UAAU,EAAE;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AA6C7D,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,IAAI,GAAG,WAAW,GAAG,OAAO,CAAC;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,mBAAmB;IAClC,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,GAAG,EAAE,aAAa,CAAC;CACpB;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA4C;IAClE,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAgB;gBAExB,IAAI,EAAE,mBAAmB;IAarC,qEAAqE;IAC/D,aAAa,CAAC,KAAK,EAAE;QACzB,QAAQ,EAAE,KAAK,GAAG,OAAO,CAAC;QAC1B,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;KAClB;;;;IASD;;;;;OAKG;IACG,MAAM,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;IAKvD,0EAA0E;IACpE,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;IAgBrD,gFAAgF;IAC1E,cAAc,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAmB1D,kFAAkF;IAC5E,mBAAmB,CACvB,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,aAAa,EACrB,SAAS,EAAE,MAAM,GAAG,SAAS,GAC5B,OAAO,CAAC,IAAI,CAAC;IAoDhB;;;;;;;;;;OAUG;YACW,YAAY;IAwC1B;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACG,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAoF1E;;;;;;;OAOG;IACG,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAIhE"}