@fastagent-sh/fastagent 0.24.4 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (487) hide show
  1. package/README.md +54 -82
  2. package/dist/agent-api.d.ts +460 -0
  3. package/dist/agent-api.js +209 -0
  4. package/dist/answer-text.d.ts +8 -0
  5. package/dist/answer-text.js +18 -0
  6. package/dist/atomic-write.d.ts +2 -2
  7. package/dist/atomic-write.js +2 -2
  8. package/dist/authoring.d.ts +81 -0
  9. package/dist/authoring.js +245 -0
  10. package/dist/channel.d.ts +29 -15
  11. package/dist/channel.js +4 -1
  12. package/dist/channels/agentcore-protocol.d.ts +36 -50
  13. package/dist/channels/agentcore-protocol.js +3 -3
  14. package/dist/channels/agentcore-service.d.ts +8 -18
  15. package/dist/channels/agentcore-service.js +108 -127
  16. package/dist/channels/agentcore.d.ts +8 -24
  17. package/dist/channels/agentcore.js +32 -65
  18. package/dist/channels/body.d.ts +0 -16
  19. package/dist/channels/body.js +0 -25
  20. package/dist/channels/busy.js +6 -1
  21. package/dist/channels/context.d.ts +4 -0
  22. package/dist/channels/context.js +35 -0
  23. package/dist/channels/discover.d.ts +18 -20
  24. package/dist/channels/discover.js +41 -99
  25. package/dist/channels/feishu/feishu-api.d.ts +54 -4
  26. package/dist/channels/feishu/feishu-api.js +192 -149
  27. package/dist/channels/feishu/feishu.d.ts +3 -0
  28. package/dist/channels/feishu/feishu.js +117 -106
  29. package/dist/channels/feishu/history.d.ts +55 -0
  30. package/dist/channels/feishu/history.js +199 -0
  31. package/dist/channels/feishu/invoke-turn.d.ts +13 -11
  32. package/dist/channels/feishu/invoke-turn.js +9 -10
  33. package/dist/channels/feishu/model.d.ts +2 -1
  34. package/dist/channels/feishu/normalize.js +100 -0
  35. package/dist/channels/feishu/preview.d.ts +2 -2
  36. package/dist/channels/feishu/register-webhook.d.ts +1 -0
  37. package/dist/channels/feishu/register-webhook.js +3 -2
  38. package/dist/channels/feishu/scaffold/channel.ts +65 -46
  39. package/dist/channels/feishu/scaffold/feishu-send.ts +38 -33
  40. package/dist/channels/feishu/scaffold/feishu-threads.ts +25 -0
  41. package/dist/channels/feishu/setup-mode.d.ts +45 -25
  42. package/dist/channels/feishu/setup-mode.js +74 -23
  43. package/dist/channels/feishu/shared-api.d.ts +24 -6
  44. package/dist/channels/feishu/shared-api.js +30 -24
  45. package/dist/channels/kit/context-buffer.d.ts +11 -2
  46. package/dist/channels/kit/context-buffer.js +7 -4
  47. package/dist/channels/kit/delivery.d.ts +19 -3
  48. package/dist/channels/kit/delivery.js +74 -15
  49. package/dist/channels/kit/event-stream.js +2 -1
  50. package/dist/channels/kit/invoke-turn-kit.d.ts +41 -41
  51. package/dist/channels/kit/invoke-turn-kit.js +62 -71
  52. package/dist/channels/kit/place-history.d.ts +107 -0
  53. package/dist/channels/kit/place-history.js +182 -0
  54. package/dist/channels/kit/preview-kit.d.ts +9 -5
  55. package/dist/channels/kit/preview-kit.js +36 -15
  56. package/dist/channels/kit/room-threads.d.ts +46 -0
  57. package/dist/channels/kit/room-threads.js +81 -0
  58. package/dist/channels/kit/seen.js +1 -1
  59. package/dist/channels/kit/stop-command.d.ts +6 -3
  60. package/dist/channels/kit/stop-command.js +20 -18
  61. package/dist/channels/kit/thread-fork.d.ts +17 -0
  62. package/dist/channels/kit/thread-fork.js +43 -0
  63. package/dist/channels/kit/thread-participants.js +1 -1
  64. package/dist/channels/kit/transport.d.ts +5 -0
  65. package/dist/channels/kit/transport.js +5 -0
  66. package/dist/channels/kit/turn-runner.d.ts +10 -9
  67. package/dist/channels/kit/turn-runner.js +17 -19
  68. package/dist/channels/kit/turn-store.d.ts +5 -4
  69. package/dist/channels/kit/turn-store.js +5 -5
  70. package/dist/channels/lark/lark.d.ts +3 -0
  71. package/dist/channels/lark/lark.js +5 -0
  72. package/dist/channels/lark/onboard.d.ts +5 -3
  73. package/dist/channels/lark/onboard.js +14 -14
  74. package/dist/channels/lark/scaffold/channel.ts +67 -44
  75. package/dist/channels/lark/scaffold/lark-send.ts +38 -33
  76. package/dist/channels/lark/scaffold/lark-threads.ts +25 -0
  77. package/dist/channels/respond.d.ts +0 -4
  78. package/dist/channels/respond.js +1 -1
  79. package/dist/channels/router.d.ts +18 -0
  80. package/dist/channels/router.js +68 -0
  81. package/dist/channels/serve.d.ts +5 -70
  82. package/dist/channels/serve.js +105 -287
  83. package/dist/channels/slack/history.d.ts +42 -0
  84. package/dist/channels/slack/history.js +282 -0
  85. package/dist/channels/slack/invoke-turn.d.ts +12 -5
  86. package/dist/channels/slack/invoke-turn.js +7 -5
  87. package/dist/channels/slack/manifest.d.ts +1 -4
  88. package/dist/channels/slack/manifest.js +22 -10
  89. package/dist/channels/slack/model.d.ts +2 -1
  90. package/dist/channels/slack/onboard.d.ts +0 -2
  91. package/dist/channels/slack/onboard.js +2 -4
  92. package/dist/channels/slack/onboarding-state.d.ts +0 -2
  93. package/dist/channels/slack/onboarding-state.js +0 -1
  94. package/dist/channels/slack/parse.d.ts +0 -5
  95. package/dist/channels/slack/parse.js +1 -16
  96. package/dist/channels/slack/preview.d.ts +2 -3
  97. package/dist/channels/slack/preview.js +78 -52
  98. package/dist/channels/slack/register-webhook.d.ts +1 -0
  99. package/dist/channels/slack/register-webhook.js +6 -5
  100. package/dist/channels/slack/scaffold/channel.ts +23 -24
  101. package/dist/channels/slack/scaffold/slack-send.ts +50 -43
  102. package/dist/channels/slack/scaffold/slack-threads.ts +29 -0
  103. package/dist/channels/slack/setup-server.d.ts +19 -0
  104. package/dist/channels/slack/setup-server.js +113 -11
  105. package/dist/channels/slack/shared-api.d.ts +15 -4
  106. package/dist/channels/slack/shared-api.js +19 -14
  107. package/dist/channels/slack/slack-api.d.ts +61 -2
  108. package/dist/channels/slack/slack-api.js +28 -5
  109. package/dist/channels/slack/slack.js +97 -52
  110. package/dist/channels/slack/welcomed.js +1 -1
  111. package/dist/channels/telegram/context-buffer.d.ts +6 -1
  112. package/dist/channels/telegram/context-buffer.js +19 -0
  113. package/dist/channels/telegram/invoke-turn.d.ts +8 -4
  114. package/dist/channels/telegram/invoke-turn.js +7 -7
  115. package/dist/channels/telegram/parse.d.ts +7 -2
  116. package/dist/channels/telegram/parse.js +7 -0
  117. package/dist/channels/telegram/preview.d.ts +2 -2
  118. package/dist/channels/telegram/register-webhook.d.ts +1 -0
  119. package/dist/channels/telegram/register-webhook.js +3 -2
  120. package/dist/channels/telegram/scaffold/channel.ts +27 -28
  121. package/dist/channels/telegram/scaffold/telegram-send.ts +61 -108
  122. package/dist/channels/telegram/shared-api.d.ts +34 -0
  123. package/dist/channels/telegram/shared-api.js +79 -0
  124. package/dist/channels/telegram/telegram-api.d.ts +15 -5
  125. package/dist/channels/telegram/telegram-api.js +10 -6
  126. package/dist/channels/telegram/telegram.js +27 -21
  127. package/dist/channels/telegram/turn-store.js +1 -1
  128. package/dist/channels/{kit/turn-queue.d.ts → turn-queue.d.ts} +2 -2
  129. package/dist/channels/{kit/turn-queue.js → turn-queue.js} +10 -10
  130. package/dist/cli/add-feishu.d.ts +42 -12
  131. package/dist/cli/add-feishu.js +177 -124
  132. package/dist/cli/add-slack.d.ts +8 -3
  133. package/dist/cli/add-slack.js +116 -64
  134. package/dist/cli/auth-view.d.ts +10 -3
  135. package/dist/cli/auth-view.js +8 -6
  136. package/dist/cli/box-login.d.ts +18 -7
  137. package/dist/cli/box-login.js +46 -12
  138. package/dist/cli/commands/add.d.ts +15 -4
  139. package/dist/cli/commands/add.js +253 -143
  140. package/dist/cli/commands/auth.d.ts +6 -0
  141. package/dist/cli/commands/auth.js +50 -0
  142. package/dist/cli/commands/content.d.ts +6 -0
  143. package/dist/cli/commands/content.js +45 -0
  144. package/dist/cli/commands/deploy/agentcore.js +50 -77
  145. package/dist/cli/commands/deploy/docker.js +38 -39
  146. package/dist/cli/commands/deploy/fly.js +21 -18
  147. package/dist/cli/commands/deploy/host-login.d.ts +26 -0
  148. package/dist/cli/commands/deploy/host-login.js +90 -0
  149. package/dist/cli/commands/deploy/railway.js +21 -26
  150. package/dist/cli/commands/deploy/shared.d.ts +45 -17
  151. package/dist/cli/commands/deploy/shared.js +34 -15
  152. package/dist/cli/commands/deploy.d.ts +0 -4
  153. package/dist/cli/commands/deploy.js +237 -50
  154. package/dist/cli/commands/destroy.js +8 -6
  155. package/dist/cli/commands/dev.d.ts +0 -2
  156. package/dist/cli/commands/dev.js +20 -19
  157. package/dist/cli/commands/env.d.ts +12 -0
  158. package/dist/cli/commands/env.js +191 -0
  159. package/dist/cli/commands/info.js +95 -156
  160. package/dist/cli/commands/init.d.ts +2 -2
  161. package/dist/cli/commands/init.js +35 -33
  162. package/dist/cli/commands/invoke.d.ts +6 -0
  163. package/dist/cli/commands/invoke.js +132 -16
  164. package/dist/cli/commands/login.d.ts +31 -2
  165. package/dist/cli/commands/login.js +416 -65
  166. package/dist/cli/commands/logout.d.ts +4 -0
  167. package/dist/cli/commands/logout.js +53 -0
  168. package/dist/cli/commands/logs.js +8 -6
  169. package/dist/cli/commands/models.d.ts +2 -1
  170. package/dist/cli/commands/models.js +39 -37
  171. package/dist/cli/commands/schedules.d.ts +21 -0
  172. package/dist/cli/commands/schedules.js +143 -0
  173. package/dist/cli/commands/start.d.ts +11 -10
  174. package/dist/cli/commands/start.js +35 -62
  175. package/dist/cli/content-view.d.ts +9 -0
  176. package/dist/cli/content-view.js +26 -0
  177. package/dist/cli/fail.d.ts +9 -12
  178. package/dist/cli/fail.js +17 -14
  179. package/dist/cli/invoke-stream.d.ts +12 -3
  180. package/dist/cli/invoke-stream.js +85 -23
  181. package/dist/cli/kernel.d.ts +9 -2
  182. package/dist/cli/kernel.js +15 -6
  183. package/dist/cli/login-relay.d.ts +6 -4
  184. package/dist/cli/login-relay.js +19 -7
  185. package/dist/cli/models-view.d.ts +5 -1
  186. package/dist/cli/models-view.js +1 -1
  187. package/dist/cli/person.d.ts +10 -0
  188. package/dist/cli/person.js +23 -0
  189. package/dist/cli/program.js +421 -203
  190. package/dist/cli/serve.d.ts +13 -63
  191. package/dist/cli/serve.js +31 -121
  192. package/dist/cli/shared.d.ts +21 -48
  193. package/dist/cli/shared.js +70 -89
  194. package/dist/cli/ticket.d.ts +136 -0
  195. package/dist/cli/ticket.js +476 -0
  196. package/dist/client.d.ts +7 -0
  197. package/dist/client.js +2 -0
  198. package/dist/content/declare.d.ts +70 -0
  199. package/dist/content/declare.js +163 -0
  200. package/dist/content/file.d.ts +32 -0
  201. package/dist/content/file.js +119 -0
  202. package/dist/content/git.d.ts +41 -0
  203. package/dist/content/git.js +220 -0
  204. package/dist/content/mount.d.ts +2 -0
  205. package/dist/content/mount.js +15 -0
  206. package/dist/content/resolve.d.ts +52 -0
  207. package/dist/content/resolve.js +151 -0
  208. package/dist/content/source.d.ts +27 -0
  209. package/dist/content/source.js +53 -0
  210. package/dist/conversation-sessions.d.ts +26 -0
  211. package/dist/conversation-sessions.js +99 -0
  212. package/dist/declared-secrets.d.ts +19 -37
  213. package/dist/declared-secrets.js +16 -58
  214. package/dist/deploy/agentcore/destroy.d.ts +3 -3
  215. package/dist/deploy/agentcore/destroy.js +46 -28
  216. package/dist/deploy/agentcore/forwarder.js +105 -22
  217. package/dist/deploy/agentcore/logs.js +1 -1
  218. package/dist/deploy/agentcore/plan.d.ts +10 -24
  219. package/dist/deploy/agentcore/plan.js +31 -135
  220. package/dist/deploy/agentcore/run.d.ts +1 -1
  221. package/dist/deploy/agentcore/run.js +8 -5
  222. package/dist/deploy/agentcore/shell.d.ts +1 -1
  223. package/dist/deploy/agentcore/shell.js +5 -5
  224. package/dist/deploy/api.d.ts +34 -0
  225. package/dist/deploy/api.js +50 -0
  226. package/dist/deploy/box-shell.d.ts +6 -0
  227. package/dist/deploy/box-shell.js +7 -0
  228. package/dist/deploy/build-context.d.ts +7 -5
  229. package/dist/deploy/build-context.js +41 -35
  230. package/dist/deploy/channel-ingress.d.ts +6 -1
  231. package/dist/deploy/channel-ingress.js +18 -5
  232. package/dist/deploy/container.d.ts +22 -9
  233. package/dist/deploy/container.js +96 -55
  234. package/dist/deploy/docker/plan.d.ts +3 -2
  235. package/dist/deploy/docker/plan.js +13 -14
  236. package/dist/deploy/docker/run.d.ts +2 -2
  237. package/dist/deploy/docker/run.js +8 -4
  238. package/dist/deploy/fly/plan.d.ts +1 -1
  239. package/dist/deploy/fly/plan.js +12 -21
  240. package/dist/deploy/fly/run.d.ts +10 -6
  241. package/dist/deploy/fly/run.js +11 -6
  242. package/dist/deploy/hosts.d.ts +2 -1
  243. package/dist/deploy/hosts.js +2 -1
  244. package/dist/deploy/preflight.d.ts +21 -23
  245. package/dist/deploy/preflight.js +223 -202
  246. package/dist/deploy/railway/plan.d.ts +8 -6
  247. package/dist/deploy/railway/plan.js +15 -58
  248. package/dist/deploy/railway/run.d.ts +8 -3
  249. package/dist/deploy/railway/run.js +12 -8
  250. package/dist/deploy/residency.d.ts +5 -11
  251. package/dist/deploy/residency.js +0 -6
  252. package/dist/deploy/runner.d.ts +1 -1
  253. package/dist/deploy/runner.js +1 -1
  254. package/dist/deploy/secrets.d.ts +16 -2
  255. package/dist/deploy/secrets.js +33 -6
  256. package/dist/deploy/workspace.d.ts +11 -6
  257. package/dist/deploy/workspace.js +55 -38
  258. package/dist/dev-supervisor.d.ts +11 -2
  259. package/dist/dev-supervisor.js +52 -27
  260. package/dist/effect-port.d.ts +4 -4
  261. package/dist/effect-port.js +4 -4
  262. package/dist/env.d.ts +0 -13
  263. package/dist/env.js +1 -44
  264. package/dist/environment/apt.d.ts +4 -0
  265. package/dist/environment/apt.js +32 -0
  266. package/dist/environment/declare.d.ts +59 -0
  267. package/dist/environment/declare.js +210 -0
  268. package/dist/environment/mise.d.ts +51 -0
  269. package/dist/environment/mise.js +272 -0
  270. package/dist/feishu.d.ts +2 -2
  271. package/dist/feishu.js +2 -2
  272. package/dist/git.d.ts +13 -0
  273. package/dist/git.js +55 -0
  274. package/dist/harnesses/models/catalog.d.ts +20 -0
  275. package/dist/harnesses/models/catalog.js +254 -0
  276. package/dist/{engines/pi/auth.d.ts → harnesses/models/credentials.d.ts} +3 -3
  277. package/dist/{engines/pi/auth.js → harnesses/models/credentials.js} +3 -3
  278. package/dist/harnesses/models/environment.d.ts +48 -0
  279. package/dist/harnesses/models/environment.js +88 -0
  280. package/dist/harnesses/models/host.d.ts +30 -0
  281. package/dist/harnesses/models/host.js +97 -0
  282. package/dist/{engines/pi → harnesses/models}/login.d.ts +66 -70
  283. package/dist/harnesses/models/login.js +439 -0
  284. package/dist/harnesses/models/models.d.ts +84 -0
  285. package/dist/harnesses/models/models.js +197 -0
  286. package/dist/harnesses/pi-durable/agent.d.ts +42 -0
  287. package/dist/harnesses/pi-durable/agent.js +581 -0
  288. package/dist/harnesses/pi-durable/defaults.d.ts +28 -0
  289. package/dist/harnesses/pi-durable/defaults.js +26 -0
  290. package/dist/harnesses/pi-durable/define.d.ts +77 -0
  291. package/dist/harnesses/pi-durable/define.js +28 -0
  292. package/dist/harnesses/pi-durable/entries.d.ts +32 -0
  293. package/dist/harnesses/pi-durable/entries.js +126 -0
  294. package/dist/harnesses/pi-durable/events.d.ts +25 -0
  295. package/dist/harnesses/pi-durable/events.js +238 -0
  296. package/dist/harnesses/pi-durable/ids.d.ts +16 -0
  297. package/dist/harnesses/pi-durable/ids.js +55 -0
  298. package/dist/harnesses/pi-durable/messages.d.ts +41 -0
  299. package/dist/harnesses/pi-durable/messages.js +116 -0
  300. package/dist/harnesses/pi-durable/open.d.ts +95 -0
  301. package/dist/harnesses/pi-durable/open.js +330 -0
  302. package/dist/harnesses/pi-durable/reads.d.ts +30 -0
  303. package/dist/harnesses/pi-durable/reads.js +94 -0
  304. package/dist/harnesses/pi-durable/registry.d.ts +18 -0
  305. package/dist/harnesses/pi-durable/registry.js +56 -0
  306. package/dist/harnesses/pi-durable/release.d.ts +13 -0
  307. package/dist/harnesses/pi-durable/release.js +40 -0
  308. package/dist/harnesses/pi-durable/serve.d.ts +90 -0
  309. package/dist/harnesses/pi-durable/serve.js +186 -0
  310. package/dist/harnesses/pi-durable/sessions.d.ts +27 -0
  311. package/dist/harnesses/pi-durable/sessions.js +47 -0
  312. package/dist/harnesses/pi-durable/storage.d.ts +4 -0
  313. package/dist/harnesses/pi-durable/storage.js +49 -0
  314. package/dist/harnesses/pi-durable/stream.d.ts +20 -0
  315. package/dist/harnesses/pi-durable/stream.js +221 -0
  316. package/dist/harnesses/pi-durable/text.d.ts +30 -0
  317. package/dist/harnesses/pi-durable/text.js +292 -0
  318. package/dist/harnesses/pi-durable/wake.d.ts +11 -0
  319. package/dist/harnesses/pi-durable/wake.js +90 -0
  320. package/dist/http/client.d.ts +6 -0
  321. package/dist/http/client.js +256 -0
  322. package/dist/http/contract.d.ts +541 -0
  323. package/dist/http/contract.js +179 -0
  324. package/dist/http/server.d.ts +34 -0
  325. package/dist/http/server.js +194 -0
  326. package/dist/index.d.ts +19 -4
  327. package/dist/index.js +13 -5
  328. package/dist/lark.d.ts +2 -2
  329. package/dist/lark.js +2 -2
  330. package/dist/locked-file.d.ts +17 -0
  331. package/dist/{engines/pi/locked-file.js → locked-file.js} +16 -10
  332. package/dist/once.d.ts +6 -0
  333. package/dist/once.js +23 -0
  334. package/dist/paths.d.ts +36 -52
  335. package/dist/paths.js +66 -182
  336. package/dist/runtime.d.ts +25 -2
  337. package/dist/runtime.js +27 -0
  338. package/dist/scaffold/add-channel.d.ts +14 -27
  339. package/dist/scaffold/add-channel.js +82 -180
  340. package/dist/scaffold/init.d.ts +17 -18
  341. package/dist/scaffold/init.js +87 -99
  342. package/dist/scaffold/templates/SYSTEM.md +12 -0
  343. package/dist/scaffold/templates/env.example +4 -10
  344. package/dist/scaffold/templates/fastagent.config.ts +21 -33
  345. package/dist/scaffold/templates/gitignore +8 -1
  346. package/dist/scaffold/templates/skills/writing-great-skills/SKILL.md +1 -2
  347. package/dist/scaffold/templates.d.ts +9 -4
  348. package/dist/scaffold/templates.js +7 -5
  349. package/dist/scaffold/vendor-skill.d.ts +1 -4
  350. package/dist/scaffold/vendor-skill.js +5 -6
  351. package/dist/schedule/cron.d.ts +13 -0
  352. package/dist/schedule/cron.js +52 -0
  353. package/dist/schedule/eventbridge-cron.d.ts +17 -0
  354. package/dist/schedule/eventbridge-cron.js +88 -0
  355. package/dist/schedule/schedule.d.ts +23 -0
  356. package/dist/schedule/schedule.js +34 -0
  357. package/dist/schedule/scheduler.d.ts +34 -33
  358. package/dist/schedule/scheduler.js +251 -137
  359. package/dist/schedule/state.d.ts +4 -14
  360. package/dist/schedule/state.js +7 -7
  361. package/dist/schedule/wake-alarm.d.ts +22 -2
  362. package/dist/schedule/wake-alarm.js +66 -20
  363. package/dist/schedule/wakeups.d.ts +16 -15
  364. package/dist/schedule/wakeups.js +47 -45
  365. package/dist/service.d.ts +56 -169
  366. package/dist/service.js +91 -187
  367. package/dist/serving-command.d.ts +10 -0
  368. package/dist/serving-command.js +16 -0
  369. package/dist/sign-in-api.d.ts +147 -0
  370. package/dist/sign-in-api.js +9 -0
  371. package/dist/slack.d.ts +1 -1
  372. package/dist/slack.js +1 -1
  373. package/dist/{channels/kit/state.js → state-file.js} +6 -6
  374. package/dist/telegram.d.ts +1 -0
  375. package/dist/telegram.js +1 -0
  376. package/dist/tunnel.d.ts +3 -0
  377. package/dist/tunnel.js +14 -5
  378. package/dist/value-file.d.ts +40 -0
  379. package/dist/value-file.js +164 -0
  380. package/dist/version.d.ts +5 -0
  381. package/dist/version.js +14 -1
  382. package/package.json +18 -28
  383. package/dist/agent.d.ts +0 -106
  384. package/dist/agent.js +0 -21
  385. package/dist/channels/control.d.ts +0 -31
  386. package/dist/channels/control.js +0 -329
  387. package/dist/channels/define-channel.d.ts +0 -39
  388. package/dist/channels/define-channel.js +0 -9
  389. package/dist/channels/feishu/context-buffer.d.ts +0 -35
  390. package/dist/channels/feishu/context-buffer.js +0 -65
  391. package/dist/channels/feishu/scaffold/channel.websocket.ts +0 -31
  392. package/dist/channels/http.d.ts +0 -23
  393. package/dist/channels/http.js +0 -57
  394. package/dist/channels/lark/scaffold/channel.websocket.ts +0 -31
  395. package/dist/channels/slack/context-buffer.d.ts +0 -23
  396. package/dist/channels/slack/context-buffer.js +0 -37
  397. package/dist/channels/sse.d.ts +0 -4
  398. package/dist/channels/sse.js +0 -66
  399. package/dist/cli/commands/chat.d.ts +0 -3
  400. package/dist/cli/commands/chat.js +0 -14
  401. package/dist/cli/commands/routine-run.d.ts +0 -6
  402. package/dist/cli/commands/routine-run.js +0 -46
  403. package/dist/cli/commands/routine.d.ts +0 -25
  404. package/dist/cli/commands/routine.js +0 -122
  405. package/dist/cli/commands/tool.d.ts +0 -1
  406. package/dist/cli/commands/tool.js +0 -57
  407. package/dist/collect.d.ts +0 -63
  408. package/dist/collect.js +0 -90
  409. package/dist/core.d.ts +0 -13
  410. package/dist/core.js +0 -15
  411. package/dist/engines/pi/agent-models.d.ts +0 -83
  412. package/dist/engines/pi/agent-models.js +0 -120
  413. package/dist/engines/pi/agent-session-factory.d.ts +0 -121
  414. package/dist/engines/pi/agent-session-factory.js +0 -535
  415. package/dist/engines/pi/chat.d.ts +0 -3
  416. package/dist/engines/pi/chat.js +0 -8
  417. package/dist/engines/pi/config.d.ts +0 -71
  418. package/dist/engines/pi/config.js +0 -199
  419. package/dist/engines/pi/create.d.ts +0 -170
  420. package/dist/engines/pi/create.js +0 -343
  421. package/dist/engines/pi/definition.d.ts +0 -74
  422. package/dist/engines/pi/definition.js +0 -236
  423. package/dist/engines/pi/entry-images.d.ts +0 -14
  424. package/dist/engines/pi/entry-images.js +0 -23
  425. package/dist/engines/pi/invoke-session.d.ts +0 -18
  426. package/dist/engines/pi/invoke-session.js +0 -403
  427. package/dist/engines/pi/locked-file.d.ts +0 -9
  428. package/dist/engines/pi/login.js +0 -331
  429. package/dist/engines/pi/machine.d.ts +0 -53
  430. package/dist/engines/pi/machine.js +0 -137
  431. package/dist/engines/pi/models.d.ts +0 -200
  432. package/dist/engines/pi/models.js +0 -533
  433. package/dist/engines/pi/open.d.ts +0 -180
  434. package/dist/engines/pi/open.js +0 -270
  435. package/dist/engines/pi/openai-account-models.d.ts +0 -35
  436. package/dist/engines/pi/openai-account-models.js +0 -116
  437. package/dist/engines/pi/report.d.ts +0 -18
  438. package/dist/engines/pi/report.js +0 -59
  439. package/dist/engines/pi/retry-event.d.ts +0 -6
  440. package/dist/engines/pi/retry-event.js +0 -15
  441. package/dist/engines/pi/service.d.ts +0 -9
  442. package/dist/engines/pi/service.js +0 -14
  443. package/dist/engines/pi/session-builder.d.ts +0 -13
  444. package/dist/engines/pi/session-builder.js +0 -117
  445. package/dist/engines/pi/session-control.d.ts +0 -77
  446. package/dist/engines/pi/session-control.js +0 -908
  447. package/dist/engines/pi/session-effects.d.ts +0 -14
  448. package/dist/engines/pi/session-effects.js +0 -30
  449. package/dist/engines/pi/session-inheritance.d.ts +0 -21
  450. package/dist/engines/pi/session-inheritance.js +0 -204
  451. package/dist/engines/pi/session-markers.d.ts +0 -49
  452. package/dist/engines/pi/session-markers.js +0 -60
  453. package/dist/engines/pi/session-settings.d.ts +0 -67
  454. package/dist/engines/pi/session-settings.js +0 -119
  455. package/dist/engines/pi/session-store.d.ts +0 -69
  456. package/dist/engines/pi/session-store.js +0 -430
  457. package/dist/engines/pi/tool-context.d.ts +0 -45
  458. package/dist/engines/pi/tool-context.js +0 -31
  459. package/dist/engines/pi/tool.d.ts +0 -99
  460. package/dist/engines/pi/tool.js +0 -118
  461. package/dist/engines/pi/turn-kit.d.ts +0 -92
  462. package/dist/engines/pi/turn-kit.js +0 -288
  463. package/dist/engines/pi/wake-tool.d.ts +0 -9
  464. package/dist/engines/pi/wake-tool.js +0 -92
  465. package/dist/loader.d.ts +0 -41
  466. package/dist/loader.js +0 -116
  467. package/dist/node.d.ts +0 -3
  468. package/dist/node.js +0 -4
  469. package/dist/observe.d.ts +0 -6
  470. package/dist/observe.js +0 -47
  471. package/dist/pi.d.ts +0 -25
  472. package/dist/pi.js +0 -15
  473. package/dist/scaffold/templates/APPEND_SYSTEM.md +0 -12
  474. package/dist/scaffold/templates/tools/fetch-url.ts +0 -25
  475. package/dist/schedule/discover.d.ts +0 -17
  476. package/dist/schedule/discover.js +0 -61
  477. package/dist/schedule/routine.d.ts +0 -43
  478. package/dist/schedule/routine.js +0 -32
  479. package/dist/schedule/run.d.ts +0 -55
  480. package/dist/schedule/run.js +0 -146
  481. package/dist/secrets-gate.d.ts +0 -32
  482. package/dist/secrets-gate.js +0 -33
  483. package/dist/session-remote.d.ts +0 -47
  484. package/dist/session-remote.js +0 -460
  485. package/dist/session.d.ts +0 -466
  486. package/dist/session.js +0 -57
  487. /package/dist/{channels/kit/state.d.ts → state-file.d.ts} +0 -0
@@ -0,0 +1,199 @@
1
+ /**
2
+ * A Feishu/Lark place's history, read from `im/v1/messages` when a turn runs (docs/design/place-history.md). What a
3
+ * place remembers between turns, and what a read leaves out, is the kit's (`createPlaceHistory`); this is the read:
4
+ * newest first up to the turn's own ask (`end_time` in seconds, then `(create_time, message_id)`), back to the cursor,
5
+ * with senders named from the chat's members.
6
+ */
7
+ import { log } from "../../log.js";
8
+ import { capBufferedRefs } from "../kit/context-buffer.js";
9
+ import { createPlaceHistory, } from "../kit/place-history.js";
10
+ import { threadList } from "../kit/room-threads.js";
11
+ import { CONTEXT_READ } from "../kit/transport.js";
12
+ import { decodeFeishuContent } from "./normalize.js";
13
+ /** How many of a place's newest messages one read lists; a place busier than this since its last answer says so. */
14
+ const PAGE_SIZE = 50;
15
+ /** What a place's first answered turn reads back, with no cursor yet. */
16
+ const FIRST_READ = 20;
17
+ /** How long a chat's member names are reused before they are read again. */
18
+ const NAMES_TTL_MS = 10 * 60_000;
19
+ /** Someone said it: not deleted, and not the platform's own (a system message has no sender). */
20
+ function isSpoken(item) {
21
+ return item.deleted !== true && item.msg_type !== "system" && Boolean(item.sender?.sender_type);
22
+ }
23
+ /** A place: the chat, or a thread within it. The turn record carries it as this key. */
24
+ export function feishuHistoryKey(place) {
25
+ return place.threadId ? `${place.chatId}:thread:${place.threadId}` : place.chatId;
26
+ }
27
+ /** The inverse of {@link feishuHistoryKey}. */
28
+ export function feishuPlaceOf(key) {
29
+ const at = key.indexOf(":thread:");
30
+ return at === -1 ? { chatId: key } : { chatId: key.slice(0, at), threadId: key.slice(at + ":thread:".length) };
31
+ }
32
+ /** Select the most recent background resources of the folded messages, excluding the turn's own primary ones. */
33
+ export function collectFoldedAttachments(folded, primary) {
34
+ const identity = (ref) => `${ref.messageId}\u0000${ref.key}`;
35
+ const refs = (pick, primaryRefs) => {
36
+ const seen = new Set(primaryRefs.map(identity));
37
+ const out = [];
38
+ for (const message of folded) {
39
+ for (const resource of pick(message)) {
40
+ const ref = { messageId: message.id, ...resource, from: message.from.label };
41
+ if (seen.has(identity(ref)))
42
+ continue;
43
+ seen.add(identity(ref));
44
+ out.push(ref);
45
+ }
46
+ }
47
+ return out;
48
+ };
49
+ const files = capBufferedRefs(refs((message) => message.files, primary.files));
50
+ const images = capBufferedRefs(refs((message) => message.images, primary.images));
51
+ return { files: files.kept, images: images.kept, skipped: files.skipped + images.skipped };
52
+ }
53
+ export function createFeishuPlaceHistory(deps) {
54
+ const { api, appId, label } = deps;
55
+ /**
56
+ * Per chat, the speakers already looked up: named, or known to have no name to find (not a member, past the page
57
+ * cap, or a failed read). Both halves expire together, so a failure or a newcomer is retried after the TTL, not on
58
+ * every turn.
59
+ */
60
+ const names = new Map();
61
+ /** Names for the speakers a fold shows; one not found is shown by open_id. Never rejects: a name is a label. */
62
+ const memberNames = async (chatId, speakers) => {
63
+ const cached = names.get(chatId);
64
+ const entry = cached && Date.now() - cached.at < NAMES_TTL_MS
65
+ ? cached
66
+ : { named: new Map(), unnamed: new Set(), at: Date.now() };
67
+ names.set(chatId, entry);
68
+ const unknown = new Set([...speakers].filter((id) => !entry.named.has(id) && !entry.unnamed.has(id)));
69
+ if (unknown.size === 0)
70
+ return entry.named;
71
+ try {
72
+ const { names: found, complete } = await api.chatMemberNames(chatId, unknown, CONTEXT_READ);
73
+ for (const [id, name] of found)
74
+ entry.named.set(id, name);
75
+ const missing = [...unknown].filter((id) => !found.has(id));
76
+ for (const id of missing)
77
+ entry.unnamed.add(id);
78
+ // A complete list without them is ordinary (they left the chat); a list cut at the page cap is a gap to say.
79
+ if (!complete && missing.length > 0) {
80
+ log.warn(`${label} chat ${chatId} has more members than one name read covers; ${missing.length} speaker(s) are shown by open_id`);
81
+ }
82
+ }
83
+ catch (error) {
84
+ for (const id of unknown)
85
+ entry.unnamed.add(id);
86
+ log.warn(`${label} could not read chat ${chatId}'s member names (speakers are shown by id): ${String(error)}`);
87
+ }
88
+ return entry.named;
89
+ };
90
+ /** One listed message as a fold shows it; `people` names the humans among its speakers. */
91
+ const placeMessage = (item, id, at, people) => {
92
+ const decoded = decodeFeishuContent({
93
+ message_type: item.msg_type ?? "unknown",
94
+ content: item.body?.content ?? "",
95
+ // The list API names a mention's id as a bare string; the decoder needs only key and name.
96
+ mentions: item.mentions?.flatMap((mention) => (mention.key ? [{ key: mention.key, name: mention.name }] : [])),
97
+ });
98
+ const senderId = item.sender?.id ?? "unknown";
99
+ const from = item.sender?.sender_type === "app"
100
+ ? senderId === appId
101
+ ? { kind: "self", label: "you" }
102
+ : { kind: "bot", label: `bot ${senderId}` }
103
+ : { kind: "human", label: people.get(senderId) ?? `user ${senderId}` };
104
+ return {
105
+ id,
106
+ at,
107
+ from,
108
+ text: decoded.text,
109
+ ...(item.parent_id ? { replyTo: item.parent_id } : {}),
110
+ images: decoded.resources.filter((r) => r.kind === "image").map((r) => ({ key: r.key })),
111
+ files: decoded.resources
112
+ .filter((r) => r.kind === "file" || r.kind === "audio" || r.kind === "video")
113
+ .map((r) => ({ key: r.key, ...(r.name ? { name: r.name } : {}) })),
114
+ };
115
+ };
116
+ /** The place's messages since its cursor (or its newest {@link FIRST_READ}), oldest first, minus the session's own. */
117
+ const read = async (key, { cursor, drop }, until) => {
118
+ const place = feishuPlaceOf(key);
119
+ const listed = await api.listMessages(place.threadId ? { type: "thread", id: place.threadId } : { type: "chat", id: place.chatId }, PAGE_SIZE,
120
+ // Seconds, inclusive: the same second's later messages still arrive, and are skipped below by time.
121
+ until ? Math.floor(until.at / 1000) : undefined, CONTEXT_READ);
122
+ // A thread id names a thread in ANY chat the bot is in: its key's chat is a claim this read checks.
123
+ const stranger = place.threadId ? listed.items.find((item) => item.chat_id !== place.chatId) : undefined;
124
+ if (stranger)
125
+ throw new Error(`thread ${place.threadId} is not in chat ${place.chatId}`);
126
+ const fresh = [];
127
+ let reachedCursor = false;
128
+ for (const item of listed.items) {
129
+ const id = item.message_id;
130
+ const at = Number(item.create_time);
131
+ if (!id || !Number.isFinite(at))
132
+ continue;
133
+ if (until && (id === until.id || at > until.at))
134
+ continue;
135
+ if (cursor && (id === cursor.id || at < cursor.at)) {
136
+ reachedCursor = true;
137
+ break;
138
+ }
139
+ fresh.push({ item, id, at });
140
+ if (!cursor && fresh.length >= FIRST_READ)
141
+ break;
142
+ }
143
+ const newest = fresh[0] ? { at: fresh[0].at, id: fresh[0].id } : undefined;
144
+ const earlier = cursor
145
+ ? !reachedCursor && (listed.hasMore || listed.items.length >= PAGE_SIZE)
146
+ : fresh.length >= FIRST_READ && (listed.items.length > FIRST_READ || listed.hasMore);
147
+ const covered = fresh.map(({ id }) => id);
148
+ const kept = fresh.reverse().filter(({ item, id }) => {
149
+ if (!isSpoken(item))
150
+ return false;
151
+ // A topic group lists every topic's replies with its chat; the room is its top-level messages.
152
+ if (!place.threadId && item.thread_id && item.root_id)
153
+ return false;
154
+ return !drop(id);
155
+ });
156
+ const speakers = new Set(kept.flatMap(({ item }) => (item.sender?.sender_type === "user" && item.sender.id ? [item.sender.id] : [])));
157
+ const people = speakers.size > 0 ? await memberNames(place.chatId, speakers) : new Map();
158
+ const messages = kept.map(({ item, id, at }) => placeMessage(item, id, at, people));
159
+ return { covered, messages, ...(newest ? { newest } : {}), earlier };
160
+ };
161
+ /**
162
+ * The chat's threads among its newest {@link PAGE_SIZE} messages. Feishu has no thread list: an ordinary group lists
163
+ * each thread's root (no reply count, no last activity), a topic group every message with its thread's id.
164
+ */
165
+ const threads = async (chatId) => {
166
+ const listed = await api.listMessages({ type: "chat", id: chatId }, PAGE_SIZE, undefined, CONTEXT_READ);
167
+ // Newest first, so a thread's first entry is its newest message seen and its last the earliest.
168
+ const byThread = new Map();
169
+ for (const item of listed.items) {
170
+ const at = Number(item.create_time);
171
+ if (!item.thread_id || !item.message_id || !isSpoken(item) || !Number.isFinite(at))
172
+ continue;
173
+ byThread.set(item.thread_id, [...(byThread.get(item.thread_id) ?? []), { item, id: item.message_id, at }]);
174
+ }
175
+ const openers = [...byThread.values()].map((seen) => seen.at(-1));
176
+ const speakers = new Set(openers.flatMap(({ item }) => (item.sender?.sender_type === "user" && item.sender.id ? [item.sender.id] : [])));
177
+ const people = speakers.size > 0 ? await memberNames(chatId, speakers) : new Map();
178
+ const summaries = [...byThread].map(([id, seen]) => {
179
+ const opener = seen.at(-1);
180
+ const message = placeMessage(opener.item, opener.id, opener.at, people);
181
+ // A reply is the thread's last activity this read saw; a root alone may have replies the listing omits.
182
+ const newest = seen[0];
183
+ const time = newest.item.root_id
184
+ ? { at: newest.at, is: "last active" }
185
+ : { at: opener.at, is: "started" };
186
+ return { id, time, first: { label: message.from.label, text: message.text } };
187
+ });
188
+ return threadList(summaries, `this chat's newest ${PAGE_SIZE} messages`);
189
+ };
190
+ const history = createPlaceHistory({
191
+ label,
192
+ path: deps.path,
193
+ isCursor: (value) => typeof value?.at === "number" && typeof value.id === "string",
194
+ compare: (a, b) => a.at - b.at,
195
+ isTurnInput: (_key, id) => deps.isTurnInput(id),
196
+ read,
197
+ });
198
+ return { ...history, threads };
199
+ }
@@ -12,11 +12,11 @@
12
12
  * ancestors, and degrade per attachment: one expired background file must not block the current ask
13
13
  * or hide its still-readable siblings.
14
14
  */
15
- import type { Agent, AgentEvent } from "../../agent.ts";
15
+ import type { Agent } from "../../agent-api.ts";
16
16
  import type * as Stream from "effect/Stream";
17
17
  import type { PortFailure } from "../../effect-port.ts";
18
- import { type BusyRetry } from "../kit/invoke-turn-kit.ts";
19
- import type { FeishuBufferedRef } from "./context-buffer.ts";
18
+ import { type TurnEvent } from "../kit/invoke-turn-kit.ts";
19
+ import type { FeishuBufferedRef } from "./history.ts";
20
20
  import type { FeishuApi } from "./feishu-api.ts";
21
21
  /** Everything the transport needs to fetch a turn's attachments. */
22
22
  export interface FeishuTurnTransport {
@@ -28,11 +28,6 @@ export interface FeishuTurnTransport {
28
28
  * sender is an app. Needed to tell the agent's OWN messages from any other bot's in the same chat:
29
29
  * `sender_type` alone says "some app", which is not the question the referent path asks. */
30
30
  appId: string;
31
- /** The place this thread branched from (the chat's main place), when the turn runs in a thread —
32
- * rides the Scope's lineage extension so a NEW thread session starts from what the room knew
33
- * (participant-model.md §5). The engine reads it once, at session creation; every later turn
34
- * carries it inertly. */
35
- parentSession?: string;
36
31
  }
37
32
  /** An attachment reference: the resource key inside its CARRYING message (the resource API addresses
38
33
  * bytes by message_id + key, so the pair travels together through the turn record). */
@@ -56,8 +51,15 @@ export interface FeishuTurnAttachments {
56
51
  };
57
52
  }
58
53
  /**
59
- * Run one turn: resolve its inputs, then ask the agent (invoke-turn-kit — a primary-input failure
60
- * surfaces as a `failed` event, never a silent drop).
54
+ * Run one turn: resolve its inputs, then ask the agent (invoke-turn-kit — a preparation failure surfaces as a
55
+ * `rejected` event, never a silent drop). `key` is the turn's: the message it answers, which is also how a later
56
+ * thread finds the message this one becomes. `session` gets the reply chain's ids, where a new thread forks from.
61
57
  */
62
- export declare function feishuTurnStream(agent: Agent, session: string, text: string, transport: FeishuTurnTransport, attachments: FeishuTurnAttachments, busyRetry?: BusyRetry): Stream.Stream<AgentEvent, PortFailure>;
58
+ export declare function feishuTurnStream(agent: Agent, turn: {
59
+ session: (referentIds: readonly string[]) => Promise<string>;
60
+ /** While it runs, the room its thread-reading tool reads (a group turn's chat). */
61
+ enter?: (sessionId: string) => () => void;
62
+ key: string;
63
+ text: string;
64
+ }, transport: FeishuTurnTransport, attachments: FeishuTurnAttachments): Stream.Stream<TurnEvent, PortFailure>;
63
65
  export {};
@@ -1,5 +1,5 @@
1
1
  import { log } from "../../log.js";
2
- import { DEFAULT_BUSY_RETRY, attachedFilesManifest, attributedFileName, backgroundImagesManifest, loadBackground, missingAttachmentsNote, turnStream, } from "../kit/invoke-turn-kit.js";
2
+ import { attachedFilesManifest, attributedFileName, backgroundImagesManifest, loadBackground, missingAttachmentsNote, turnStream, promptContent, } from "../kit/invoke-turn-kit.js";
3
3
  import { BUFFER_ATTACH_MAX } from "../kit/context-buffer.js";
4
4
  import { parseContent } from "./parse.js";
5
5
  import { REFERENT_MAX_CODE_POINTS, truncateCodePointPrefix } from "../kit/text.js";
@@ -226,21 +226,20 @@ async function resolveTurnInputs(t, attachments) {
226
226
  };
227
227
  }
228
228
  /**
229
- * Run one turn: resolve its inputs, then ask the agent (invoke-turn-kit — a primary-input failure
230
- * surfaces as a `failed` event, never a silent drop).
229
+ * Run one turn: resolve its inputs, then ask the agent (invoke-turn-kit — a preparation failure surfaces as a
230
+ * `rejected` event, never a silent drop). `key` is the turn's: the message it answers, which is also how a later
231
+ * thread finds the message this one becomes. `session` gets the reply chain's ids, where a new thread forks from.
231
232
  */
232
- export function feishuTurnStream(agent, session, text, transport, attachments, busyRetry = DEFAULT_BUSY_RETRY) {
233
+ export function feishuTurnStream(agent, turn, transport, attachments) {
233
234
  return turnStream({
234
235
  agent,
235
236
  label: transport.label,
236
- busyRetry,
237
+ session: (resolved) => turn.session(resolved.referentIds),
238
+ ...(turn.enter === undefined ? {} : { enter: turn.enter }),
237
239
  resolve: () => resolveTurnInputs(transport, attachments),
238
240
  turn: (resolved) => ({
239
- // Lineage is resolved per turn; the engine reads it only when creating a new session.
240
- scope: (transport.parentSession === undefined
241
- ? { session }
242
- : { session, parentSession: transport.parentSession, branchHints: resolved.referentIds }),
243
- prompt: { text: `${text}${resolved.promptSuffix}${REPLY_INSTRUCTION}`, images: resolved.images },
241
+ idempotencyKey: turn.key,
242
+ content: promptContent(`${turn.text}${resolved.promptSuffix}${REPLY_INSTRUCTION}`, resolved.images),
244
243
  }),
245
244
  // A resource fetch that failed may simply succeed on the redelivery.
246
245
  retryableLoadFailure: () => true,
@@ -54,7 +54,8 @@ export interface FeishuMessageEvent {
54
54
  }
55
55
  /** Existing public route result: act with these overrides, or return null to ignore the message. */
56
56
  export interface FeishuRoute {
57
- session?: string;
57
+ /** Which conversation it belongs to, the one its session is kept by (default: the chat, or the thread). */
58
+ conversation?: string;
58
59
  chatId?: string;
59
60
  text?: string;
60
61
  }
@@ -45,6 +45,95 @@ function renderNodes(nodes, resources) {
45
45
  }
46
46
  return parts.join("").trim();
47
47
  }
48
+ function isRecord(value) {
49
+ return typeof value === "object" && value !== null && !Array.isArray(value);
50
+ }
51
+ /** Text components of a card as sent, whose `content` is what a reader sees. */
52
+ const CARD_TEXT_TAGS = new Set(["markdown", "plain_text", "lark_md"]);
53
+ /**
54
+ * Whether a card body is the card as SENT (`card_msg_content_type=user_card_content`): a Card 2.0 has `body`, a card
55
+ * 1.0 has a `header` object, component objects in `elements`, or per-locale `i18n_elements`. The rendered-down shape
56
+ * is a string `title` and `elements` of paragraph ARRAYS.
57
+ */
58
+ function isSentCard(card) {
59
+ return (isRecord(card.body) ||
60
+ isRecord(card.header) ||
61
+ isRecord(card.i18n_elements) ||
62
+ (Array.isArray(card.elements) && card.elements.some((element) => isRecord(element))));
63
+ }
64
+ /** A card's declared `config.locales`, in order: the locales a multi-language card is read in, first carried wins. */
65
+ function declaredLocales(card) {
66
+ const declared = isRecord(card.config) && Array.isArray(card.config.locales) ? card.config.locales : [];
67
+ return declared.filter((locale) => typeof locale === "string");
68
+ }
69
+ /**
70
+ * One locale's form out of per-locale `forms`: the first declared locale it carries, else the first it carries. A card
71
+ * holds the same content once per locale; one of them is the content.
72
+ */
73
+ function pickLocale(forms, locales) {
74
+ const declared = locales.find((locale) => locale in forms);
75
+ return declared !== undefined ? forms[declared] : Object.values(forms)[0];
76
+ }
77
+ /** A text field's value: `content`, else its per-locale form (1.0 `i18n`, 2.0 `i18n_content`). */
78
+ function localizedText(node, locales) {
79
+ const own = nonEmptyString(node.content)?.trim();
80
+ if (own)
81
+ return own;
82
+ for (const key of ["i18n", "i18n_content"]) {
83
+ const forms = node[key];
84
+ if (!isRecord(forms))
85
+ continue;
86
+ const text = nonEmptyString(pickLocale(forms, locales))?.trim();
87
+ if (text)
88
+ return text;
89
+ }
90
+ return undefined;
91
+ }
92
+ /**
93
+ * A sent card's visible text, in reading order: the header title, then every text component, through the containers
94
+ * (columns, panels, forms, a 1.0 `div`'s fields and `extra`, an action row) that nest them, with a control's label or
95
+ * placeholder. An image or a table says it is there; controls with no text say nothing. Card 2.0 keeps its components in `body.elements`, card 1.0 in `elements`.
96
+ */
97
+ function sentCardLines(card) {
98
+ const lines = [];
99
+ const locales = declaredLocales(card);
100
+ const titleOf = (header) => isRecord(header) && isRecord(header.title) ? localizedText(header.title, locales) : undefined;
101
+ const title = titleOf(card.header);
102
+ if (title)
103
+ lines.push(title);
104
+ const visit = (node) => {
105
+ if (Array.isArray(node)) {
106
+ for (const child of node)
107
+ visit(child);
108
+ return;
109
+ }
110
+ if (!isRecord(node))
111
+ return;
112
+ // A collapsible panel's title is the one line it shows folded.
113
+ const panelTitle = titleOf(node.header);
114
+ if (panelTitle)
115
+ lines.push(panelTitle);
116
+ const content = localizedText(node, locales);
117
+ if (typeof node.tag === "string" && CARD_TEXT_TAGS.has(node.tag) && content)
118
+ lines.push(content);
119
+ else if (node.tag === "img")
120
+ lines.push("[image]");
121
+ // Rows of typed cells, not text components: said, so the rest of the card does not read as the whole of it.
122
+ else if (node.tag === "table")
123
+ lines.push("[table]");
124
+ // A control's visible words: a button's or a div's `text`, a picker's `placeholder`.
125
+ for (const key of ["text", "placeholder"]) {
126
+ const label = isRecord(node[key]) ? localizedText(node[key], locales) : undefined;
127
+ if (label)
128
+ lines.push(label);
129
+ }
130
+ for (const key of ["elements", "columns", "fields", "actions", "extra"])
131
+ visit(node[key]);
132
+ };
133
+ const i18nElements = isRecord(card.i18n_elements) ? pickLocale(card.i18n_elements, locales) : undefined;
134
+ visit(isRecord(card.body) ? card.body.elements : (card.elements ?? i18nElements));
135
+ return lines;
136
+ }
48
137
  function nonEmptyString(value) {
49
138
  return typeof value === "string" && value !== "" ? value : undefined;
50
139
  }
@@ -90,6 +179,17 @@ export function decodeFeishuContent(message) {
90
179
  // A CARD, as the platform hands it BACK.
91
180
  case "interactive":
92
181
  case "card": {
182
+ // As SENT (`card_msg_content_type=user_card_content`, what every read asks for): a Card 2.0 (every answer this
183
+ // channel streams) or a card 1.0 (what many CI and alert bots post).
184
+ // A card sent from a template is, as sent, a reference (`template_id` + variables), and its text was never in
185
+ // the message. Said in the prompt, so the model does not read the marker as an empty card.
186
+ if (content.type === "template") {
187
+ return { text: `[${rawType} message: a template card; its text cannot be read]`, resources };
188
+ }
189
+ if (isSentCard(content)) {
190
+ const lines = sentCardLines(content);
191
+ return { text: lines.length > 0 ? lines.join("\n") : `[${rawType} message]`, resources };
192
+ }
93
193
  const lines = [];
94
194
  const title = nonEmptyString(content.title);
95
195
  if (title)
@@ -1,4 +1,4 @@
1
- import type { AgentEvent } from "../../agent.ts";
1
+ import type { TurnEvent } from "../kit/invoke-turn-kit.ts";
2
2
  import * as Effect from "effect/Effect";
3
3
  import type * as Stream from "effect/Stream";
4
4
  import { type PortFailure } from "../../effect-port.ts";
@@ -29,4 +29,4 @@ export declare function settleFeishuPreview(api: FeishuApi, target: FeishuTarget
29
29
  /**
30
30
  * Consume one turn's event stream into a Feishu-compatible chat, live.
31
31
  */
32
- export declare function feishuReply(events: Stream.Stream<AgentEvent, PortFailure>, api: FeishuApi, target: FeishuTarget, formatError: (failed: FeishuFailure) => string | undefined, initialPreview?: MountedFeishuPreview, label?: string, onAnswered?: (answer: string) => void): Effect.Effect<void, PortFailure, import("effect/Scope").Scope>;
32
+ export declare function feishuReply(events: Stream.Stream<TurnEvent, PortFailure>, api: FeishuApi, target: FeishuTarget, formatError: (failed: FeishuFailure) => string | undefined, initialPreview?: MountedFeishuPreview, label?: string, onAnswered?: (answer: string) => void): Effect.Effect<void, PortFailure, import("effect/Scope").Scope>;
@@ -6,6 +6,7 @@ interface FeishuManualRegistration {
6
6
  requestUrl: string;
7
7
  }
8
8
  export interface RegisterFeishuWebhookOptions {
9
+ env?: NodeJS.ProcessEnv;
9
10
  attempts?: number;
10
11
  retryMs?: number;
11
12
  apiBase?: string;
@@ -9,8 +9,9 @@ import { createFeishuApi, isFeishuConfigApiMissing, isTransientFeishuRegistratio
9
9
  export async function registerFeishuWebhook(baseUrl, kind, opts = {}) {
10
10
  const profile = cloudFor(kind);
11
11
  const envPrefix = profile.envPrefix;
12
- const appId = process.env[`${envPrefix}_APP_ID`];
13
- const appSecret = process.env[`${envPrefix}_APP_SECRET`];
12
+ const env = opts.env ?? process.env;
13
+ const appId = env[`${envPrefix}_APP_ID`];
14
+ const appSecret = env[`${envPrefix}_APP_SECRET`];
14
15
  const apiBase = opts.apiBase ?? profile.apiBase;
15
16
  const requestUrl = `${baseUrl}/${kind}`;
16
17
  const manual = `switch Subscription mode to webhook and set the event Request URL in the developer console (Events & Callbacks) to ${requestUrl} — keep the server running while you save (the console verifies the URL with a challenge)`;
@@ -1,50 +1,69 @@
1
- import { feishuChannel } from "@fastagent-sh/fastagent/feishu";
2
- import { defineChannel } from "@fastagent-sh/fastagent";
1
+ import { needs } from "@fastagent-sh/fastagent";
2
+ import {
3
+ type FeishuChannelOptions,
4
+ feishuChannel,
5
+ feishuIngress,
6
+ feishuWebSocketChannel,
7
+ } from "@fastagent-sh/fastagent/feishu";
3
8
 
4
- // feishuChannel is fastagent's canonical Feishu adapter (verify + run + reply), configured with YOUR
5
- // policy. fastagent discovers this file, mounts POST /feishu, and pipes the agent + state home to it.
6
- // Feishu (open.feishu.cn) only; a Lark-international
7
- // tenant uses `fastagent add lark` instead. Setup (developer console):
9
+ // fastagent's canonical Feishu adapter (verify + run + reply), configured with YOUR policy. Feishu
10
+ // (open.feishu.cn) only; a Lark-international tenant uses `fastagent add lark` instead.
11
+ //
12
+ // How it receives is FEISHU_INGRESS (webhook | websocket). Unset, `fastagent dev` connects OUT by
13
+ // WebSocket (no public URL, no tunnel), and everything else — `fastagent start`, every deployment —
14
+ // receives by webhook at POST /feishu. Dev reads .secrets/.env; production reads
15
+ // .secrets/production/.env. Interactive `deploy --run` creates a separate production app.
16
+ //
17
+ // `fastagent add feishu` creates and configures the app (scan-to-create). For a hand-made app, in the
18
+ // developer console:
8
19
  // 1. create a custom app → enable the BOT capability → copy App ID / App Secret into .env
9
20
  // 2. Permissions: add `im:message.p2p_msg:readonly` (direct messages), `im:message.group_at_msg:readonly`
10
21
  // (group @mentions), `im:message:send_as_bot` (reply), `im:resource` (attachments), and the
11
- // card scope ("Create and update card" — the live preview streams through a card). To answer bare
12
- // messages in threads the Agent takes part in, and buffer other unsummoned group/thread context, also add the
13
- // sensitive `im:message.group_msg` scope (tenant-admin approval) and publish a new version. Add a
14
- // message-read scope (e.g. `im:message:readonly`) too, so a thread's opening ask can carry the
15
- // message it quotes; without it that quote degrades to a marker in the prompt.
16
- // 3. Events & Callbacks → subscribe to `im.message.receive_v1`; copy the Verification Token into
17
- // .env; RECOMMENDED: set an Encrypt Key there and mirror it in FEISHU_ENCRYPT_KEY
18
- // 4. the event Request URL (https://your.host/feishu) is registered AUTOMATICALLY by
19
- // `fastagent dev --tunnel` and `fastagent deploy … --run`; to set it by hand in the console,
20
- // keep the server running while you save (the platform verifies the URL with a challenge)
21
- // 5. create a version and publish the app (a Feishu admin approves it), then add the bot to a chat
22
- // `fastagent add feishu` already did steps 1-3 for you (scan-to-create); this walkthrough is for a
23
- // hand-made app or for auditing what the scan configured.
24
- export default defineChannel({
25
- // The env vars this channel needs. fastagent carries them to a deployed box and refuses to serve
26
- // while one is unset — a bare process.env read gets neither guarantee. FEISHU_ENCRYPT_KEY is
27
- // optional, so it is NOT declared here: an undeclared read stays undeclared on purpose.
28
- secrets: ["FEISHU_APP_ID", "FEISHU_APP_SECRET", "FEISHU_VERIFICATION_TOKEN"],
29
- channel: (secrets) =>
30
- feishuChannel({
31
- appId: secrets.FEISHU_APP_ID,
32
- appSecret: secrets.FEISHU_APP_SECRET,
33
- verificationToken: secrets.FEISHU_VERIFICATION_TOKEN, // authenticates inbound events
34
- encryptKey: process.env.FEISHU_ENCRYPT_KEY || undefined, // optional; when set, plaintext events are refused
35
- // No session modes: a chat is one session and a thread is another, and where the answer goes follows
36
- // from that (docs/design/participant-model.md).
37
- // Dev/personal bot: surface raw errors to the chat so you (and your AI agent) can act on them. The
38
- // chat is customer-facing by default — for a public bot, drop this or return a neutral string;
39
- // full details always go to the server log regardless.
40
- onError: (failed) => `⚠️ ${failed.details}`,
41
- // The channel owns transport + format (markdown card) + attachments (image→vision, file→disk) +
42
- // the live streaming preview. `route` (POLICY) is OPTIONAL — omitted, it uses defaultFeishuRoute:
43
- // p2p chats always answer; groups answer on @this-bot, plus bare messages in a thread where the
44
- // Agent takes part and exactly ONE human does. Other human group/thread discussion buffers until
45
- // that place's next answered turn; @other-only messages buffer rather than triggering the Agent.
46
- // Override to customise explicit routing, reusing the export:
47
- // route: (e) => defaultFeishuRoute(e, { botOpenId: "ou_xxx" }) && { session: `user:${e.sender?.sender_id?.open_id}` },
48
- // route: (e) => defaultFeishuRoute(e, { botOpenId: "ou_xxx" }) && { text: `${feishuEnvelope(e)}\n[extra]` },
49
- }),
50
- });
22
+ // card scope ("Create and update card" — the live preview streams through a card). So the Agent
23
+ // hears its group chats, also add `im:message.group_msg` (sensitive), `im:message:readonly` and
24
+ // `im:chat.members:read`; for webhook, `application:application:patch` lets fastagent set the
25
+ // Request URL. What each is for is in the channel guide; the channel warns about any it lacks.
26
+ // 3. Events & Callbacks → subscribe to `im.message.receive_v1`. For webhook, copy the Verification
27
+ // Token into .env (RECOMMENDED: set an Encrypt Key there and mirror it in FEISHU_ENCRYPT_KEY).
28
+ // 4. create a version and publish the app (a Feishu admin approves it), then add the bot to a chat
29
+
30
+ // Your policy, the same whichever way the channel receives.
31
+ const policy: Pick<FeishuChannelOptions, "onError" | "route"> = {
32
+ // A chat is one conversation and a thread is another, each with a session of its own, and where the
33
+ // answer goes follows from that (docs/design/participant-model.md).
34
+ // Dev/personal bot: surface raw errors to the chat so you (and your AI agent) can act on them. The
35
+ // chat is customer-facing by default — for a public bot, drop this or return a neutral string;
36
+ // full details always go to the server log regardless.
37
+ onError: (failed) => `⚠️ ${failed.details}`,
38
+ // The channel owns transport + format (markdown card) + attachments (image→vision, file→disk) +
39
+ // the live streaming preview. `route` (POLICY) is OPTIONAL — omitted, it uses defaultFeishuRoute:
40
+ // p2p chats always answer; groups answer on @this-bot, plus bare messages in a thread where the
41
+ // Agent takes part and exactly ONE human does. Other group/thread discussion is read from the
42
+ // platform by that place's next answered turn; @other-only messages are discussion, not asks.
43
+ // Override to customise explicit routing, reusing the export:
44
+ // route: (e) => defaultFeishuRoute(e, { botOpenId: "ou_xxx" }) && { conversation: `user:${e.sender?.sender_id?.open_id}` },
45
+ // route: (e) => defaultFeishuRoute(e, { botOpenId: "ou_xxx" }) && { text: `${feishuEnvelope(e)}\n[extra]` },
46
+ };
47
+
48
+ // `needs` declares the env vars this channel reads: fastagent carries them to a deployed box and
49
+ // refuses to serve while a required one is unset. FEISHU_ENCRYPT_KEY is optional.
50
+ export default feishuIngress() === "webhook"
51
+ ? needs(
52
+ [
53
+ "FEISHU_APP_ID",
54
+ "FEISHU_APP_SECRET",
55
+ "FEISHU_VERIFICATION_TOKEN",
56
+ { name: "FEISHU_ENCRYPT_KEY", description: "when set, plaintext events are refused", optional: true },
57
+ ],
58
+ (secrets) =>
59
+ feishuChannel({
60
+ appId: secrets.FEISHU_APP_ID,
61
+ appSecret: secrets.FEISHU_APP_SECRET,
62
+ verificationToken: secrets.FEISHU_VERIFICATION_TOKEN, // authenticates inbound events
63
+ encryptKey: secrets.FEISHU_ENCRYPT_KEY,
64
+ ...policy,
65
+ }),
66
+ )
67
+ : needs(["FEISHU_APP_ID", "FEISHU_APP_SECRET"], (secrets) =>
68
+ feishuWebSocketChannel({ appId: secrets.FEISHU_APP_ID, appSecret: secrets.FEISHU_APP_SECRET, ...policy }),
69
+ );