@fastagent-sh/fastagent 0.21.1 → 0.22.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 (388) hide show
  1. package/README.md +9 -12
  2. package/dist/agent.d.ts +24 -37
  3. package/dist/agent.js +7 -15
  4. package/dist/atomic-write.d.ts +13 -17
  5. package/dist/atomic-write.js +18 -29
  6. package/dist/bind.d.ts +12 -22
  7. package/dist/bind.js +18 -27
  8. package/dist/channel.d.ts +11 -33
  9. package/dist/channels/agentcore-limits.d.ts +2 -13
  10. package/dist/channels/agentcore-limits.js +2 -13
  11. package/dist/channels/agentcore-protocol.d.ts +67 -58
  12. package/dist/channels/agentcore-protocol.js +23 -15
  13. package/dist/channels/agentcore-service.d.ts +19 -33
  14. package/dist/channels/agentcore-service.js +176 -59
  15. package/dist/channels/agentcore.d.ts +39 -33
  16. package/dist/channels/agentcore.js +90 -224
  17. package/dist/channels/body.d.ts +15 -2
  18. package/dist/channels/body.js +24 -2
  19. package/dist/channels/busy.d.ts +1 -21
  20. package/dist/channels/busy.js +2 -39
  21. package/dist/channels/control.d.ts +17 -62
  22. package/dist/channels/control.js +74 -182
  23. package/dist/channels/define-channel.d.ts +39 -0
  24. package/dist/channels/define-channel.js +9 -0
  25. package/dist/channels/discover.d.ts +10 -31
  26. package/dist/channels/discover.js +37 -31
  27. package/dist/channels/feishu/bootstrap-token.d.ts +1 -8
  28. package/dist/channels/feishu/bootstrap-token.js +7 -23
  29. package/dist/channels/feishu/card.d.ts +14 -28
  30. package/dist/channels/feishu/card.js +16 -30
  31. package/dist/channels/feishu/cloud.d.ts +1 -5
  32. package/dist/channels/feishu/context-buffer.d.ts +4 -23
  33. package/dist/channels/feishu/context-buffer.js +8 -31
  34. package/dist/channels/feishu/crypto.d.ts +10 -5
  35. package/dist/channels/feishu/crypto.js +12 -19
  36. package/dist/channels/feishu/feishu-api.d.ts +2 -1
  37. package/dist/channels/feishu/feishu-api.js +5 -4
  38. package/dist/channels/feishu/feishu.d.ts +4 -10
  39. package/dist/channels/feishu/feishu.js +113 -227
  40. package/dist/channels/feishu/invoke-turn.d.ts +5 -4
  41. package/dist/channels/feishu/invoke-turn.js +36 -54
  42. package/dist/channels/feishu/model.d.ts +4 -21
  43. package/dist/channels/feishu/model.js +1 -8
  44. package/dist/channels/feishu/normalize.d.ts +2 -10
  45. package/dist/channels/feishu/normalize.js +7 -42
  46. package/dist/channels/feishu/parse.d.ts +9 -29
  47. package/dist/channels/feishu/parse.js +8 -26
  48. package/dist/channels/feishu/preview.d.ts +12 -14
  49. package/dist/channels/feishu/preview.js +108 -207
  50. package/dist/channels/feishu/register-app.d.ts +8 -23
  51. package/dist/channels/feishu/register-app.js +7 -20
  52. package/dist/channels/feishu/register-webhook.d.ts +2 -9
  53. package/dist/channels/feishu/register-webhook.js +9 -33
  54. package/dist/channels/feishu/scaffold/channel.ts +27 -19
  55. package/dist/channels/feishu/scaffold/channel.websocket.ts +31 -0
  56. package/dist/channels/feishu/setup-mode.d.ts +11 -36
  57. package/dist/channels/feishu/setup-mode.js +7 -26
  58. package/dist/channels/feishu/ws-ingress.d.ts +1 -2
  59. package/dist/channels/feishu/ws-ingress.js +6 -8
  60. package/dist/channels/http.d.ts +15 -15
  61. package/dist/channels/http.js +19 -9
  62. package/dist/channels/kit/attachment-path.d.ts +4 -5
  63. package/dist/channels/kit/attachment-path.js +10 -34
  64. package/dist/channels/kit/context-buffer.d.ts +14 -21
  65. package/dist/channels/kit/context-buffer.js +25 -38
  66. package/dist/channels/kit/delivery.d.ts +35 -0
  67. package/dist/channels/kit/delivery.js +129 -0
  68. package/dist/channels/kit/event-stream.d.ts +5 -0
  69. package/dist/channels/kit/event-stream.js +29 -0
  70. package/dist/channels/kit/invoke-turn-kit.d.ts +53 -26
  71. package/dist/channels/kit/invoke-turn-kit.js +89 -32
  72. package/dist/channels/kit/preview-kit.d.ts +18 -72
  73. package/dist/channels/kit/preview-kit.js +22 -88
  74. package/dist/channels/kit/signature.d.ts +3 -9
  75. package/dist/channels/kit/signature.js +3 -9
  76. package/dist/channels/kit/state.d.ts +1 -2
  77. package/dist/channels/kit/state.js +5 -14
  78. package/dist/channels/kit/stop-command.d.ts +1 -2
  79. package/dist/channels/kit/stop-command.js +6 -9
  80. package/dist/channels/kit/tasks.d.ts +1 -1
  81. package/dist/channels/kit/tasks.js +8 -21
  82. package/dist/channels/kit/text.d.ts +2 -16
  83. package/dist/channels/kit/text.js +23 -31
  84. package/dist/channels/kit/thread-participants.d.ts +4 -17
  85. package/dist/channels/kit/thread-participants.js +8 -55
  86. package/dist/channels/kit/transport.d.ts +26 -0
  87. package/dist/channels/kit/transport.js +20 -0
  88. package/dist/channels/kit/turn-queue.d.ts +7 -11
  89. package/dist/channels/kit/turn-queue.js +21 -39
  90. package/dist/channels/kit/turn-runner.d.ts +32 -18
  91. package/dist/channels/kit/turn-runner.js +98 -53
  92. package/dist/channels/kit/turn-store.d.ts +32 -32
  93. package/dist/channels/kit/turn-store.js +45 -88
  94. package/dist/channels/lark/lark.d.ts +1 -5
  95. package/dist/channels/lark/onboard.d.ts +3 -12
  96. package/dist/channels/lark/onboard.js +3 -13
  97. package/dist/channels/lark/scaffold/channel.ts +27 -19
  98. package/dist/channels/lark/scaffold/channel.websocket.ts +31 -0
  99. package/dist/channels/registration.d.ts +9 -35
  100. package/dist/channels/registration.js +6 -37
  101. package/dist/channels/secret.js +2 -5
  102. package/dist/channels/serve.d.ts +47 -44
  103. package/dist/channels/serve.js +165 -101
  104. package/dist/channels/slack/config-api.d.ts +4 -27
  105. package/dist/channels/slack/config-api.js +5 -34
  106. package/dist/channels/slack/context-buffer.d.ts +2 -3
  107. package/dist/channels/slack/context-buffer.js +5 -5
  108. package/dist/channels/slack/invoke-turn.d.ts +3 -1
  109. package/dist/channels/slack/invoke-turn.js +25 -34
  110. package/dist/channels/slack/manifest.js +1 -3
  111. package/dist/channels/slack/model.d.ts +1 -2
  112. package/dist/channels/slack/model.js +1 -1
  113. package/dist/channels/slack/onboard.d.ts +4 -3
  114. package/dist/channels/slack/onboard.js +8 -11
  115. package/dist/channels/slack/onboarding-state.d.ts +3 -5
  116. package/dist/channels/slack/onboarding-state.js +9 -6
  117. package/dist/channels/slack/parse.d.ts +4 -13
  118. package/dist/channels/slack/parse.js +7 -28
  119. package/dist/channels/slack/preview.d.ts +12 -2
  120. package/dist/channels/slack/preview.js +292 -269
  121. package/dist/channels/slack/reaction.d.ts +11 -3
  122. package/dist/channels/slack/reaction.js +19 -8
  123. package/dist/channels/slack/register-webhook.js +4 -7
  124. package/dist/channels/slack/scaffold/channel.ts +20 -13
  125. package/dist/channels/slack/scaffold/slack-send.ts +2 -2
  126. package/dist/channels/slack/setup-server.d.ts +1 -5
  127. package/dist/channels/slack/setup-server.js +1 -5
  128. package/dist/channels/slack/shared-api.d.ts +1 -4
  129. package/dist/channels/slack/shared-api.js +2 -13
  130. package/dist/channels/slack/slack-api.d.ts +2 -1
  131. package/dist/channels/slack/slack-api.js +6 -4
  132. package/dist/channels/slack/slack.d.ts +11 -14
  133. package/dist/channels/slack/slack.js +121 -122
  134. package/dist/channels/sse.d.ts +1 -1
  135. package/dist/channels/sse.js +1 -1
  136. package/dist/channels/telegram/context-buffer.d.ts +7 -22
  137. package/dist/channels/telegram/context-buffer.js +6 -23
  138. package/dist/channels/telegram/invoke-turn.d.ts +7 -7
  139. package/dist/channels/telegram/invoke-turn.js +24 -43
  140. package/dist/channels/telegram/parse.d.ts +24 -51
  141. package/dist/channels/telegram/parse.js +32 -84
  142. package/dist/channels/telegram/preview.d.ts +10 -1
  143. package/dist/channels/telegram/preview.js +79 -100
  144. package/dist/channels/telegram/register-webhook.js +5 -23
  145. package/dist/channels/telegram/scaffold/channel.ts +25 -18
  146. package/dist/channels/telegram/scaffold/telegram-send.ts +6 -4
  147. package/dist/channels/telegram/telegram-api.d.ts +4 -3
  148. package/dist/channels/telegram/telegram-api.js +9 -7
  149. package/dist/channels/telegram/telegram.d.ts +4 -18
  150. package/dist/channels/telegram/telegram.js +38 -94
  151. package/dist/channels/telegram/turn-store.d.ts +11 -14
  152. package/dist/channels/telegram/turn-store.js +8 -6
  153. package/dist/channels/wait-health.d.ts +4 -6
  154. package/dist/channels/wait-health.js +10 -14
  155. package/dist/cli/add-feishu.d.ts +2 -12
  156. package/dist/cli/add-feishu.js +24 -71
  157. package/dist/cli/add-slack.d.ts +5 -3
  158. package/dist/cli/add-slack.js +53 -76
  159. package/dist/cli/auth-view.d.ts +2 -16
  160. package/dist/cli/auth-view.js +2 -16
  161. package/dist/cli/commands/add.js +25 -40
  162. package/dist/cli/commands/chat.d.ts +0 -1
  163. package/dist/cli/commands/chat.js +5 -6
  164. package/dist/cli/commands/deploy/agentcore.js +60 -65
  165. package/dist/cli/commands/deploy/docker.js +50 -23
  166. package/dist/cli/commands/deploy/fly.js +23 -61
  167. package/dist/cli/commands/deploy/railway.js +23 -18
  168. package/dist/cli/commands/deploy/shared.d.ts +38 -57
  169. package/dist/cli/commands/deploy/shared.js +40 -59
  170. package/dist/cli/commands/deploy.d.ts +10 -27
  171. package/dist/cli/commands/deploy.js +38 -60
  172. package/dist/cli/commands/destroy.d.ts +4 -0
  173. package/dist/cli/commands/destroy.js +56 -0
  174. package/dist/cli/commands/dev.d.ts +2 -1
  175. package/dist/cli/commands/dev.js +20 -23
  176. package/dist/cli/commands/info.d.ts +0 -2
  177. package/dist/cli/commands/info.js +83 -49
  178. package/dist/cli/commands/init.d.ts +1 -2
  179. package/dist/cli/commands/init.js +16 -55
  180. package/dist/cli/commands/invoke.d.ts +0 -1
  181. package/dist/cli/commands/invoke.js +5 -7
  182. package/dist/cli/commands/login.d.ts +2 -1
  183. package/dist/cli/commands/login.js +33 -38
  184. package/dist/cli/commands/logs.js +3 -4
  185. package/dist/cli/commands/models.js +1 -5
  186. package/dist/cli/commands/routine-run.d.ts +6 -0
  187. package/dist/cli/commands/routine-run.js +47 -0
  188. package/dist/cli/commands/routine.d.ts +25 -0
  189. package/dist/cli/commands/routine.js +122 -0
  190. package/dist/cli/commands/start.d.ts +31 -3
  191. package/dist/cli/commands/start.js +131 -81
  192. package/dist/cli/commands/tool.js +26 -12
  193. package/dist/cli/fail.d.ts +13 -23
  194. package/dist/cli/fail.js +20 -23
  195. package/dist/cli/invoke-stream.d.ts +1 -8
  196. package/dist/cli/kernel.d.ts +6 -38
  197. package/dist/cli/kernel.js +14 -77
  198. package/dist/cli/models-view.d.ts +1 -9
  199. package/dist/cli/models-view.js +11 -22
  200. package/dist/cli/program.d.ts +5 -6
  201. package/dist/cli/program.js +120 -157
  202. package/dist/cli/serve.d.ts +57 -41
  203. package/dist/cli/serve.js +94 -121
  204. package/dist/cli/shared.d.ts +15 -61
  205. package/dist/cli/shared.js +55 -181
  206. package/dist/cli.js +1 -8
  207. package/dist/collect.d.ts +6 -0
  208. package/dist/collect.js +6 -0
  209. package/dist/core.d.ts +4 -1
  210. package/dist/core.js +9 -7
  211. package/dist/declared-secrets.d.ts +55 -0
  212. package/dist/declared-secrets.js +88 -0
  213. package/dist/deploy/agentcore/aws-cli.d.ts +91 -0
  214. package/dist/deploy/agentcore/aws-cli.js +64 -0
  215. package/dist/deploy/agentcore/destroy.d.ts +51 -0
  216. package/dist/deploy/agentcore/destroy.js +273 -0
  217. package/dist/deploy/agentcore/forwarder.js +28 -79
  218. package/dist/deploy/agentcore/logs.d.ts +2 -18
  219. package/dist/deploy/agentcore/logs.js +27 -51
  220. package/dist/deploy/agentcore/plan.d.ts +85 -105
  221. package/dist/deploy/agentcore/plan.js +152 -314
  222. package/dist/deploy/agentcore/run.d.ts +28 -28
  223. package/dist/deploy/agentcore/run.js +227 -306
  224. package/dist/deploy/agentcore/zip.d.ts +2 -15
  225. package/dist/deploy/agentcore/zip.js +3 -17
  226. package/dist/deploy/build-context.d.ts +21 -0
  227. package/dist/deploy/build-context.js +137 -0
  228. package/dist/deploy/channel-ingress.d.ts +26 -39
  229. package/dist/deploy/channel-ingress.js +57 -28
  230. package/dist/deploy/container.d.ts +20 -34
  231. package/dist/deploy/container.js +49 -100
  232. package/dist/deploy/docker/plan.d.ts +12 -11
  233. package/dist/deploy/docker/plan.js +43 -24
  234. package/dist/deploy/docker/run.d.ts +22 -25
  235. package/dist/deploy/docker/run.js +39 -37
  236. package/dist/deploy/fly/plan.d.ts +15 -44
  237. package/dist/deploy/fly/plan.js +41 -73
  238. package/dist/deploy/fly/run.d.ts +16 -51
  239. package/dist/deploy/fly/run.js +42 -95
  240. package/dist/deploy/hosts.d.ts +24 -3
  241. package/dist/deploy/hosts.js +24 -3
  242. package/dist/deploy/preflight.d.ts +47 -41
  243. package/dist/deploy/preflight.js +282 -383
  244. package/dist/deploy/railway/plan.d.ts +16 -42
  245. package/dist/deploy/railway/plan.js +42 -60
  246. package/dist/deploy/railway/run.d.ts +30 -68
  247. package/dist/deploy/railway/run.js +86 -100
  248. package/dist/deploy/registration-gate.d.ts +6 -13
  249. package/dist/deploy/residency.d.ts +37 -0
  250. package/dist/deploy/residency.js +24 -0
  251. package/dist/deploy/runner.d.ts +10 -9
  252. package/dist/deploy/runner.js +7 -18
  253. package/dist/deploy/secrets.d.ts +40 -41
  254. package/dist/deploy/secrets.js +117 -103
  255. package/dist/deploy/workspace.d.ts +50 -0
  256. package/dist/deploy/workspace.js +197 -0
  257. package/dist/dev-supervisor.d.ts +1 -9
  258. package/dist/dev-supervisor.js +21 -64
  259. package/dist/effect-port.d.ts +50 -0
  260. package/dist/effect-port.js +77 -0
  261. package/dist/engines/pi/agent-session-factory.d.ts +50 -69
  262. package/dist/engines/pi/agent-session-factory.js +249 -146
  263. package/dist/engines/pi/auth.d.ts +15 -11
  264. package/dist/engines/pi/auth.js +96 -99
  265. package/dist/engines/pi/chat.d.ts +1 -5
  266. package/dist/engines/pi/chat.js +2 -14
  267. package/dist/engines/pi/config.d.ts +53 -51
  268. package/dist/engines/pi/config.js +83 -92
  269. package/dist/engines/pi/create.d.ts +60 -75
  270. package/dist/engines/pi/create.js +108 -141
  271. package/dist/engines/pi/definition.d.ts +14 -58
  272. package/dist/engines/pi/definition.js +21 -85
  273. package/dist/engines/pi/invoke-session.d.ts +5 -25
  274. package/dist/engines/pi/invoke-session.js +233 -244
  275. package/dist/engines/pi/login.d.ts +95 -35
  276. package/dist/engines/pi/login.js +259 -107
  277. package/dist/engines/pi/machine.d.ts +31 -0
  278. package/dist/engines/pi/machine.js +106 -0
  279. package/dist/engines/pi/models.d.ts +70 -89
  280. package/dist/engines/pi/models.js +188 -81
  281. package/dist/engines/pi/open.d.ts +65 -53
  282. package/dist/engines/pi/open.js +129 -69
  283. package/dist/engines/pi/report.d.ts +3 -12
  284. package/dist/engines/pi/report.js +2 -16
  285. package/dist/engines/pi/search-tools.d.ts +1 -9
  286. package/dist/engines/pi/search-tools.js +26 -57
  287. package/dist/engines/pi/service.d.ts +2 -16
  288. package/dist/engines/pi/service.js +2 -16
  289. package/dist/engines/pi/session-builder.d.ts +5 -9
  290. package/dist/engines/pi/session-builder.js +44 -82
  291. package/dist/engines/pi/session-control.js +286 -269
  292. package/dist/engines/pi/session-effects.d.ts +14 -0
  293. package/dist/engines/pi/session-effects.js +30 -0
  294. package/dist/engines/pi/session-inheritance.d.ts +9 -18
  295. package/dist/engines/pi/session-inheritance.js +103 -160
  296. package/dist/engines/pi/session-markers.d.ts +31 -30
  297. package/dist/engines/pi/session-markers.js +26 -25
  298. package/dist/engines/pi/session-settings.d.ts +6 -22
  299. package/dist/engines/pi/session-settings.js +10 -21
  300. package/dist/engines/pi/session-store.d.ts +26 -92
  301. package/dist/engines/pi/session-store.js +96 -310
  302. package/dist/engines/pi/tool-context.d.ts +17 -45
  303. package/dist/engines/pi/tool-context.js +7 -26
  304. package/dist/engines/pi/tool.d.ts +45 -36
  305. package/dist/engines/pi/tool.js +43 -57
  306. package/dist/engines/pi/turn-kit.d.ts +14 -52
  307. package/dist/engines/pi/turn-kit.js +18 -67
  308. package/dist/engines/pi/wake-tool.d.ts +2 -10
  309. package/dist/engines/pi/wake-tool.js +7 -27
  310. package/dist/env.d.ts +27 -28
  311. package/dist/env.js +45 -50
  312. package/dist/index.js +0 -2
  313. package/dist/lark.d.ts +1 -2
  314. package/dist/lark.js +1 -2
  315. package/dist/loader.d.ts +14 -55
  316. package/dist/loader.js +24 -43
  317. package/dist/log.d.ts +2 -12
  318. package/dist/log.js +3 -24
  319. package/dist/node.d.ts +2 -15
  320. package/dist/node.js +2 -18
  321. package/dist/observe.d.ts +2 -8
  322. package/dist/open-url.d.ts +1 -1
  323. package/dist/open-url.js +1 -1
  324. package/dist/paths.d.ts +70 -134
  325. package/dist/paths.js +165 -232
  326. package/dist/pi.d.ts +4 -6
  327. package/dist/pi.js +3 -3
  328. package/dist/proxy.d.ts +16 -6
  329. package/dist/proxy.js +52 -16
  330. package/dist/runtime.d.ts +18 -12
  331. package/dist/runtime.js +18 -11
  332. package/dist/scaffold/add-channel.d.ts +14 -31
  333. package/dist/scaffold/add-channel.js +48 -112
  334. package/dist/scaffold/init.d.ts +9 -31
  335. package/dist/scaffold/init.js +45 -138
  336. package/dist/scaffold/templates/env.example +6 -4
  337. package/dist/scaffold/templates/fastagent.config.ts +36 -0
  338. package/dist/scaffold/templates.d.ts +3 -3
  339. package/dist/scaffold/templates.js +5 -8
  340. package/dist/scaffold/vendor-skill.d.ts +2 -5
  341. package/dist/scaffold/vendor-skill.js +19 -29
  342. package/dist/schedule/cron.d.ts +9 -5
  343. package/dist/schedule/cron.js +9 -11
  344. package/dist/schedule/discover.d.ts +12 -12
  345. package/dist/schedule/discover.js +43 -34
  346. package/dist/schedule/routine.d.ts +43 -0
  347. package/dist/schedule/routine.js +32 -0
  348. package/dist/schedule/run.d.ts +55 -0
  349. package/dist/schedule/run.js +146 -0
  350. package/dist/schedule/scheduler.d.ts +44 -55
  351. package/dist/schedule/scheduler.js +291 -265
  352. package/dist/schedule/state.d.ts +105 -10
  353. package/dist/schedule/state.js +293 -19
  354. package/dist/schedule/wake-alarm.d.ts +10 -23
  355. package/dist/schedule/wake-alarm.js +91 -121
  356. package/dist/schedule/wakeups.d.ts +23 -39
  357. package/dist/schedule/wakeups.js +33 -75
  358. package/dist/secrets-gate.d.ts +32 -0
  359. package/dist/secrets-gate.js +33 -0
  360. package/dist/service.d.ts +137 -98
  361. package/dist/service.js +250 -252
  362. package/dist/session-remote.d.ts +33 -44
  363. package/dist/session-remote.js +203 -192
  364. package/dist/session.d.ts +166 -229
  365. package/dist/session.js +33 -55
  366. package/dist/tunnel.d.ts +8 -36
  367. package/dist/tunnel.js +24 -65
  368. package/dist/version.d.ts +2 -2
  369. package/dist/version.js +2 -2
  370. package/package.json +10 -16
  371. package/dist/channels/agentcore-state.d.ts +0 -77
  372. package/dist/channels/agentcore-state.js +0 -276
  373. package/dist/channels/github/github.d.ts +0 -31
  374. package/dist/channels/github/github.js +0 -89
  375. package/dist/channels/github/scaffold/channel.ts +0 -25
  376. package/dist/cli/commands/attach.d.ts +0 -108
  377. package/dist/cli/commands/attach.js +0 -637
  378. package/dist/cli/commands/fire.d.ts +0 -7
  379. package/dist/cli/commands/fire.js +0 -38
  380. package/dist/cli/commands/schedule.d.ts +0 -12
  381. package/dist/cli/commands/schedule.js +0 -87
  382. package/dist/github.d.ts +0 -3
  383. package/dist/github.js +0 -2
  384. package/dist/scaffold/templates/fastagent.config.mjs +0 -23
  385. package/dist/schedule/audit.d.ts +0 -18
  386. package/dist/schedule/audit.js +0 -57
  387. package/dist/schedule/schedule.d.ts +0 -35
  388. package/dist/schedule/schedule.js +0 -23
@@ -1,54 +1,33 @@
1
1
  import type { ChannelContext, LongConnection, Routes } from "../channel.ts";
2
2
  import { type ModuleLoadFailure } from "../loader.ts";
3
- /** A dropped route: two channels claim the same key. Surfaced, never silent. */
3
+ import { type DeclaredSecret } from "../declared-secrets.ts";
4
+ /** A dropped route: two channels claim the same key. */
4
5
  export interface ChannelCollision {
5
6
  route: string;
6
7
  source: string;
7
8
  }
8
- /** A long-connection module bound to the same context route factories receive. Internal serving shape. */
9
+ /** A long-connection module bound to the same context route factories receive. */
9
10
  export interface LoadedLongConnectionChannel {
10
11
  name: string;
11
12
  connect(signal: AbortSignal): LongConnection;
12
13
  }
13
- /**
14
- * HOW A CHANNEL IS REACHED — the authored structural fact, and the ONE shape it travels in.
15
- *
16
- * A webhook channel is reached at a URL someone must set; a long-connection channel dials out, so
17
- * there is no URL and setting one breaks it (Telegram answers `getUpdates` with 409 once a webhook
18
- * exists). Everything downstream — which secrets to carry, what the runbook says, what `--run` and
19
- * `--tunnel` register — is a question about THIS.
20
- *
21
- * It is one list because it used to be three (`channels` + `routeChannels` + `longConnectionChannels`,
22
- * two of them including custom channels and one not), and every consumer re-derived the answer from
23
- * whichever pair it happened to hold. Two deploys shipped a webhook for a long-connection channel
24
- * that way. A list of pairs cannot be recombined wrongly, and a consumer that needs a subset asks for
25
- * it here rather than trusting its caller to have filtered.
26
- */
14
+ /** HOW A CHANNEL IS REACHED — the authored structural fact, and the ONE shape it travels in. */
27
15
  export type ChannelIngress = "webhook" | "long-connection";
28
- /** One channel a directory declares, with the ingress its module shape says it has. `name` is the
29
- * basename, which is a {@link ChannelKind} for the first-party ones and anything for a custom one. */
16
+ /** One channel a directory declares, with the ingress its module shape says it has. */
30
17
  export interface DeclaredChannel {
31
18
  name: string;
32
19
  ingress: ChannelIngress;
33
20
  }
34
- /** Declared channels from basenames that share one ingress: the serving surface's mounted route list
35
- * (a long-connection channel mounts no HTTP route, so every route IS a webhook channel), and fixtures. */
21
+ /** Declared channels from basenames that share one ingress. */
36
22
  export declare function declaredChannels(names: readonly string[], ingress?: ChannelIngress): DeclaredChannel[];
37
- /**
38
- * Import channel files without mounting route factories or opening connections. Deployment needs only
39
- * the authored structural fact: function exports are webhook channels; `{ connect() }` exports are
40
- * long-connection channels. There is no second ingress/lifecycle declaration to keep in sync.
41
- */
23
+ /** Import channel files without mounting route factories or opening connections. */
42
24
  export declare function inspectChannels(dir: string): Promise<{
43
25
  channels: DeclaredChannel[];
26
+ /** What each channel declared through {@link defineChannel}, BY CHANNEL NAME — the only way a
27
+ * CUSTOM channel's credentials can reach a deploy, since nothing else here can name them. */
28
+ secrets: Map<string, DeclaredSecret[]>;
44
29
  failures: ModuleLoadFailure[];
45
30
  }>;
46
- /**
47
- * Channel file basenames under `<dir>/channels/` — the authoring view (`fastagent info`), which lists
48
- * WITHOUT importing. A symlinked channels directory must remain inside the agent dir.
49
- */
50
- export declare function discoverChannelFiles(dir: string): Promise<string[]>;
51
- /** Discover, validate, and bind all channel modules. No long connection is opened here; the CLI owns it. */
52
31
  export declare function loadChannels(dir: string, ctx: ChannelContext): Promise<{
53
32
  routes: Routes;
54
33
  longConnections: LoadedLongConnectionChannel[];
@@ -1,13 +1,9 @@
1
- /**
2
- * Channel discovery (the N axis, filesystem form). A channel file default-exports either the existing
3
- * route factory `(ctx) => Routes`, or an explicit long-connection module `{ name, connect(ctx, signal) }`.
4
- *
5
- * Engine-neutral, and living here rather than under `engines/` because of it: reading `channels/*.ts`
6
- * is the Channel contract plus a directory, with no engine in sight.
7
- */
1
+ /** Channel discovery (the N axis, filesystem form). */
8
2
  import { isAbsolute, join } from "node:path";
9
3
  import { assertRouteKey, routeKeysConflict } from "./serve.js";
10
- import { loadModuleDir, moduleInventory } from "../loader.js";
4
+ import { loadModuleDir } from "../loader.js";
5
+ import { readSecretDeclaration } from "../declared-secrets.js";
6
+ import { gateSecrets } from "../secrets-gate.js";
11
7
  import { assertInsideAgentDir } from "../paths.js";
12
8
  function longConnectionModule(value) {
13
9
  return value !== null && typeof value === "object" && typeof value.connect === "function";
@@ -17,47 +13,38 @@ function validateLongConnectionModule(value, label) {
17
13
  throw new Error(`${label}: long-connection channel name must be a non-empty string`);
18
14
  }
19
15
  }
20
- /** Declared channels from basenames that share one ingress: the serving surface's mounted route list
21
- * (a long-connection channel mounts no HTTP route, so every route IS a webhook channel), and fixtures. */
16
+ /** Declared channels from basenames that share one ingress. */
22
17
  export function declaredChannels(names, ingress = "webhook") {
23
18
  return names.map((name) => ({ name, ingress }));
24
19
  }
25
- /**
26
- * Import channel files without mounting route factories or opening connections. Deployment needs only
27
- * the authored structural fact: function exports are webhook channels; `{ connect() }` exports are
28
- * long-connection channels. There is no second ingress/lifecycle declaration to keep in sync.
29
- */
20
+ /** Import channel files without mounting route factories or opening connections. */
30
21
  export async function inspectChannels(dir) {
31
22
  await assertInsideAgentDir(dir, "channels");
32
23
  const { modules, failures } = await loadModuleDir(join(dir, "channels"));
33
24
  const channels = [];
25
+ const secrets = new Map();
34
26
  for (const { name, label, file, mod } of modules) {
35
27
  try {
28
+ const declaration = readSecretDeclaration(mod.default, label);
29
+ if (declaration.error !== undefined)
30
+ throw new Error(declaration.error);
36
31
  if (typeof mod.default === "function") {
37
32
  channels.push({ name, ingress: "webhook" });
38
- continue;
39
33
  }
40
- if (longConnectionModule(mod.default)) {
34
+ else if (longConnectionModule(mod.default)) {
41
35
  validateLongConnectionModule(mod.default, label);
42
36
  channels.push({ name, ingress: "long-connection" });
43
- continue;
44
37
  }
45
- throw new Error(`${label} must default-export (ctx) => Routes or { name, connect(ctx, signal) }`);
38
+ else {
39
+ throw new Error(`${label} must default-export (ctx) => Routes or { name, connect(ctx, signal) }`);
40
+ }
41
+ secrets.set(name, declaration.secrets);
46
42
  }
47
43
  catch (error) {
48
44
  failures.push({ label, file, message: error.message });
49
45
  }
50
46
  }
51
- return { channels, failures };
52
- }
53
- /**
54
- * Channel file basenames under `<dir>/channels/` — the authoring view (`fastagent info`), which lists
55
- * WITHOUT importing. A symlinked channels directory must remain inside the agent dir.
56
- */
57
- export async function discoverChannelFiles(dir) {
58
- await assertInsideAgentDir(dir, "channels");
59
- const entries = await moduleInventory(join(dir, "channels"));
60
- return entries.map((entry) => entry.name);
47
+ return { channels, secrets, failures };
61
48
  }
62
49
  function validateRoutes(value, label) {
63
50
  if (value === null || typeof value !== "object" || value instanceof Map) {
@@ -75,7 +62,6 @@ function validateRoutes(value, label) {
75
62
  }
76
63
  return routes;
77
64
  }
78
- /** Discover, validate, and bind all channel modules. No long connection is opened here; the CLI owns it. */
79
65
  export async function loadChannels(dir, ctx) {
80
66
  if (!isAbsolute(ctx.stateRoot)) {
81
67
  throw new Error(`ChannelContext.stateRoot must be absolute, got "${ctx.stateRoot}"`);
@@ -86,7 +72,21 @@ export async function loadChannels(dir, ctx) {
86
72
  const longConnections = [];
87
73
  const routeChannels = [];
88
74
  const collisions = [];
89
- for (const { name, label, file, mod } of modules) {
75
+ // Collected from the channels that actually MOUNTED, GATED at the end of the function: this is a
76
+ // serving path, so an unset declared value must stop it (a channel built from an empty credential
77
+ // is the failure the declaration exists to prevent — a webhook that accepts forged updates, a bot
78
+ // that cannot reply), while `inspectChannels` — the reporting reader — only collects. A channel
79
+ // that failed to mount does not gate: its own failure is the report the author needs.
80
+ const declaredSecrets = new Map();
81
+ const declaring = modules.flatMap((entry) => {
82
+ const declaration = readSecretDeclaration(entry.mod.default, entry.label);
83
+ if (declaration.error !== undefined) {
84
+ failures.push({ label: entry.label, file: entry.file, message: declaration.error });
85
+ return [];
86
+ }
87
+ return [{ ...entry, secrets: declaration.secrets }];
88
+ });
89
+ for (const { name, label, file, mod, secrets } of declaring) {
90
90
  try {
91
91
  if (longConnectionModule(mod.default)) {
92
92
  validateLongConnectionModule(mod.default, label);
@@ -95,6 +95,7 @@ export async function loadChannels(dir, ctx) {
95
95
  name: channel.name,
96
96
  connect: (signal) => channel.connect(ctx, signal),
97
97
  });
98
+ declaredSecrets.set(name, secrets);
98
99
  continue;
99
100
  }
100
101
  if (typeof mod.default !== "function") {
@@ -117,10 +118,15 @@ export async function loadChannels(dir, ctx) {
117
118
  routes[route] = handler;
118
119
  }
119
120
  routeChannels.push(name);
121
+ declaredSecrets.set(name, secrets);
120
122
  }
121
123
  catch (error) {
122
124
  failures.push({ label, file, message: error.message });
123
125
  }
124
126
  }
127
+ // Gated LAST, after the loop has said what it found (the gate reports those failures before it
128
+ // refuses — src/secrets-gate.ts, decision 2). Binding a module ahead of the gate costs nothing: a
129
+ // route factory builds handlers; nothing listens or dials until the caller mounts what this returns.
130
+ gateSecrets({ declared: declaredSecrets, failures });
125
131
  return { routes, longConnections, routeChannels, collisions, failures };
126
132
  }
@@ -18,11 +18,9 @@ export interface FeishuBootstrapTokenOptions {
18
18
  } | undefined>;
19
19
  /** Budget for the whole capture (the challenge normally lands within the PATCH round-trip). */
20
20
  timeoutMs?: number;
21
- /** PATCH attempts × delay — the PATCH is the one and only readiness probe (see below). */
21
+ /** PATCH attempts × delay — the PATCH is the one and only readiness probe. */
22
22
  patchAttempts?: number;
23
23
  patchRetryMs?: number;
24
- /** Retry classifier. Default: retry every PATCH failure (Feishu edge warm-up compatibility).
25
- * Lark onboarding rejects a definitive config-route 404 immediately so it can fall back by hand. */
26
24
  shouldRetryPatch?: (error: unknown) => boolean;
27
25
  /** Progress hooks: production prints the assigned URL/retries; tests keep transport IO injected. */
28
26
  onTunnelReady?: (url: string) => void;
@@ -33,10 +31,5 @@ export interface FeishuBootstrapTokenOptions {
33
31
  retryMs: number;
34
32
  }) => void;
35
33
  }
36
- /**
37
- * Run the bootstrap (module header). Resolves with the app's Verification Token; rejects with a
38
- * plain, actionable Error (tunnel unavailable, PATCH refused, challenge never arrived) — the caller
39
- * degrades to the manual console instruction.
40
- */
41
34
  export declare function bootstrapFeishuVerificationToken(options: FeishuBootstrapTokenOptions): Promise<string>;
42
35
  export {};
@@ -1,30 +1,15 @@
1
1
  /**
2
- * Verification-Token bootstrap — the token has NO read API (field-tested 2026-07: the v6 app detail
3
- * never returns `encryption`, and the v7 config route only supports PATCH). The platform's only
4
- * programmatic delivery is the `url_verification` challenge it POSTs to the request URL while an
5
- * event-subscription PATCH is verified — the challenge body carries `token`, the app's Verification
6
- * Token. So `add feishu`'s create flow finishes the job by: standing up a throwaway local responder,
7
- * exposing it on an ephemeral quick tunnel, PATCHing the app's event subscription at it, capturing
8
- * the token from the challenge, and tearing everything down. `dev --tunnel` / `deploy … --run`
9
- * re-PATCH the request URL onto the real server later (the token is app-level and survives that).
10
- *
11
- * Security: the serving path keeps its strict non-empty-token requirement — no bootstrap mode there.
12
- * The throwaway URL is random (unguessable), lives for seconds, and only the FIRST challenge is
13
- * accepted, immediately after our own credential-authenticated PATCH.
2
+ * Verification-Token bootstrap — the token has NO read API (field-tested 2026-07: the v6 app detail never returns
3
+ * `encryption`, and the v7 config route only supports PATCH).
14
4
  */
15
5
  import { createServer } from "node:http";
16
- /**
17
- * Run the bootstrap (module header). Resolves with the app's Verification Token; rejects with a
18
- * plain, actionable Error (tunnel unavailable, PATCH refused, challenge never arrived) — the caller
19
- * degrades to the manual console instruction.
20
- */
21
6
  export async function bootstrapFeishuVerificationToken(options) {
22
7
  let capturedToken;
23
8
  const token = new Promise((resolve) => {
24
9
  capturedToken = resolve;
25
10
  });
26
- // The throwaway responder: answers /health (diagnostics) and the url_verification challenge
27
- // (echo `challenge` back), capturing `token` from the FIRST challenge only.
11
+ // The throwaway responder: answers /health (diagnostics) and the url_verification challenge (echo `challenge`
12
+ // back), capturing `token` from the FIRST challenge only.
28
13
  const server = createServer((req, res) => {
29
14
  if (req.method === "GET") {
30
15
  res.end("ok");
@@ -46,7 +31,7 @@ export async function bootstrapFeishuVerificationToken(options) {
46
31
  }
47
32
  }
48
33
  catch {
49
- /* not the challenge — fall through to the empty ACK */
34
+ // not the challenge — fall through to the empty ACK
50
35
  }
51
36
  res.end("{}");
52
37
  });
@@ -59,9 +44,8 @@ export async function bootstrapFeishuVerificationToken(options) {
59
44
  if (!tunnel)
60
45
  throw new Error("no tunnel came up (is cloudflared installed?)");
61
46
  options.onTunnelReady?.(tunnel.url);
62
- // Do NOT health-poll the edge here: "can WE reach it" says nothing about platform→edge, the path
63
- // the challenge uses (field-tested: local negative-DNS caching failed while that path worked).
64
- // PATCH immediately; it is the real probe because success requires the challenge round-trip.
47
+ // Do NOT health-poll the edge here: "can WE reach it" says nothing about platform→edge, the path the challenge
48
+ // uses (field-tested: local negative-DNS caching failed while that path worked).
65
49
  const attempts = options.patchAttempts ?? 8;
66
50
  for (let attempt = 1;; attempt++) {
67
51
  try {
@@ -1,21 +1,6 @@
1
1
  /**
2
- * Card JSON builders — PURE: the streaming-card entity (JSON 2.0) the live preview creates, the settled
3
- * final card, and the message content that mounts a card entity into a chat. Kept out of preview.ts so
4
- * the card DSL is data-in → string-out and testable without the pump.
5
- *
6
- * The streaming card is TWO markdown elements: `process` (the volatile block — thinking tail, tool
7
- * lines, retry notice) and `answer` (append-only). The pump PUTs full-text snapshots per element
8
- * (feishu-api.ts `updateCardElement`) and the client renders the typewriter effect. The split is the
9
- * prefix-stability rule made structural: the client animates an element's update only when the old
10
- * text is a PREFIX of the new — otherwise it re-types everything after the first divergent character.
11
- * The process block's head changes every frame (a sliding thinking tail, `…`→`✓` status flips), so
12
- * sharing one element with the answer re-typed the whole card once a second; two elements confine the
13
- * churn to the small process block and keep the answer's typewriter smooth. Settling replaces the
14
- * whole entity (`updateCard`) with the answer element alone, `streaming_mode` off — one write flips
15
- * content and mode together and drops the process block.
16
- *
17
- * Budget: a card entity is capped at 30 KB, so the final answer's card chunk (and the live view) stay
18
- * well under it; longer answers overflow into follow-up messages (preview.ts owns that policy).
2
+ * Card JSON builders — PURE: the streaming-card entity (JSON 2.0) the live preview creates, the settled final card,
3
+ * and the message content that mounts a card entity into a chat.
19
4
  */
20
5
  /** The append-only answer element's id — shared by create (card.ts) and update (preview.ts). */
21
6
  export declare const ANSWER_ELEMENT_ID = "answer";
@@ -24,20 +9,21 @@ export declare const PROCESS_ELEMENT_ID = "process";
24
9
  /** Byte budget for markdown carried by ONE card (entity cap 30 KB minus JSON envelope + escaping room). */
25
10
  export declare const CARD_MARKDOWN_MAX_BYTES: number;
26
11
  /**
27
- * The answer's first line as plain text — what the chat list and the push notification show for the
28
- * settled card (`config.summary.content`). Without it a card message previews as a generic "[Card]"
29
- * placeholder: the user's notification would never carry the actual answer. Markdown is stripped
30
- * lightly (this is a one-line teaser, not a renderer): fenced code dropped, links/images → their text,
31
- * emphasis/heading/list markers removed.
12
+ * The answer's first line as plain text — what the chat list and the push notification show for the settled card
13
+ * (`config.summary.content`).
32
14
  */
33
15
  export declare function cardSummary(markdown: string): string;
34
- /** The live-preview card entity: streaming on, the process element seeded with the placeholder/queue
35
- * status and the answer element seeded EMPTY (the platform accepts an empty markdown element; it
36
- * renders zero-height until the first answer snapshot lands as a clean prefix extension of ""). */
16
+ /**
17
+ * The live-preview card entity: streaming on, the process element seeded with the placeholder/queue status and the
18
+ * answer element seeded EMPTY (the platform accepts an empty markdown element; it renders zero-height until the first
19
+ * answer snapshot lands as a clean prefix extension of "").
20
+ */
37
21
  export declare function streamingCardJson(initialProcess: string): string;
38
- /** The settled card: final markdown alone (the process block was preview-only), streaming off (stops
39
- * the client's streaming affordance), plus the answer-derived summary so the chat list / notification
40
- * shows the reply, not "[Card]". */
22
+ /**
23
+ * The settled card: final markdown alone (the process block was preview-only), streaming off (stops the client's
24
+ * streaming affordance), plus the answer-derived summary so the chat list / notification shows the reply, not
25
+ * "[Card]".
26
+ */
41
27
  export declare function finalCardJson(markdown: string): string;
42
28
  /** The `interactive` message content that mounts a card ENTITY (vs an inline static card). */
43
29
  export declare function cardEntityContent(cardId: string): string;
@@ -1,21 +1,6 @@
1
1
  /**
2
- * Card JSON builders — PURE: the streaming-card entity (JSON 2.0) the live preview creates, the settled
3
- * final card, and the message content that mounts a card entity into a chat. Kept out of preview.ts so
4
- * the card DSL is data-in → string-out and testable without the pump.
5
- *
6
- * The streaming card is TWO markdown elements: `process` (the volatile block — thinking tail, tool
7
- * lines, retry notice) and `answer` (append-only). The pump PUTs full-text snapshots per element
8
- * (feishu-api.ts `updateCardElement`) and the client renders the typewriter effect. The split is the
9
- * prefix-stability rule made structural: the client animates an element's update only when the old
10
- * text is a PREFIX of the new — otherwise it re-types everything after the first divergent character.
11
- * The process block's head changes every frame (a sliding thinking tail, `…`→`✓` status flips), so
12
- * sharing one element with the answer re-typed the whole card once a second; two elements confine the
13
- * churn to the small process block and keep the answer's typewriter smooth. Settling replaces the
14
- * whole entity (`updateCard`) with the answer element alone, `streaming_mode` off — one write flips
15
- * content and mode together and drops the process block.
16
- *
17
- * Budget: a card entity is capped at 30 KB, so the final answer's card chunk (and the live view) stay
18
- * well under it; longer answers overflow into follow-up messages (preview.ts owns that policy).
2
+ * Card JSON builders — PURE: the streaming-card entity (JSON 2.0) the live preview creates, the settled final card,
3
+ * and the message content that mounts a card entity into a chat.
19
4
  */
20
5
  import { truncateCodePointPrefix } from "../kit/text.js";
21
6
  /** The append-only answer element's id — shared by create (card.ts) and update (preview.ts). */
@@ -27,11 +12,8 @@ export const CARD_MARKDOWN_MAX_BYTES = 20 * 1024;
27
12
  /** Character budget for the settled card's summary (the chat-list / push-notification preview). */
28
13
  const SUMMARY_MAX_CHARS = 60;
29
14
  /**
30
- * The answer's first line as plain text — what the chat list and the push notification show for the
31
- * settled card (`config.summary.content`). Without it a card message previews as a generic "[Card]"
32
- * placeholder: the user's notification would never carry the actual answer. Markdown is stripped
33
- * lightly (this is a one-line teaser, not a renderer): fenced code dropped, links/images → their text,
34
- * emphasis/heading/list markers removed.
15
+ * The answer's first line as plain text — what the chat list and the push notification show for the settled card
16
+ * (`config.summary.content`).
35
17
  */
36
18
  export function cardSummary(markdown) {
37
19
  const line = markdown
@@ -52,25 +34,29 @@ function cardJson(elements, streaming, summary) {
52
34
  config: {
53
35
  streaming_mode: streaming,
54
36
  update_multi: true,
55
- // Only the settled card sets a summary — while streaming, the platform's default (a localized
56
- // "[Generating…]") is better than any fixed text we could pin.
37
+ // Only the settled card sets a summary — while streaming, the platform's default (a localized "[Generating…]")
38
+ // is better than any fixed text we could pin.
57
39
  ...(summary ? { summary: { content: summary } } : {}),
58
40
  },
59
41
  body: { elements },
60
42
  });
61
43
  }
62
- /** The live-preview card entity: streaming on, the process element seeded with the placeholder/queue
63
- * status and the answer element seeded EMPTY (the platform accepts an empty markdown element; it
64
- * renders zero-height until the first answer snapshot lands as a clean prefix extension of ""). */
44
+ /**
45
+ * The live-preview card entity: streaming on, the process element seeded with the placeholder/queue status and the
46
+ * answer element seeded EMPTY (the platform accepts an empty markdown element; it renders zero-height until the first
47
+ * answer snapshot lands as a clean prefix extension of "").
48
+ */
65
49
  export function streamingCardJson(initialProcess) {
66
50
  return cardJson([
67
51
  { tag: "markdown", content: initialProcess, element_id: PROCESS_ELEMENT_ID },
68
52
  { tag: "markdown", content: "", element_id: ANSWER_ELEMENT_ID },
69
53
  ], true);
70
54
  }
71
- /** The settled card: final markdown alone (the process block was preview-only), streaming off (stops
72
- * the client's streaming affordance), plus the answer-derived summary so the chat list / notification
73
- * shows the reply, not "[Card]". */
55
+ /**
56
+ * The settled card: final markdown alone (the process block was preview-only), streaming off (stops the client's
57
+ * streaming affordance), plus the answer-derived summary so the chat list / notification shows the reply, not
58
+ * "[Card]".
59
+ */
74
60
  export function finalCardJson(markdown) {
75
61
  return cardJson([{ tag: "markdown", content: markdown, element_id: ANSWER_ELEMENT_ID }], false, cardSummary(markdown));
76
62
  }
@@ -1,8 +1,4 @@
1
- /**
2
- * Feishu is the canonical implementation. Lark international speaks the same protocol but trails the
3
- * primary cloud in control-plane capabilities, so it binds the Feishu engine through this explicit
4
- * compatibility profile instead of becoming the engine's naming/design center.
5
- */
1
+ /** Feishu is the canonical implementation. */
6
2
  export type FeishuCloudKind = "feishu" | "lark";
7
3
  export interface FeishuCloudProfile {
8
4
  kind: FeishuCloudKind;
@@ -1,9 +1,4 @@
1
- /**
2
- * Feishu/Lark's half of the shared context buffer (mechanics + consume protocol:
3
- * ../kit/context-buffer.ts): the entry shape, its fold-line rendering, place-key derivation, and
4
- * buffered-resource selection. Entries are bucketed by conversation place (main chat, or one
5
- * concrete thread root) and folded into the next answered turn in that place.
6
- */
1
+ /** Feishu/Lark's half of the shared context buffer (mechanics + consume protocol: ../kit/context-buffer.ts). */
7
2
  import { type ContextBuffer } from "../kit/context-buffer.ts";
8
3
  import type { NormalizedFeishuMessage } from "./model.ts";
9
4
  export interface FeishuBufferedResource {
@@ -23,25 +18,11 @@ export interface FeishuBufferEntry {
23
18
  export interface FeishuBufferedRef extends FeishuBufferedResource {
24
19
  from: string;
25
20
  }
26
- /**
27
- * The place a message belongs to: the main chat, or a thread within it. Keyed by `thread_id`, the
28
- * platform's own identity for a side conversation — NOT `root_id`, which tracks the reply chain and
29
- * can differ between messages of one thread (which would split a thread's context across buckets).
30
- * A quoted reply outside a thread carries a root but is main-chat discussion, so it buckets there.
31
- *
32
- * Its own namespace, deliberately: this names a BUCKET of undelivered text, while `parse.ts`'s
33
- * `placeKey` names a SESSION. Nothing here claims anything about that session — unlike thread
34
- * participation, which asserts "the agent answered into this memory" and is therefore keyed by the
35
- * session itself. Re-keying either one leaves the other correct, and converging them now would strand
36
- * live buckets for no gain (Slack keeps its own shape for the same reason).
37
- */
21
+ /** The place a message belongs to: the main chat, or a thread within it. */
38
22
  export declare function feishuBufferPlaceKey(conversation: Pick<NormalizedFeishuMessage["conversation"], "chatId" | "threadId">): string;
39
- /** One-line, bounded background text. Resource-only messages already carry a visible decoder marker. */
23
+ /** One-line, bounded background text. */
40
24
  export declare function feishuBufferText(text: string): string;
41
- /**
42
- * Select the most recent background resources, excluding resources already primary on this turn. A
43
- * resource is message-scoped in Feishu/Lark, so identity is `message_id + key`, never the bare key.
44
- */
25
+ /** Select the most recent background resources, excluding resources already primary on this turn. */
45
26
  export declare function collectFeishuBufferedAttachments(consumed: FeishuBufferEntry[], primary: {
46
27
  files: FeishuBufferedResource[];
47
28
  images: FeishuBufferedResource[];
@@ -1,10 +1,5 @@
1
- /**
2
- * Feishu/Lark's half of the shared context buffer (mechanics + consume protocol:
3
- * ../kit/context-buffer.ts): the entry shape, its fold-line rendering, place-key derivation, and
4
- * buffered-resource selection. Entries are bucketed by conversation place (main chat, or one
5
- * concrete thread root) and folded into the next answered turn in that place.
6
- */
7
- import { BUFFER_ATTACH_MAX, BUFFER_LINE_MAX_CHARS, createContextBuffer as createGenericContextBuffer, } from "../kit/context-buffer.js";
1
+ /** Feishu/Lark's half of the shared context buffer (mechanics + consume protocol: ../kit/context-buffer.ts). */
2
+ import { capBufferedRefs, BUFFER_LINE_MAX_CHARS, createContextBuffer as createGenericContextBuffer, } from "../kit/context-buffer.js";
8
3
  import { truncateCodePointPrefix } from "../kit/text.js";
9
4
  function bufferLine(entry) {
10
5
  const meta = [`msg ${entry.messageId}`, entry.replyTo ? `reply to msg ${entry.replyTo}` : undefined]
@@ -12,32 +7,18 @@ function bufferLine(entry) {
12
7
  .join(", ");
13
8
  return `${entry.sender} (${meta}): ${entry.body}`;
14
9
  }
15
- /**
16
- * The place a message belongs to: the main chat, or a thread within it. Keyed by `thread_id`, the
17
- * platform's own identity for a side conversation — NOT `root_id`, which tracks the reply chain and
18
- * can differ between messages of one thread (which would split a thread's context across buckets).
19
- * A quoted reply outside a thread carries a root but is main-chat discussion, so it buckets there.
20
- *
21
- * Its own namespace, deliberately: this names a BUCKET of undelivered text, while `parse.ts`'s
22
- * `placeKey` names a SESSION. Nothing here claims anything about that session — unlike thread
23
- * participation, which asserts "the agent answered into this memory" and is therefore keyed by the
24
- * session itself. Re-keying either one leaves the other correct, and converging them now would strand
25
- * live buckets for no gain (Slack keeps its own shape for the same reason).
26
- */
10
+ /** The place a message belongs to: the main chat, or a thread within it. */
27
11
  export function feishuBufferPlaceKey(conversation) {
28
12
  return conversation.threadId ? `${conversation.chatId}:thread:${conversation.threadId}` : conversation.chatId;
29
13
  }
30
- /** One-line, bounded background text. Resource-only messages already carry a visible decoder marker. */
14
+ /** One-line, bounded background text. */
31
15
  export function feishuBufferText(text) {
32
16
  return truncateCodePointPrefix(text.replace(/\s+/g, " ").trim(), BUFFER_LINE_MAX_CHARS);
33
17
  }
34
18
  function resourceIdentity(resource) {
35
19
  return `${resource.messageId}\u0000${resource.key}`;
36
20
  }
37
- /**
38
- * Select the most recent background resources, excluding resources already primary on this turn. A
39
- * resource is message-scoped in Feishu/Lark, so identity is `message_id + key`, never the bare key.
40
- */
21
+ /** Select the most recent background resources, excluding resources already primary on this turn. */
41
22
  export function collectFeishuBufferedAttachments(consumed, primary) {
42
23
  const refs = (pick, primaryRefs) => {
43
24
  const excluded = new Set(primaryRefs.map(resourceIdentity));
@@ -54,13 +35,9 @@ export function collectFeishuBufferedAttachments(consumed, primary) {
54
35
  }
55
36
  return out;
56
37
  };
57
- const files = refs((entry) => entry.files, primary.files);
58
- const images = refs((entry) => entry.images, primary.images);
59
- return {
60
- files: files.slice(-BUFFER_ATTACH_MAX),
61
- images: images.slice(-BUFFER_ATTACH_MAX),
62
- skipped: Math.max(0, files.length - BUFFER_ATTACH_MAX) + Math.max(0, images.length - BUFFER_ATTACH_MAX),
63
- };
38
+ const files = capBufferedRefs(refs((entry) => entry.files, primary.files));
39
+ const images = capBufferedRefs(refs((entry) => entry.images, primary.images));
40
+ return { files: files.kept, images: images.kept, skipped: files.skipped + images.skipped };
64
41
  }
65
42
  function isResource(value) {
66
43
  const resource = value;
@@ -1,9 +1,14 @@
1
- /** Decrypt an `{"encrypt": …}` event payload to its plaintext JSON string. Throws on malformed
2
- * input or invalid padding. AES-CBC is not authenticated, so a wrong key is not mathematically
3
- * guaranteed to fail padding; the caller verifies signed events before decrypting and JSON-parses every
4
- * plaintext envelope, turning wrong-key garbage into a 4xx rather than a silent drop. */
1
+ /**
2
+ * Decrypt an `{"encrypt": …}` event payload to its plaintext JSON string; throws on malformed input or invalid
3
+ * padding. AES-CBC is NOT authenticated, so a wrong key is not mathematically guaranteed to fail padding: the caller
4
+ * must verify a signed event BEFORE decrypting it, and JSON-parse every plaintext envelope afterwards, so wrong-key
5
+ * garbage becomes a 4xx rather than a silent drop.
6
+ */
5
7
  export declare function decryptEvent(encryptKey: string, encryptB64: string): string;
6
- /** The expected `X-Lark-Signature` for a request: sha256(timestamp + nonce + encryptKey + rawBody) hex. */
8
+ /**
9
+ * The expected `X-Lark-Signature` for a request: sha256(timestamp + nonce + encryptKey + rawBody) hex. `rawBody` is
10
+ * the VERBATIM request body (the encrypted form) — the digest is over bytes, so a re-serialization never matches.
11
+ */
7
12
  export declare function eventSignature(encryptKey: string, timestamp: string, nonce: string, rawBody: string): string;
8
13
  /** Whether a request's signature headers verify against the raw body (constant-time). */
9
14
  export declare function verifySignature(encryptKey: string, headers: {
@@ -1,25 +1,15 @@
1
1
  /**
2
- * Canonical Feishu webhook security (reused by Lark compatibility) — PURE: AES event decryption and request
3
- * signature, exactly as the open platform defines them. When an Encrypt Key is configured in the
4
- * developer console, every event arrives as `{"encrypt": "<base64>"}` with signature headers; without
5
- * one, events arrive in plaintext and carry only the verification token in the body. feishu.ts owns the
6
- * fail-closed policy (which checks run when); this module owns the math.
7
- *
8
- * - Decryption: AES-256-CBC. The key is sha256(encryptKey); the base64 payload is IV (16 bytes) ‖
9
- * ciphertext; the plaintext is the event JSON (PKCS#7 padding handled by the cipher).
10
- * - Signature: `X-Lark-Signature = sha256(timestamp + nonce + encryptKey + rawBody)` hex, where
11
- * rawBody is the VERBATIM request body (the encrypted form) — computed over bytes, so the caller
12
- * must pass the raw text, never a re-serialization.
13
- *
14
- * Comparisons are constant-time (timingSafeEqual) so neither the signature check nor the verification-
15
- * token check leaks a timing signal.
2
+ * Canonical Feishu webhook security (reused by Lark compatibility) — PURE: AES event decryption and request signature,
3
+ * exactly as the open platform defines them.
16
4
  */
17
5
  import { createDecipheriv, createHash } from "node:crypto";
18
6
  import { secretEquals } from "../secret.js";
19
- /** Decrypt an `{"encrypt": …}` event payload to its plaintext JSON string. Throws on malformed
20
- * input or invalid padding. AES-CBC is not authenticated, so a wrong key is not mathematically
21
- * guaranteed to fail padding; the caller verifies signed events before decrypting and JSON-parses every
22
- * plaintext envelope, turning wrong-key garbage into a 4xx rather than a silent drop. */
7
+ /**
8
+ * Decrypt an `{"encrypt": …}` event payload to its plaintext JSON string; throws on malformed input or invalid
9
+ * padding. AES-CBC is NOT authenticated, so a wrong key is not mathematically guaranteed to fail padding: the caller
10
+ * must verify a signed event BEFORE decrypting it, and JSON-parse every plaintext envelope afterwards, so wrong-key
11
+ * garbage becomes a 4xx rather than a silent drop.
12
+ */
23
13
  export function decryptEvent(encryptKey, encryptB64) {
24
14
  const key = createHash("sha256").update(encryptKey, "utf8").digest();
25
15
  const buf = Buffer.from(encryptB64, "base64");
@@ -28,7 +18,10 @@ export function decryptEvent(encryptKey, encryptB64) {
28
18
  const decipher = createDecipheriv("aes-256-cbc", key, buf.subarray(0, 16));
29
19
  return Buffer.concat([decipher.update(buf.subarray(16)), decipher.final()]).toString("utf8");
30
20
  }
31
- /** The expected `X-Lark-Signature` for a request: sha256(timestamp + nonce + encryptKey + rawBody) hex. */
21
+ /**
22
+ * The expected `X-Lark-Signature` for a request: sha256(timestamp + nonce + encryptKey + rawBody) hex. `rawBody` is
23
+ * the VERBATIM request body (the encrypted form) — the digest is over bytes, so a re-serialization never matches.
24
+ */
32
25
  export function eventSignature(encryptKey, timestamp, nonce, rawBody) {
33
26
  return createHash("sha256").update(`${timestamp}${nonce}${encryptKey}${rawBody}`, "utf8").digest("hex");
34
27
  }
@@ -1,5 +1,6 @@
1
1
  import type { ImageRef } from "../../agent.ts";
2
2
  import type { FeishuCloudKind } from "./cloud.ts";
3
+ import type { CallOptions } from "../kit/transport.ts";
3
4
  /** Where a reply goes: a chat, optionally quote-replying a message inside a platform thread. */
4
5
  export interface FeishuTarget {
5
6
  chatId: string;
@@ -118,7 +119,7 @@ export interface FeishuApi {
118
119
  /** Create a card entity (card JSON 2.0). Returns its card_id. */
119
120
  createCard(cardJson: string): Promise<string>;
120
121
  /** Stream-update a card element's text (full-content snapshot + strictly increasing sequence). */
121
- updateCardElement(cardId: string, elementId: string, content: string, sequence: number): Promise<void>;
122
+ updateCardElement(cardId: string, elementId: string, content: string, sequence: number, opts?: CallOptions): Promise<void>;
122
123
  /** Replace a card entity's content (the settle write; also flips streaming_mode off via the JSON). */
123
124
  updateCard(cardId: string, cardJson: string, sequence: number): Promise<void>;
124
125
  }