@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
@@ -0,0 +1,35 @@
1
+ import * as Effect from "effect/Effect";
2
+ import type * as Scope from "effect/Scope";
3
+ import * as Stream from "effect/Stream";
4
+ import type { AgentEvent } from "../../agent.ts";
5
+ import type { ChannelFailure } from "./preview-kit.ts";
6
+ import { PortFailure } from "../../effect-port.ts";
7
+ /** Snapshot/edit renderers share terminal ownership; platform-specific settle policies stay injected. */
8
+ export declare function renderReply(events: Stream.Stream<AgentEvent, PortFailure>, opts: {
9
+ label: string;
10
+ onEvent: (event: AgentEvent) => void;
11
+ finish: Effect.Effect<void>;
12
+ answer: () => string;
13
+ settle: (text: string) => Effect.Effect<void, PortFailure>;
14
+ formatError: (failure: ChannelFailure) => string | undefined;
15
+ /** The finished reply, handed over before it is delivered — where a channel records it for recovery. */
16
+ onAnswered?: (answer: string) => void;
17
+ }): Effect.Effect<void, PortFailure, Scope.Scope>;
18
+ export interface PreviewPump {
19
+ touch(): void;
20
+ /** Cancel pacing, join an issued write, and prevent any subsequent frame. */
21
+ finish: Effect.Effect<void>;
22
+ }
23
+ export declare function previewPump(opts: {
24
+ flush: () => Promise<void>;
25
+ throttleMs: number;
26
+ /** Slack's mutation slot precedes a write; stopping the pump can discard this pending frame. */
27
+ beforeFlush?: Effect.Effect<void>;
28
+ onError: (error: unknown) => void;
29
+ }): Effect.Effect<PreviewPump, never, Scope.Scope>;
30
+ export interface SerialWriter {
31
+ enqueue(work: () => Promise<void>): void;
32
+ /** End admission and join every accepted operation, including its caller-owned failure policy. */
33
+ finish: Effect.Effect<void, PortFailure>;
34
+ }
35
+ export declare function serialWriter(): Effect.Effect<SerialWriter, never, Scope.Scope>;
@@ -0,0 +1,129 @@
1
+ /** Scoped outbound writers. Frames coalesce; native append/status operations retain every queued write. */
2
+ import * as Cause from "effect/Cause";
3
+ import * as Effect from "effect/Effect";
4
+ import * as Exit from "effect/Exit";
5
+ import * as Fiber from "effect/Fiber";
6
+ import * as Queue from "effect/Queue";
7
+ import * as Stream from "effect/Stream";
8
+ import { log } from "../../log.js";
9
+ import { PortFailure, portJoin } from "../../effect-port.js";
10
+ /** Snapshot/edit renderers share terminal ownership; platform-specific settle policies stay injected. */
11
+ export function renderReply(events, opts) {
12
+ return Effect.gen(function* () {
13
+ let finalized = false;
14
+ const notify = (failure, phase) => Effect.try({ try: () => opts.formatError(failure) ?? "", catch: (cause) => new PortFailure(cause) }).pipe(Effect.flatMap(opts.settle), Effect.catchTag("PortFailure", (error) => Effect.sync(() => log.error(`${opts.label} failed to deliver the ${phase} notice: ${String(error.cause)}`))));
15
+ yield* Effect.addFinalizer(() => Effect.gen(function* () {
16
+ yield* opts.finish;
17
+ if (!finalized)
18
+ yield* notify({ details: "the turn ended without completing", retryable: false }, "abnormal-turn");
19
+ }));
20
+ yield* Stream.runForEachWhile(events, (event) => {
21
+ if (event.type !== "completed" && event.type !== "failed") {
22
+ return Effect.try({
23
+ try: () => {
24
+ opts.onEvent(event);
25
+ return true;
26
+ },
27
+ catch: (cause) => new PortFailure(cause),
28
+ });
29
+ }
30
+ return Effect.gen(function* () {
31
+ yield* opts.finish;
32
+ finalized = true;
33
+ if (event.type === "completed") {
34
+ const answer = opts.answer();
35
+ yield* Effect.try({
36
+ try: () => opts.onAnswered?.(answer),
37
+ catch: (cause) => new PortFailure(cause),
38
+ });
39
+ yield* opts.settle(answer);
40
+ }
41
+ else {
42
+ yield* notify({
43
+ details: event.details,
44
+ retryable: event.retryable,
45
+ ...(event.code !== undefined ? { code: event.code } : {}),
46
+ }, "agent-failure");
47
+ return yield* Effect.fail(new PortFailure(new Error(`agent failed: ${event.details} (retryable=${event.retryable})`)));
48
+ }
49
+ return false;
50
+ }).pipe(Effect.uninterruptible);
51
+ });
52
+ if (!finalized)
53
+ yield* Effect.fail(new PortFailure(new Error("stream ended without a terminal event")));
54
+ });
55
+ }
56
+ export function previewPump(opts) {
57
+ return Effect.gen(function* () {
58
+ const scope = yield* Effect.scope;
59
+ const run = Effect.runSyncWith(yield* Effect.context());
60
+ let fiber;
61
+ let stopped = false;
62
+ let dirty = false;
63
+ let reported = false;
64
+ const work = Effect.gen(function* () {
65
+ while (dirty && !stopped) {
66
+ dirty = false;
67
+ if (opts.beforeFlush)
68
+ yield* opts.beforeFlush;
69
+ // An issued mutation may have reached the platform. Join it, including publishing its id
70
+ // and diagnosing its outcome, before the final writer takes over.
71
+ yield* portJoin(opts.flush).pipe(Effect.catchTag("PortFailure", (error) => Effect.sync(() => {
72
+ if (!reported) {
73
+ reported = true;
74
+ opts.onError(error.cause);
75
+ }
76
+ })), Effect.uninterruptible);
77
+ if (dirty && !stopped)
78
+ yield* Effect.sleep(opts.throttleMs);
79
+ }
80
+ });
81
+ const finish = Effect.gen(function* () {
82
+ stopped = true;
83
+ if (!fiber)
84
+ return;
85
+ yield* Fiber.interrupt(fiber);
86
+ const exit = yield* Fiber.await(fiber);
87
+ if (Exit.isFailure(exit) && !Cause.hasInterruptsOnly(exit.cause))
88
+ yield* Effect.failCause(exit.cause);
89
+ });
90
+ yield* Effect.addFinalizer(() => finish);
91
+ return {
92
+ touch: () => {
93
+ if (stopped)
94
+ return;
95
+ dirty = true;
96
+ const exit = fiber?.pollUnsafe();
97
+ if (fiber && (!exit || Exit.isFailure(exit)))
98
+ return;
99
+ // The first placeholder starts before a fast source can reach its terminal event.
100
+ fiber = run(Effect.forkIn(work, scope, { startImmediately: true }));
101
+ },
102
+ finish,
103
+ };
104
+ });
105
+ }
106
+ export function serialWriter() {
107
+ return Effect.gen(function* () {
108
+ const queue = yield* Queue.unbounded();
109
+ const work = Effect.gen(function* () {
110
+ for (;;) {
111
+ const next = yield* Queue.take(queue);
112
+ yield* portJoin(next).pipe(Effect.uninterruptible);
113
+ }
114
+ }).pipe(Effect.catchTag("Done", () => Effect.void));
115
+ const fiber = yield* Effect.forkScoped(work);
116
+ const finish = Effect.sync(() => {
117
+ Queue.endUnsafe(queue);
118
+ }).pipe(Effect.andThen(Fiber.join(fiber)));
119
+ // This finalizer precedes forkScoped's interruption: accepted append/status writes must drain.
120
+ yield* Effect.addFinalizer(() => finish.pipe(Effect.orDie));
121
+ return {
122
+ enqueue: (next) => {
123
+ if (!Queue.offerUnsafe(queue, next))
124
+ throw new Error("delivery writer is closed");
125
+ },
126
+ finish,
127
+ };
128
+ });
129
+ }
@@ -0,0 +1,5 @@
1
+ import type * as Scope from "effect/Scope";
2
+ import * as Stream from "effect/Stream";
3
+ import { PortFailure } from "../../effect-port.ts";
4
+ /** Acquire lazily; interruption closes the source, while natural exhaustion needs no extra return. */
5
+ export declare function eventStream<A>(open: () => AsyncIterable<A>, label: string): Stream.Stream<A, PortFailure, Scope.Scope>;
@@ -0,0 +1,29 @@
1
+ /** Own an Agent's AsyncIterable inside a typed, demand-driven channel stream. */
2
+ import * as Cause from "effect/Cause";
3
+ import * as Effect from "effect/Effect";
4
+ import * as Stream from "effect/Stream";
5
+ import { log } from "../../log.js";
6
+ import { PortFailure } from "../../effect-port.js";
7
+ /** Acquire lazily; interruption closes the source, while natural exhaustion needs no extra return. */
8
+ export function eventStream(open, label) {
9
+ return Stream.fromPull(Effect.gen(function* () {
10
+ let exhausted = false;
11
+ const iterator = yield* Effect.acquireRelease(Effect.try({ try: () => open()[Symbol.asyncIterator](), catch: (cause) => new PortFailure(cause) }), (iterator) => {
12
+ const close = iterator.return?.bind(iterator);
13
+ if (!close || exhausted)
14
+ return Effect.void;
15
+ return Effect.promise(close).pipe(Effect.onError((cause) => Effect.sync(() => log.warn(`${label} source cleanup failed: ${String(Cause.squash(cause))}`))));
16
+ });
17
+ return Effect.gen(function* () {
18
+ const result = yield* Effect.tryPromise({
19
+ try: () => iterator.next(),
20
+ catch: (cause) => new PortFailure(cause),
21
+ });
22
+ if (result.done) {
23
+ exhausted = true;
24
+ return yield* Cause.done();
25
+ }
26
+ return [result.value];
27
+ });
28
+ }));
29
+ }
@@ -1,20 +1,32 @@
1
+ import * as Stream from "effect/Stream";
2
+ import { type Agent, type AgentEvent, type Prompt, type Scope } from "../../agent.ts";
3
+ import { type PortFailure } from "../../effect-port.ts";
1
4
  /**
2
- * Shared pieces of the channels' invoke-turn modules (telegram/feishu/slack `invoke-turn.ts`) — the
3
- * halves that are channel-independent, so a retry-policy or prompt-wording change lands ONCE:
4
- *
5
- * - {@link streamTurnWithBusyRetry}: the busy-retry loop around `agent.invoke`, with the
6
- * `onCompleted` durable-commit point;
7
- * - the prompt-suffix wording: {@link attachedFilesManifest}, {@link backgroundImagesManifest},
8
- * {@link missingAttachmentsNote}, {@link attributedFileName}.
9
- *
10
- * None of it asks what the agent can DO with an attachment. A channel resolves platform resources and
11
- * states what it found; deciding whether to open a file is the agent's, and one assembled without a
12
- * file tool answers that it cannot — visibly, at the moment it is asked.
5
+ * One turn, end to end: resolve the platform inputs, then stream `agent.invoke` with the shared
6
+ * busy-wait. All three chat channels wrote this out, and the copies had already disagreed about the
7
+ * one thing that is actually a per-platform judgement (whether a load failure is worth a redelivery)
8
+ * while agreeing on everything that is not.
13
9
  *
14
- * Attachment RESOLUTION stays per channel — the platform resource models (Bot API file_ids,
15
- * message-scoped Feishu keys, Slack file objects) are real differences.
10
+ * A resolution failure becomes a `failed` EVENT. It must never throw out of the stream: the SPEC
11
+ * forbids it (MUST 2), and the turn's own settlement path is what tells the user — an exception here
12
+ * would take the reply with it.
16
13
  */
17
- import { type Agent, type AgentEvent, type Prompt, type Scope } from "../../agent.ts";
14
+ export declare function turnStream<R>(opts: {
15
+ agent: Agent;
16
+ label: string;
17
+ /** Resolve this turn's platform inputs (download attachments, walk a reply chain). */
18
+ resolve: () => Promise<R>;
19
+ /** What the resolved inputs make of the turn: where it runs, and what it asks. */
20
+ turn: (resolved: R) => {
21
+ scope: Scope;
22
+ prompt: Prompt;
23
+ };
24
+ /** Whether a failed resolution is worth a platform redelivery. Stated per channel because it IS a
25
+ * platform question — Slack reads its API error's status, and a transport that cannot tell says
26
+ * yes, since a retried download is cheap next to an unanswered ask. */
27
+ retryableLoadFailure: (cause: unknown) => boolean;
28
+ busyRetry?: BusyRetry;
29
+ }): Stream.Stream<AgentEvent, PortFailure>;
18
30
  /** How the busy-wait paces: retry the invoke every `delayMs` while the session's lease is held by an
19
31
  * EXTERNAL turn (a self-scheduled wake, a concurrent embedder invoke), up to `maxWaitMs` total. The
20
32
  * channel's own turns never collide (the turn-queue serializes per session), so a busy reject here is
@@ -25,12 +37,10 @@ export interface BusyRetry {
25
37
  }
26
38
  export declare const DEFAULT_BUSY_RETRY: BusyRetry;
27
39
  /**
28
- * Stream one Agent turn with the shared busy-wait. `onCompleted` (if given) fires on the turn's
29
- * `completed` event — the durable-commit point: only then does the turn provably live in the session,
30
- * so a failure or crash at ANY earlier point leaves the caller's pre-ACK state (turn intent, context
31
- * buffer) intact for replay/the next summon. The caller uses it to remove the turn intent AND commit
32
- * the context buffer, in that order, so a crash between the two clears cannot replay a
33
- * context-stripped turn.
40
+ * Stream one Agent turn with the shared busy-wait. The durable-commit point is NOT here: an answer becomes durable
41
+ * where its text is known and about to be written to the chat (the renderer's `onAnswered`), because "the model
42
+ * finished" and "the user has it" are different events. Source pulls remain demand-driven, including while final
43
+ * platform delivery runs.
34
44
  *
35
45
  * BUSY-WAIT: a `failed{code: session_busy}` FIRST event means an external turn holds this session's
36
46
  * lease and OUR turn never started — replay-safe. Retry (bounded) instead of yielding it: the user
@@ -39,14 +49,31 @@ export declare const DEFAULT_BUSY_RETRY: BusyRetry;
39
49
  * busy retries — a fail-fast reject is the only shape the engine emits it in, so nothing that started
40
50
  * is ever re-run.
41
51
  */
42
- export declare function streamTurnWithBusyRetry(agent: Agent,
43
- /** The full scope, not a session string — channels that set extension fields (lineage) pass them
44
- * through here; channels that don't pass `{ session }` and nothing changes. */
45
- scope: Scope, prompt: Prompt, options: {
52
+ export declare function busyRetryStream(agent: Agent, scope: Scope, prompt: Prompt, { label, busyRetry }: {
46
53
  label: string;
47
- onCompleted?: () => void;
48
54
  busyRetry?: BusyRetry;
49
- }): AsyncIterable<AgentEvent>;
55
+ }): Stream.Stream<AgentEvent, PortFailure>;
56
+ /**
57
+ * Load the turn's BACKGROUND refs — what was folded in from earlier un-summoned discussion, not the
58
+ * ask itself. Per-ref degradation is the whole meaning of that tier: one expired file costs a warn
59
+ * and a place in the missing-attachments count ({@link missingAttachmentsNote}), never the answer it
60
+ * merely accompanies, and never its still-readable siblings. Parallel, input order kept.
61
+ *
62
+ * PRIMARY refs are the opposite policy and stay with their channel: a failure there throws, so the
63
+ * agent never runs on an input the user pointed at and we failed to load.
64
+ *
65
+ * Shared by all three channels; `what` is the only part that is the platform's.
66
+ */
67
+ export declare function loadBackground<R, T>(refs: readonly R[], load: (ref: R) => Promise<T>, opts: {
68
+ label: string;
69
+ what: string;
70
+ }): Promise<{
71
+ loaded: {
72
+ ref: R;
73
+ value: T;
74
+ }[];
75
+ lost: number;
76
+ }>;
50
77
  /** What the attached-files manifest renders per file: display name, byte size, absolute local path. */
51
78
  export interface ManifestFile {
52
79
  name: string;
@@ -2,8 +2,9 @@
2
2
  * Shared pieces of the channels' invoke-turn modules (telegram/feishu/slack `invoke-turn.ts`) — the
3
3
  * halves that are channel-independent, so a retry-policy or prompt-wording change lands ONCE:
4
4
  *
5
- * - {@link streamTurnWithBusyRetry}: the busy-retry loop around `agent.invoke`, with the
6
- * `onCompleted` durable-commit point;
5
+ * - {@link turnStream}: resolve inputs → ask the agent, with a load failure arriving as a `failed`
6
+ * EVENT rather than a thrown iteration (SPEC MUST 2);
7
+ * - {@link busyRetryStream}: the busy-retry loop around `agent.invoke`;
7
8
  * - the prompt-suffix wording: {@link attachedFilesManifest}, {@link backgroundImagesManifest},
8
9
  * {@link missingAttachmentsNote}, {@link attributedFileName}.
9
10
  *
@@ -12,10 +13,39 @@
12
13
  * file tool answers that it cannot — visibly, at the moment it is asked.
13
14
  *
14
15
  * Attachment RESOLUTION stays per channel — the platform resource models (Bot API file_ids,
15
- * message-scoped Feishu keys, Slack file objects) are real differences.
16
+ * message-scoped Feishu keys, Slack file objects) are real differences. The two TIERS' failure
17
+ * policies are not: see {@link loadBackground}.
16
18
  */
19
+ import * as Clock from "effect/Clock";
20
+ import * as Effect from "effect/Effect";
21
+ import * as Stream from "effect/Stream";
17
22
  import { SESSION_BUSY_CODE } from "../../agent.js";
23
+ import { eventStream } from "./event-stream.js";
24
+ import { portJoin } from "../../effect-port.js";
18
25
  import { log } from "../../log.js";
26
+ /**
27
+ * One turn, end to end: resolve the platform inputs, then stream `agent.invoke` with the shared
28
+ * busy-wait. All three chat channels wrote this out, and the copies had already disagreed about the
29
+ * one thing that is actually a per-platform judgement (whether a load failure is worth a redelivery)
30
+ * while agreeing on everything that is not.
31
+ *
32
+ * A resolution failure becomes a `failed` EVENT. It must never throw out of the stream: the SPEC
33
+ * forbids it (MUST 2), and the turn's own settlement path is what tells the user — an exception here
34
+ * would take the reply with it.
35
+ */
36
+ export function turnStream(opts) {
37
+ return Stream.unwrap(portJoin(opts.resolve).pipe(Effect.map((resolved) => {
38
+ const { scope, prompt } = opts.turn(resolved);
39
+ return busyRetryStream(opts.agent, scope, prompt, {
40
+ label: opts.label,
41
+ ...(opts.busyRetry ? { busyRetry: opts.busyRetry } : {}),
42
+ });
43
+ }), Effect.catchTag("PortFailure", ({ cause }) => Effect.succeed(Stream.succeed({
44
+ type: "failed",
45
+ details: `could not load attachment: ${String(cause)}`,
46
+ retryable: opts.retryableLoadFailure(cause),
47
+ })))));
48
+ }
19
49
  // Each retry is a lease-check-level reject (tryAcquire runs before the session is bound) — waiting is nearly
20
50
  // free, and the loop exits within one delay of the holder finishing. So the cap is sized to outlast a
21
51
  // real tool-using wake turn (minutes), not to be short: 10 min. CEILING: a holder that runs longer than
@@ -23,12 +53,10 @@ import { log } from "../../log.js";
23
53
  // turn forever.
24
54
  export const DEFAULT_BUSY_RETRY = { delayMs: 5_000, maxWaitMs: 600_000 };
25
55
  /**
26
- * Stream one Agent turn with the shared busy-wait. `onCompleted` (if given) fires on the turn's
27
- * `completed` event — the durable-commit point: only then does the turn provably live in the session,
28
- * so a failure or crash at ANY earlier point leaves the caller's pre-ACK state (turn intent, context
29
- * buffer) intact for replay/the next summon. The caller uses it to remove the turn intent AND commit
30
- * the context buffer, in that order, so a crash between the two clears cannot replay a
31
- * context-stripped turn.
56
+ * Stream one Agent turn with the shared busy-wait. The durable-commit point is NOT here: an answer becomes durable
57
+ * where its text is known and about to be written to the chat (the renderer's `onAnswered`), because "the model
58
+ * finished" and "the user has it" are different events. Source pulls remain demand-driven, including while final
59
+ * platform delivery runs.
32
60
  *
33
61
  * BUSY-WAIT: a `failed{code: session_busy}` FIRST event means an external turn holds this session's
34
62
  * lease and OUR turn never started — replay-safe. Retry (bounded) instead of yielding it: the user
@@ -37,31 +65,60 @@ export const DEFAULT_BUSY_RETRY = { delayMs: 5_000, maxWaitMs: 600_000 };
37
65
  * busy retries — a fail-fast reject is the only shape the engine emits it in, so nothing that started
38
66
  * is ever re-run.
39
67
  */
40
- export async function* streamTurnWithBusyRetry(agent,
41
- /** The full scope, not a session string — channels that set extension fields (lineage) pass them
42
- * through here; channels that don't pass `{ session }` and nothing changes. */
43
- scope, prompt, options) {
44
- const { label, onCompleted, busyRetry = DEFAULT_BUSY_RETRY } = options;
45
- const session = scope.session;
46
- const deadline = Date.now() + busyRetry.maxWaitMs;
47
- for (;;) {
48
- let retryBusy = false;
49
- let first = true;
50
- for await (const e of agent.invoke(scope, prompt)) {
51
- if (first && e.type === "failed" && e.code === SESSION_BUSY_CODE && Date.now() + busyRetry.delayMs < deadline) {
52
- retryBusy = true; // fail-fast reject — the stream ends after this event; wait and re-invoke
53
- break;
54
- }
55
- first = false;
56
- if (e.type === "completed")
57
- onCompleted?.(); // the turn is durably in the session — commit point
58
- yield e;
68
+ export function busyRetryStream(agent, scope, prompt, { label, busyRetry = DEFAULT_BUSY_RETRY }) {
69
+ return Stream.unwrap(Effect.map(Clock.currentTimeMillis, (started) => {
70
+ const deadline = started + busyRetry.maxWaitMs;
71
+ const attempt = () => Stream.suspend(() => {
72
+ let retryBusy = false;
73
+ let first = true;
74
+ return eventStream(() => agent.invoke(scope, prompt), `${label} (session=${scope.session})`).pipe(Stream.takeWhileEffect((event) => Effect.gen(function* () {
75
+ if (first &&
76
+ event.type === "failed" &&
77
+ event.code === SESSION_BUSY_CODE &&
78
+ (yield* Clock.currentTimeMillis) + busyRetry.delayMs < deadline) {
79
+ retryBusy = true;
80
+ return false;
81
+ }
82
+ first = false;
83
+ return true;
84
+ })), Stream.scoped,
85
+ // concat closes the attempt's scope (and its iterator) before the wait or next invoke.
86
+ Stream.concat(Stream.suspend(() => retryBusy
87
+ ? Stream.unwrap(Effect.gen(function* () {
88
+ log.info(`${label} session ${scope.session} is busy (an external turn holds it) — retrying in ${busyRetry.delayMs}ms`);
89
+ yield* Effect.sleep(busyRetry.delayMs);
90
+ return attempt();
91
+ }))
92
+ : Stream.empty)));
93
+ });
94
+ return attempt();
95
+ }));
96
+ }
97
+ /**
98
+ * Load the turn's BACKGROUND refs — what was folded in from earlier un-summoned discussion, not the
99
+ * ask itself. Per-ref degradation is the whole meaning of that tier: one expired file costs a warn
100
+ * and a place in the missing-attachments count ({@link missingAttachmentsNote}), never the answer it
101
+ * merely accompanies, and never its still-readable siblings. Parallel, input order kept.
102
+ *
103
+ * PRIMARY refs are the opposite policy and stay with their channel: a failure there throws, so the
104
+ * agent never runs on an input the user pointed at and we failed to load.
105
+ *
106
+ * Shared by all three channels; `what` is the only part that is the platform's.
107
+ */
108
+ export async function loadBackground(refs, load, opts) {
109
+ const loaded = [];
110
+ let lost = 0;
111
+ const results = await Promise.allSettled(refs.map(load));
112
+ for (const [index, result] of results.entries()) {
113
+ const ref = refs[index];
114
+ if (result.status === "fulfilled")
115
+ loaded.push({ ref, value: result.value });
116
+ else {
117
+ lost++;
118
+ log.warn(`${opts.label} could not load an earlier (buffered) ${opts.what}: ${String(result.reason)}`);
59
119
  }
60
- if (!retryBusy)
61
- return;
62
- log.info(`${label} session ${session} is busy (an external turn holds it) — retrying in ${busyRetry.delayMs}ms`);
63
- await new Promise((r) => setTimeout(r, busyRetry.delayMs));
64
120
  }
121
+ return { loaded, lost };
65
122
  }
66
123
  /**
67
124
  * The downloaded-file manifest appended to the prompt: name, size, path. Empty input renders nothing.
@@ -1,12 +1,6 @@
1
1
  /**
2
- * Channel-neutral live-preview pieces shared by every messaging channel's preview renderer
3
- * (telegram/preview.ts, feishu/preview.ts, slack/preview.ts): the turn-view REDUCER (the one
4
- * event → view-state machine every renderer consumes), its line renderers, the terminal-failure
5
- * shape a channel hands to its `onError`, the customer-facing wording for it, and the serialized
6
- * single-writer pump. DELIVERY stays per-platform — message edits vs streaming cards vs chat.update,
7
- * pacing constants, reveal timing, and terminal-write policies are real platform differences — but
8
- * everything platform-independent lives here, so a new event type or a wording change lands in ONE
9
- * place instead of one hunk per channel.
2
+ * Channel-neutral live-preview pieces shared by every messaging channel's preview renderer (telegram/preview.ts,
3
+ * feishu/preview.ts, slack/preview.ts).
10
4
  */
11
5
  import type { AgentEvent, Json } from "../../agent.ts";
12
6
  /** A terminal failure, as a channel hands it to its `onError`. */
@@ -16,13 +10,12 @@ export interface ChannelFailure {
16
10
  /** The engine's failure code, when it set one. */
17
11
  code?: string;
18
12
  }
19
- /** The customer-facing default: neutral, no leaked internals. Differentiate on retryability and always
20
- * offer a next step (Slack agent-design: never leave the user with a dead-end "something went wrong").
21
- * The non-retryable branch keeps the "something went wrong" phrase deliberately — it is neutral (we only
22
- * know a boolean, never the specific limitation) and shared verbatim across channels. */
13
+ /** The customer-facing default: neutral, no leaked internals. */
23
14
  export declare function defaultErrorMessage(failed: ChannelFailure): string;
24
- /** Customer-facing live-preview line for an engine-internal retry backoff (the advisory `retrying`
25
- * event): neutral, no leaked internals — the reason stays in operator logs. */
15
+ /**
16
+ * Customer-facing live-preview line for an engine-internal retry backoff (the advisory `retrying` event): neutral, no
17
+ * leaked internals — the reason stays in operator logs.
18
+ */
26
19
  export declare const RETRY_NOTICE = "\u23F3 Temporary problem \u2014 retrying\u2026";
27
20
  /** The placeholder shown before any reasoning/tool/text arrives. */
28
21
  export declare const THINKING_PLACEHOLDER = "\uD83D\uDCAD Thinking\u2026";
@@ -31,12 +24,7 @@ interface ToolLine {
31
24
  label: string;
32
25
  status: "running" | "ok" | "error";
33
26
  }
34
- /**
35
- * The channel-neutral view STATE of one in-flight turn. Renderers own everything after this state:
36
- * when to reveal the young answer (age vs timer), how to format (HTML / card markdown / mrkdwn),
37
- * and how to deliver frames. Terminal events are deliberately NOT view state — completed/failed
38
- * resolve the preview into a final write, which is each platform's terminal-write policy.
39
- */
27
+ /** The channel-neutral view STATE of one in-flight turn. */
40
28
  export interface TurnView {
41
29
  thinking: string;
42
30
  tools: ToolLine[];
@@ -49,69 +37,27 @@ export interface TurnView {
49
37
  retrying: boolean;
50
38
  }
51
39
  export declare function createTurnView(): TurnView;
52
- /**
53
- * Apply one event to the view state. Returns true when the view changed (the caller repaints).
54
- * This is the ONE place the shared view rules live: tool labels are humanized with a compact arg
55
- * summary, and any progress event closes an open retry notice (a stale "retrying" line must never
56
- * outlive actual progress). Terminal events only close the notice — they are the caller's business.
57
- */
58
- export declare function applyTurnEvent(view: TurnView, e: AgentEvent): boolean;
40
+ export declare function applyTurnEvent(view: TurnView, e: AgentEvent, now?: number): boolean;
59
41
  /** The tool-activity block: one `🔧 label …/✓/✗` line per call, in call order. */
60
42
  export declare function toolLines(view: TurnView): string;
61
- /** The reasoning peek: the most recent tail of the (growing) reasoning, one line, code-point safe.
62
- * Process, not the answer — renderers show it live only, never in the persisted final message. */
43
+ /** The reasoning peek: the most recent tail of the (growing) reasoning, one line, code-point safe. */
63
44
  export declare function thinkingLine(view: TurnView, maxTail: number): string;
64
45
  /**
65
- * The shared answer-reveal policy: the answer stays hidden until its first delta has aged one
66
- * throttle window (`ageMs` — each platform passes its own pacing constant). The pump's leading-edge
67
- * flush would otherwise turn the very first content delta (often a lone character or unbalanced
68
- * markup) into its own frame — the short-reply flicker (placeholder → "O" → "OK."). Aging is
69
- * anchored at delta ARRIVAL (`answerSince`, set by the reducer) so an in-flight write can't skew the
70
- * clock, and there is deliberately NO timer at the boundary: a young answer surfaces on the next
71
- * content-driven pass, so a turn completing within the window delivers the final answer only.
46
+ * The shared answer-reveal policy: the answer stays hidden until its first delta has aged one throttle window (`ageMs`
47
+ * — each platform passes its own pacing constant).
72
48
  */
73
- export declare function revealedAnswer(view: TurnView, ageMs: number): string;
49
+ export declare function revealedAnswer(view: TurnView, ageMs: number, now?: number): string;
74
50
  /** Compose body parts (thinking/tools/retry/answer) into one frame: skip empties, blank-line joins. */
75
51
  export declare function composeTurnBody(parts: readonly string[]): string;
76
52
  /**
77
- * The serialized live-preview writer shared by the edit/snapshot renderers (telegram, feishu).
78
- * Events mark the view dirty; the pump repaints to the LATEST view with at most ONE write in
79
- * flight, paced by `throttleMs`. One-in-flight is the whole point: concurrent writes can land out
80
- * of order — an older frame over a newer one is the "shows 3-4 steps, blanks, re-fills" flicker —
81
- * so a single writer keeps frames monotonic (and makes feishu's strictly-increasing card `sequence`
82
- * correct by construction). NOT used by slack-classic: its pacing lives inside the flush (the 3s
83
- * chat.update rate slot), not at the frame boundary.
84
- */
85
- export interface PreviewPump {
86
- /** Mark the view dirty and ensure the single writer runs (an in-flight write picks the new state
87
- * up on its next loop). Synchronous — callers never await a network write. */
88
- touch(): void;
89
- /** Stop the pump, cut an in-flight throttle short, and await any in-flight write — so the
90
- * caller's terminal write is strictly the LAST one (no stale frame landing after the answer). */
91
- finish(): Promise<void>;
92
- }
93
- export declare function createPreviewPump(opts: {
94
- /** Write the LATEST view. Best-effort — the terminal write is authoritative. */
95
- flush: () => Promise<void>;
96
- /** Pace + coalesce a burst into one write; finish() interrupts a throttle in progress. */
97
- throttleMs: number;
98
- /** Called for the FIRST failing flush only (the pump keeps running): a never-rendering preview
99
- * must be diagnosable, not silent — and not a log flood. */
100
- onError: (error: unknown) => void;
101
- }): PreviewPump;
102
- /**
103
- * A compact, human-readable preview of a tool call's args so the live view reads `🔧 read AGENTS.md`
104
- * rather than just `🔧 read`. Generic (a channel knows no tool schemas): show the salient value — the
105
- * first primitive field, conventionally the subject (path / command / query / url) — else compact JSON.
53
+ * A compact, human-readable preview of a tool call's args so the live view reads `🔧 read AGENTS.md` rather than just
54
+ * `🔧 read`.
106
55
  */
107
56
  export declare function summarizeToolArgs(args: Json): string;
108
57
  /**
109
- * A plain-language label for a tool call, following Slack's agent-design guidance to name what a tool
110
- * does rather than expose a raw identifier ("Create issue", not "create_issue"; "Github: create issue",
111
- * not "mcp__github__create_issue"). Deliberately generic and engine-neutral: it only reshapes the
112
- * identifier string — it never invents semantics and never exposes arguments. An `mcp__server__tool`
113
- * identifier becomes `server: tool`; any other identifier has its separators normalized to spaces and
114
- * its first letter capitalized.
58
+ * A plain-language label for a tool call, following Slack's agent-design guidance to name what a tool does rather than
59
+ * expose a raw identifier ("Create issue", not "create_issue"; "Github: create issue", not
60
+ * "mcp__github__create_issue").
115
61
  */
116
62
  export declare function humanizeToolName(name: string): string;
117
63
  export {};