@fastagent-sh/fastagent 0.24.4 → 0.25.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 (480) hide show
  1. package/README.md +53 -82
  2. package/dist/agent-api.d.ts +452 -0
  3. package/dist/agent-api.js +201 -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 +77 -0
  9. package/dist/authoring.js +238 -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 +115 -63
  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 +17 -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 +252 -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 +190 -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 +131 -16
  164. package/dist/cli/commands/login.d.ts +31 -2
  165. package/dist/cli/commands/login.js +413 -64
  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 +3 -2
  184. package/dist/cli/login-relay.js +18 -6
  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 +68 -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 +217 -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 +222 -201
  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 +51 -26
  260. package/dist/effect-port.d.ts +4 -4
  261. package/dist/effect-port.js +4 -4
  262. package/dist/environment/apt.d.ts +4 -0
  263. package/dist/environment/apt.js +32 -0
  264. package/dist/environment/declare.d.ts +59 -0
  265. package/dist/environment/declare.js +210 -0
  266. package/dist/environment/mise.d.ts +50 -0
  267. package/dist/environment/mise.js +268 -0
  268. package/dist/feishu.d.ts +2 -2
  269. package/dist/feishu.js +2 -2
  270. package/dist/git.d.ts +13 -0
  271. package/dist/git.js +55 -0
  272. package/dist/harnesses/models/catalog.d.ts +20 -0
  273. package/dist/harnesses/models/catalog.js +254 -0
  274. package/dist/{engines/pi/auth.d.ts → harnesses/models/credentials.d.ts} +3 -3
  275. package/dist/{engines/pi/auth.js → harnesses/models/credentials.js} +3 -3
  276. package/dist/harnesses/models/environment.d.ts +43 -0
  277. package/dist/harnesses/models/environment.js +82 -0
  278. package/dist/{engines/pi → harnesses/models}/login.d.ts +57 -26
  279. package/dist/{engines/pi → harnesses/models}/login.js +187 -79
  280. package/dist/harnesses/models/models.d.ts +83 -0
  281. package/dist/harnesses/models/models.js +158 -0
  282. package/dist/harnesses/pi-durable/agent.d.ts +42 -0
  283. package/dist/harnesses/pi-durable/agent.js +579 -0
  284. package/dist/harnesses/pi-durable/defaults.d.ts +28 -0
  285. package/dist/harnesses/pi-durable/defaults.js +26 -0
  286. package/dist/harnesses/pi-durable/define.d.ts +77 -0
  287. package/dist/harnesses/pi-durable/define.js +28 -0
  288. package/dist/harnesses/pi-durable/entries.d.ts +32 -0
  289. package/dist/harnesses/pi-durable/entries.js +126 -0
  290. package/dist/harnesses/pi-durable/events.d.ts +25 -0
  291. package/dist/harnesses/pi-durable/events.js +238 -0
  292. package/dist/harnesses/pi-durable/ids.d.ts +16 -0
  293. package/dist/harnesses/pi-durable/ids.js +55 -0
  294. package/dist/harnesses/pi-durable/messages.d.ts +41 -0
  295. package/dist/harnesses/pi-durable/messages.js +116 -0
  296. package/dist/harnesses/pi-durable/open.d.ts +88 -0
  297. package/dist/harnesses/pi-durable/open.js +329 -0
  298. package/dist/harnesses/pi-durable/reads.d.ts +30 -0
  299. package/dist/harnesses/pi-durable/reads.js +94 -0
  300. package/dist/harnesses/pi-durable/registry.d.ts +18 -0
  301. package/dist/harnesses/pi-durable/registry.js +56 -0
  302. package/dist/harnesses/pi-durable/release.d.ts +13 -0
  303. package/dist/harnesses/pi-durable/release.js +40 -0
  304. package/dist/harnesses/pi-durable/serve.d.ts +80 -0
  305. package/dist/harnesses/pi-durable/serve.js +156 -0
  306. package/dist/harnesses/pi-durable/sessions.d.ts +27 -0
  307. package/dist/harnesses/pi-durable/sessions.js +47 -0
  308. package/dist/harnesses/pi-durable/storage.d.ts +4 -0
  309. package/dist/harnesses/pi-durable/storage.js +49 -0
  310. package/dist/harnesses/pi-durable/stream.d.ts +20 -0
  311. package/dist/harnesses/pi-durable/stream.js +221 -0
  312. package/dist/harnesses/pi-durable/text.d.ts +30 -0
  313. package/dist/harnesses/pi-durable/text.js +292 -0
  314. package/dist/harnesses/pi-durable/wake.d.ts +4 -0
  315. package/dist/harnesses/pi-durable/wake.js +81 -0
  316. package/dist/http/client.d.ts +6 -0
  317. package/dist/http/client.js +256 -0
  318. package/dist/http/contract.d.ts +541 -0
  319. package/dist/http/contract.js +179 -0
  320. package/dist/http/server.d.ts +34 -0
  321. package/dist/http/server.js +194 -0
  322. package/dist/index.d.ts +17 -4
  323. package/dist/index.js +10 -5
  324. package/dist/lark.d.ts +2 -2
  325. package/dist/lark.js +2 -2
  326. package/dist/locked-file.d.ts +17 -0
  327. package/dist/{engines/pi/locked-file.js → locked-file.js} +16 -10
  328. package/dist/once.d.ts +6 -0
  329. package/dist/once.js +23 -0
  330. package/dist/paths.d.ts +36 -52
  331. package/dist/paths.js +66 -182
  332. package/dist/runtime.d.ts +25 -2
  333. package/dist/runtime.js +27 -0
  334. package/dist/scaffold/add-channel.d.ts +14 -27
  335. package/dist/scaffold/add-channel.js +82 -180
  336. package/dist/scaffold/init.d.ts +17 -18
  337. package/dist/scaffold/init.js +87 -99
  338. package/dist/scaffold/templates/SYSTEM.md +12 -0
  339. package/dist/scaffold/templates/env.example +4 -10
  340. package/dist/scaffold/templates/fastagent.config.ts +21 -33
  341. package/dist/scaffold/templates/gitignore +8 -1
  342. package/dist/scaffold/templates/skills/writing-great-skills/SKILL.md +1 -2
  343. package/dist/scaffold/templates.d.ts +9 -4
  344. package/dist/scaffold/templates.js +7 -5
  345. package/dist/scaffold/vendor-skill.d.ts +1 -4
  346. package/dist/scaffold/vendor-skill.js +5 -6
  347. package/dist/schedule/cron.d.ts +13 -0
  348. package/dist/schedule/cron.js +52 -0
  349. package/dist/schedule/eventbridge-cron.d.ts +17 -0
  350. package/dist/schedule/eventbridge-cron.js +88 -0
  351. package/dist/schedule/schedule.d.ts +23 -0
  352. package/dist/schedule/schedule.js +34 -0
  353. package/dist/schedule/scheduler.d.ts +34 -33
  354. package/dist/schedule/scheduler.js +251 -137
  355. package/dist/schedule/state.d.ts +4 -14
  356. package/dist/schedule/state.js +7 -7
  357. package/dist/schedule/wake-alarm.d.ts +22 -2
  358. package/dist/schedule/wake-alarm.js +66 -20
  359. package/dist/schedule/wakeups.d.ts +16 -15
  360. package/dist/schedule/wakeups.js +47 -45
  361. package/dist/service.d.ts +56 -169
  362. package/dist/service.js +91 -187
  363. package/dist/serving-command.d.ts +10 -0
  364. package/dist/serving-command.js +16 -0
  365. package/dist/slack.d.ts +1 -1
  366. package/dist/slack.js +1 -1
  367. package/dist/{channels/kit/state.js → state-file.js} +6 -6
  368. package/dist/telegram.d.ts +1 -0
  369. package/dist/telegram.js +1 -0
  370. package/dist/tunnel.d.ts +3 -0
  371. package/dist/tunnel.js +12 -4
  372. package/dist/value-file.d.ts +21 -0
  373. package/dist/value-file.js +108 -0
  374. package/dist/version.d.ts +5 -0
  375. package/dist/version.js +14 -1
  376. package/package.json +18 -28
  377. package/dist/agent.d.ts +0 -106
  378. package/dist/agent.js +0 -21
  379. package/dist/channels/control.d.ts +0 -31
  380. package/dist/channels/control.js +0 -329
  381. package/dist/channels/define-channel.d.ts +0 -39
  382. package/dist/channels/define-channel.js +0 -9
  383. package/dist/channels/feishu/context-buffer.d.ts +0 -35
  384. package/dist/channels/feishu/context-buffer.js +0 -65
  385. package/dist/channels/feishu/scaffold/channel.websocket.ts +0 -31
  386. package/dist/channels/http.d.ts +0 -23
  387. package/dist/channels/http.js +0 -57
  388. package/dist/channels/lark/scaffold/channel.websocket.ts +0 -31
  389. package/dist/channels/slack/context-buffer.d.ts +0 -23
  390. package/dist/channels/slack/context-buffer.js +0 -37
  391. package/dist/channels/sse.d.ts +0 -4
  392. package/dist/channels/sse.js +0 -66
  393. package/dist/cli/commands/chat.d.ts +0 -3
  394. package/dist/cli/commands/chat.js +0 -14
  395. package/dist/cli/commands/routine-run.d.ts +0 -6
  396. package/dist/cli/commands/routine-run.js +0 -46
  397. package/dist/cli/commands/routine.d.ts +0 -25
  398. package/dist/cli/commands/routine.js +0 -122
  399. package/dist/cli/commands/tool.d.ts +0 -1
  400. package/dist/cli/commands/tool.js +0 -57
  401. package/dist/collect.d.ts +0 -63
  402. package/dist/collect.js +0 -90
  403. package/dist/core.d.ts +0 -13
  404. package/dist/core.js +0 -15
  405. package/dist/engines/pi/agent-models.d.ts +0 -83
  406. package/dist/engines/pi/agent-models.js +0 -120
  407. package/dist/engines/pi/agent-session-factory.d.ts +0 -121
  408. package/dist/engines/pi/agent-session-factory.js +0 -535
  409. package/dist/engines/pi/chat.d.ts +0 -3
  410. package/dist/engines/pi/chat.js +0 -8
  411. package/dist/engines/pi/config.d.ts +0 -71
  412. package/dist/engines/pi/config.js +0 -199
  413. package/dist/engines/pi/create.d.ts +0 -170
  414. package/dist/engines/pi/create.js +0 -343
  415. package/dist/engines/pi/definition.d.ts +0 -74
  416. package/dist/engines/pi/definition.js +0 -236
  417. package/dist/engines/pi/entry-images.d.ts +0 -14
  418. package/dist/engines/pi/entry-images.js +0 -23
  419. package/dist/engines/pi/invoke-session.d.ts +0 -18
  420. package/dist/engines/pi/invoke-session.js +0 -403
  421. package/dist/engines/pi/locked-file.d.ts +0 -9
  422. package/dist/engines/pi/machine.d.ts +0 -53
  423. package/dist/engines/pi/machine.js +0 -137
  424. package/dist/engines/pi/models.d.ts +0 -200
  425. package/dist/engines/pi/models.js +0 -533
  426. package/dist/engines/pi/open.d.ts +0 -180
  427. package/dist/engines/pi/open.js +0 -270
  428. package/dist/engines/pi/openai-account-models.d.ts +0 -35
  429. package/dist/engines/pi/openai-account-models.js +0 -116
  430. package/dist/engines/pi/report.d.ts +0 -18
  431. package/dist/engines/pi/report.js +0 -59
  432. package/dist/engines/pi/retry-event.d.ts +0 -6
  433. package/dist/engines/pi/retry-event.js +0 -15
  434. package/dist/engines/pi/service.d.ts +0 -9
  435. package/dist/engines/pi/service.js +0 -14
  436. package/dist/engines/pi/session-builder.d.ts +0 -13
  437. package/dist/engines/pi/session-builder.js +0 -117
  438. package/dist/engines/pi/session-control.d.ts +0 -77
  439. package/dist/engines/pi/session-control.js +0 -908
  440. package/dist/engines/pi/session-effects.d.ts +0 -14
  441. package/dist/engines/pi/session-effects.js +0 -30
  442. package/dist/engines/pi/session-inheritance.d.ts +0 -21
  443. package/dist/engines/pi/session-inheritance.js +0 -204
  444. package/dist/engines/pi/session-markers.d.ts +0 -49
  445. package/dist/engines/pi/session-markers.js +0 -60
  446. package/dist/engines/pi/session-settings.d.ts +0 -67
  447. package/dist/engines/pi/session-settings.js +0 -119
  448. package/dist/engines/pi/session-store.d.ts +0 -69
  449. package/dist/engines/pi/session-store.js +0 -430
  450. package/dist/engines/pi/tool-context.d.ts +0 -45
  451. package/dist/engines/pi/tool-context.js +0 -31
  452. package/dist/engines/pi/tool.d.ts +0 -99
  453. package/dist/engines/pi/tool.js +0 -118
  454. package/dist/engines/pi/turn-kit.d.ts +0 -92
  455. package/dist/engines/pi/turn-kit.js +0 -288
  456. package/dist/engines/pi/wake-tool.d.ts +0 -9
  457. package/dist/engines/pi/wake-tool.js +0 -92
  458. package/dist/loader.d.ts +0 -41
  459. package/dist/loader.js +0 -116
  460. package/dist/node.d.ts +0 -3
  461. package/dist/node.js +0 -4
  462. package/dist/observe.d.ts +0 -6
  463. package/dist/observe.js +0 -47
  464. package/dist/pi.d.ts +0 -25
  465. package/dist/pi.js +0 -15
  466. package/dist/scaffold/templates/APPEND_SYSTEM.md +0 -12
  467. package/dist/scaffold/templates/tools/fetch-url.ts +0 -25
  468. package/dist/schedule/discover.d.ts +0 -17
  469. package/dist/schedule/discover.js +0 -61
  470. package/dist/schedule/routine.d.ts +0 -43
  471. package/dist/schedule/routine.js +0 -32
  472. package/dist/schedule/run.d.ts +0 -55
  473. package/dist/schedule/run.js +0 -146
  474. package/dist/secrets-gate.d.ts +0 -32
  475. package/dist/secrets-gate.js +0 -33
  476. package/dist/session-remote.d.ts +0 -47
  477. package/dist/session-remote.js +0 -460
  478. package/dist/session.d.ts +0 -466
  479. package/dist/session.js +0 -57
  480. /package/dist/{channels/kit/state.d.ts → state-file.d.ts} +0 -0
@@ -1,7 +1,8 @@
1
+ import { answerText } from "../../answer-text.js";
1
2
  import * as Effect from "effect/Effect";
2
3
  import * as Clock from "effect/Clock";
3
4
  import * as Stream from "effect/Stream";
4
- import { previewPump, renderReply, serialWriter } from "../kit/delivery.js";
5
+ import { previewPump, renderReply, serialWriter, turnEnd, unfinishedTurn } from "../kit/delivery.js";
5
6
  import { PortFailure, portJoin } from "../../effect-port.js";
6
7
  import { log } from "../../log.js";
7
8
  import { RETRY_NOTICE, THINKING_PLACEHOLDER, applyTurnEvent, composeTurnBody, createTurnView, defaultErrorMessage, humanizeToolName, revealedAnswer, summarizeToolArgs, toolLines, } from "../kit/preview-kit.js";
@@ -45,7 +46,7 @@ function boldText(value) {
45
46
  /**
46
47
  * One trace line: the tool, what it was called on, and — on the failure line — that it failed.
47
48
  *
48
- * Tool OUTPUT never leaves the process, failed or not: the channel would have to guess the engine's
49
+ * Tool OUTPUT never leaves the process, failed or not: the channel would have to guess the harness's
49
50
  * result shape to read it, and the agent already explains a failure it recovered from in its answer.
50
51
  * The failure line therefore repeats the operation instead of adding one: it states WHICH call failed
51
52
  * (six `Bash` calls in a turn are otherwise indistinguishable) without exposing anything the start
@@ -105,6 +106,8 @@ export async function settleSlackPreview(api, target, previewTs, text) {
105
106
  }
106
107
  await api.postMessage(target, text);
107
108
  }
109
+ /** Whether an event carries answer text: a text delta, or the settled message it adds up to. */
110
+ const isAnswerText = (event) => (event.type === "delta" && event.channel === "text") || (event.type === "entry" && event.entry.kind === "assistant");
108
111
  function streamClassicSlackReply(events, api, target, formatError, initialPreviewTs, disclaimer, label, onAnswered) {
109
112
  return Effect.gen(function* () {
110
113
  const clock = yield* Clock.Clock;
@@ -176,7 +179,7 @@ function streamClassicSlackReply(events, api, target, formatError, initialPrevie
176
179
  onEvent: (event) => {
177
180
  const changed = applyTurnEvent(turn, event, now());
178
181
  // A hidden answer must not open a placeholder followed by a rate-limited edit on a fast turn.
179
- if (changed && (event.type !== "text" || revealedAnswer(turn, CLASSIC_UPDATE_INTERVAL_MS, now()) !== ""))
182
+ if (changed && (!isAnswerText(event) || revealedAnswer(turn, CLASSIC_UPDATE_INTERVAL_MS, now()) !== ""))
180
183
  touch();
181
184
  },
182
185
  });
@@ -206,7 +209,9 @@ function streamNativeSlackReply(events, api, target, formatError, initialPreview
206
209
  yield* Effect.addFinalizer(() => Effect.gen(function* () {
207
210
  if (target.channelId.startsWith("D"))
208
211
  setStatus("");
209
- yield* statuses.finish.pipe(Effect.orDie);
212
+ // A finalizer does not fail: its defect would make the turn's typed failure one too. Unreachable while
213
+ // `setStatus` catches its own failures, which no test can make it stop doing.
214
+ yield* statuses.finish.pipe(Effect.catchTag("PortFailure", (error) => Effect.sync(() => log.warn(`${label} could not finish the Slack Agent status: ${String(error.cause)}`))));
210
215
  }));
211
216
  let stopAttempted = false;
212
217
  const stop = (ts, markdown) => {
@@ -350,6 +355,32 @@ function streamNativeSlackReply(events, api, target, formatError, initialPreview
350
355
  ]);
351
356
  }
352
357
  });
358
+ let messageId;
359
+ let streamError;
360
+ const textOf = answerText();
361
+ /** An ending that is a failure: its notice woven into the stream, then the turn fails with it. */
362
+ const failWith = (failure) => Effect.gen(function* () {
363
+ finalized = true;
364
+ yield* Effect.gen(function* () {
365
+ const notice = yield* Effect.try({
366
+ try: () => formatError(failure) ?? "",
367
+ catch: (cause) => new PortFailure(cause),
368
+ });
369
+ if (notice) {
370
+ pendingText += `${fullAnswer.trim() ? "\n\n" : ""}${notice}`;
371
+ fullAnswer += `${fullAnswer.trim() ? "\n\n" : ""}${notice}`;
372
+ }
373
+ yield* settleNative(fullAnswer.trim() || GENERIC_FAILURE);
374
+ }).pipe(Effect.catchTag("PortFailure", (error) => Effect.sync(() => log.error(`${label} failed to deliver the agent-failure stream: ${String(error.cause)}`))));
375
+ return yield* Effect.fail(new PortFailure(new Error(`agent failed: ${failure.details} (retryable=${failure.retryable})`)));
376
+ });
377
+ const appendAnswer = (text) => {
378
+ if (!text)
379
+ return;
380
+ pendingText += text;
381
+ fullAnswer += text;
382
+ scheduleText();
383
+ };
353
384
  yield* Stream.runForEachWhile(events, (event) => Effect.gen(function* () {
354
385
  if (event.type !== "retrying" && retryStatusShown) {
355
386
  // Progress after a retry notice: restore the normal working status so the stale line doesn't
@@ -357,34 +388,43 @@ function streamNativeSlackReply(events, api, target, formatError, initialPreview
357
388
  retryStatusShown = false;
358
389
  setStatus(WORKING_STATUS);
359
390
  }
360
- if (event.type === "text") {
361
- pendingText += event.delta;
362
- fullAnswer += event.delta;
363
- scheduleText();
364
- }
365
- else if (event.type === "thinking") {
366
- // Slack's native loading status represents private reasoning without exposing it.
367
- }
368
- else if (event.type === "retrying") {
369
- // A summarization retry backoff pauses the stream (~14s worst case). Channels have no per-run
370
- // status surface in native mode; DMs get the explicit Agent status line, restored on progress.
371
- if (target.channelId.startsWith("D")) {
372
- retryStatusShown = true;
373
- setStatus("hit a temporary problem — retrying…");
391
+ if (event.type === "message")
392
+ messageId = event.message.id;
393
+ if (event.type === "stream_error")
394
+ streamError = event.error.message;
395
+ const end = turnEnd(event, messageId);
396
+ if (end === undefined) {
397
+ // Slack's native loading status represents private reasoning (`thinking` deltas) without exposing it.
398
+ if (event.type === "delta" && event.channel === "text")
399
+ appendAnswer(textOf(event));
400
+ else if (event.type === "retrying") {
401
+ // A summarization retry backoff pauses the stream (~14s worst case). Channels have no per-message
402
+ // status surface in native mode; DMs get the explicit Agent status line, restored on progress.
403
+ if (target.channelId.startsWith("D")) {
404
+ retryStatusShown = true;
405
+ setStatus("hit a temporary problem — retrying…");
406
+ }
374
407
  }
408
+ else if (event.type === "tool_started") {
409
+ const trace = nativeToolTrace(event.name, event.args);
410
+ toolTraces.set(event.toolCallId, trace);
411
+ sendToolTrace(nativeToolLine(trace));
412
+ }
413
+ else if (event.type === "entry") {
414
+ if (event.entry.kind === "assistant")
415
+ appendAnswer(textOf(event));
416
+ if (event.entry.kind === "tool_result") {
417
+ const data = event.entry.data;
418
+ const trace = (data.toolCallId && toolTraces.get(data.toolCallId)) || { label: "Tool" };
419
+ if (data.toolCallId)
420
+ toolTraces.delete(data.toolCallId);
421
+ if (data.isError === true)
422
+ sendToolTrace(nativeToolLine(trace, " failed"));
423
+ }
424
+ }
425
+ return true;
375
426
  }
376
- else if (event.type === "tool_started") {
377
- const trace = nativeToolTrace(event.name, event.args);
378
- toolTraces.set(event.id, trace);
379
- sendToolTrace(nativeToolLine(trace));
380
- }
381
- else if (event.type === "tool_ended") {
382
- const trace = toolTraces.get(event.id) ?? { label: "Tool" };
383
- toolTraces.delete(event.id);
384
- if (event.isError)
385
- sendToolTrace(nativeToolLine(trace, " failed"));
386
- }
387
- else if (event.type === "completed") {
427
+ if ("answered" in end) {
388
428
  finalized = true;
389
429
  const finalAnswer = withDisclaimer(fullAnswer, disclaimer);
390
430
  const footer = finalAnswer.slice(fullAnswer.trim().length);
@@ -396,29 +436,15 @@ function streamNativeSlackReply(events, api, target, formatError, initialPreview
396
436
  yield* settleNative(finalAnswer);
397
437
  return false;
398
438
  }
399
- else if (event.type === "failed") {
400
- finalized = true;
401
- yield* Effect.gen(function* () {
402
- const notice = yield* Effect.try({
403
- try: () => formatError({
404
- details: event.details,
405
- retryable: event.retryable,
406
- ...(event.code !== undefined ? { code: event.code } : {}),
407
- }) ?? "",
408
- catch: (cause) => new PortFailure(cause),
409
- });
410
- if (notice) {
411
- pendingText += `${fullAnswer.trim() ? "\n\n" : ""}${notice}`;
412
- fullAnswer += `${fullAnswer.trim() ? "\n\n" : ""}${notice}`;
413
- }
414
- yield* settleNative(fullAnswer.trim() || GENERIC_FAILURE);
415
- }).pipe(Effect.catchTag("PortFailure", (error) => Effect.sync(() => log.error(`${label} failed to deliver the agent-failure stream: ${String(error.cause)}`))));
416
- return yield* Effect.fail(new PortFailure(new Error(`agent failed: ${event.details} (retryable=${event.retryable})`)));
417
- }
418
- return true;
439
+ return yield* failWith(end.failure);
419
440
  }).pipe(Effect.uninterruptible));
420
- if (!finalized)
421
- yield* Effect.fail(new PortFailure(new Error("stream ended without a terminal event")));
441
+ if (finalized)
442
+ return;
443
+ const unfinished = unfinishedTurn(streamError);
444
+ if (unfinished === undefined) {
445
+ return yield* Effect.fail(new PortFailure(new Error("stream ended without a terminal event")));
446
+ }
447
+ yield* failWith(unfinished).pipe(Effect.uninterruptible);
422
448
  });
423
449
  }
424
450
  export function slackReply(events, api, target, formatError, options = {}) {
@@ -1,6 +1,7 @@
1
1
  import { type RegistrationOutcome } from "../registration.ts";
2
2
  export interface RegisterSlackWebhookOptions {
3
3
  stateRoot: string;
4
+ environment?: "dev" | "production";
4
5
  log?: (message: string) => void;
5
6
  attempts?: number;
6
7
  retryMs?: number;
@@ -6,6 +6,8 @@ import { currentSlackConfigToken, readSlackOnboardingState } from "./onboarding-
6
6
  export async function registerSlackWebhook(baseUrl, options) {
7
7
  const note = options.log ?? ((message) => console.error(message));
8
8
  const publicBaseUrl = baseUrl.replace(/\/$/, "");
9
+ const add = `fastagent add slack${options.environment === "production" ? " --env production" : ""}`;
10
+ const repair = `${add} --replace-config`;
9
11
  let state;
10
12
  try {
11
13
  state = readSlackOnboardingState(options.stateRoot);
@@ -15,9 +17,9 @@ export async function registerSlackWebhook(baseUrl, options) {
15
17
  return "failed";
16
18
  }
17
19
  if (!state?.appId || !state.installedAt) {
18
- note(`[fastagent] slack: no completed local onboarding state on this machine — the config credential lives only where \`fastagent add slack\` ran. ` +
20
+ note(`[fastagent] slack: no completed local onboarding state on this machine — the config credential lives only where \`${add}\` ran. ` +
19
21
  `Set Event Subscriptions → Request URL = ${publicBaseUrl}/slack manually in the Slack console, or re-run this command from the onboarding machine ` +
20
- `(repair its expired/revoked tokens with \`fastagent add slack --replace-config\`)`);
22
+ `(repair its expired/revoked tokens with \`${repair}\`)`);
21
23
  return "manual";
22
24
  }
23
25
  const consoleFallback = `set ${publicBaseUrl}/slack as the Event Subscriptions Request URL in the Slack console`;
@@ -31,14 +33,13 @@ export async function registerSlackWebhook(baseUrl, options) {
31
33
  }
32
34
  catch (error) {
33
35
  note(`[fastagent] slack: could not refresh the App Configuration token: ${String(error)} — ` +
34
- `re-run \`fastagent add slack --replace-config\` to repair it, or ${consoleFallback}`);
36
+ `re-run \`${repair}\` to repair it, or ${consoleFallback}`);
35
37
  return "failed";
36
38
  }
37
39
  // Slack verifies the new request_url with a challenge DURING apps.manifest.update, from Slack's own network.
38
40
  try {
39
41
  await retryWhile(() => updateSlackAppManifest(current.token, current.state.appId, buildSlackManifest({
40
42
  name: current.state.appName,
41
- groupBehavior: current.state.groupBehavior,
42
43
  requestUrl: `${publicBaseUrl}/slack`,
43
44
  // A manifest update replaces the whole manifest and redirect_urls may not be empty, so a placeholder is
44
45
  // declared.
@@ -60,7 +61,7 @@ export async function registerSlackWebhook(baseUrl, options) {
60
61
  ? `[fastagent] slack: Slack could not verify ${publicBaseUrl}/slack after retries (last error: ${String(error)}) — ` +
61
62
  `once the app is up, ${consoleFallback}`
62
63
  : `[fastagent] slack: automatic Request URL registration failed: ${String(error)} — ` +
63
- `re-run \`fastagent add slack --replace-config\` to repair the configuration tokens, or ${consoleFallback}`);
64
+ `re-run \`${repair}\` to repair the configuration tokens, or ${consoleFallback}`);
64
65
  return "failed";
65
66
  }
66
67
  }
@@ -1,33 +1,32 @@
1
+ import { needs } from "@fastagent-sh/fastagent";
1
2
  import { slackChannel } from "@fastagent-sh/fastagent/slack";
2
- import { defineChannel } from "@fastagent-sh/fastagent";
3
3
 
4
4
  // Slack HTTP Events API channel. Setup:
5
5
  // 1. Create a Slack app at https://api.slack.com/apps and add a bot user.
6
6
  // 2. Bot Token Scopes: app_mentions:read, assistant:write, chat:write, im:history,
7
- // files:read, files:write, channels:history, groups:history, and mpim:history. The explicit
8
- // mention-only mode may omit the three group-history scopes.
7
+ // files:read, files:write, reactions:write, channels:history, groups:history, mpim:history,
8
+ // and users:read.
9
9
  // 3. Event Subscriptions: app_home_opened, app_context_changed, app_mention, message.im,
10
- // message.channels, message.groups, and message.mpim. Mention-only may omit the last three.
10
+ // message.channels, message.groups, and message.mpim.
11
11
  // Set Request URL to https://<host>/slack.
12
12
  // 4. Install the app (reinstall after changing scopes) and leave token rotation OFF: it cannot be
13
13
  // turned off again, and this channel takes one long-lived Bot User OAuth Token.
14
- export default defineChannel({
15
- // The env vars this channel needs. fastagent carries them to a deployed box and refuses to serve
16
- // while one is unset — a bare process.env read gets neither guarantee.
17
- secrets: ["SLACK_BOT_TOKEN", "SLACK_SIGNING_SECRET"],
18
- channel: (secrets) =>
19
- slackChannel({
20
- botToken: secrets.SLACK_BOT_TOKEN, // OAuth & Permissions → Bot User OAuth Token (xoxb-…)
21
- signingSecret: secrets.SLACK_SIGNING_SECRET, // Basic Information → App Credentials
22
- // Slack Agent stream; its inline tool traces show each call's first argument and stay in the
23
- // delivered message. "classic" settles into the answer alone (and gives up native streaming).
24
- rendering: "native",
25
- // Optional per-reply footer, if your policy requires one: aiDisclaimer: "AI-generated; verify important information.",
26
- // welcome: "Custom first-run DM greeting", // sent once on first DM open; false disables (default: a generic greeting)
27
- // reactionAck: false, // disable the 👀→✅ ack on the user's message (default on; needs reactions:write)
28
- // No session modes: an answer attaches to its question with a thread (Slack has no quote primitive),
29
- // and that thread is the session — see docs/design/participant-model.md.
30
- // Dev/personal bot: surface raw errors. Remove this for a customer-facing bot; details remain in logs.
31
- onError: (failed) => `⚠️ ${failed.details}`,
32
- }),
33
- });
14
+ //
15
+ // `needs` declares the env vars this channel reads: fastagent carries them to a deployed box and
16
+ // refuses to serve while one is unset.
17
+ export default needs(["SLACK_BOT_TOKEN", "SLACK_SIGNING_SECRET"], (secrets) =>
18
+ slackChannel({
19
+ botToken: secrets.SLACK_BOT_TOKEN, // OAuth & Permissions → Bot User OAuth Token (xoxb-…)
20
+ signingSecret: secrets.SLACK_SIGNING_SECRET, // Basic Information → App Credentials
21
+ // Slack Agent stream; its inline tool traces show each call's first argument and stay in the
22
+ // delivered message. "classic" settles into the answer alone (and gives up native streaming).
23
+ rendering: "native",
24
+ // Optional per-reply footer, if your policy requires one: aiDisclaimer: "AI-generated; verify important information.",
25
+ // welcome: "Custom first-run DM greeting", // sent once on first DM open; false disables (default: a generic greeting)
26
+ // reactionAck: false, // disable the 👀→✅ ack on the user's message (default on; needs reactions:write)
27
+ // An answer attaches to its question with a thread (Slack has no quote primitive), and that thread is
28
+ // one conversation, with a session of its own: see docs/design/participant-model.md.
29
+ // Dev/personal bot: surface raw errors. Remove this for a customer-facing bot; details remain in logs.
30
+ onError: (failed) => `⚠️ ${failed.details}`,
31
+ }),
32
+ );
@@ -1,48 +1,55 @@
1
- import { defineTool, z } from "@fastagent-sh/fastagent";
1
+ import { Type } from "@earendil-works/pi-ai";
2
+ import { defineTool } from "@earendil-works/pi-durable";
3
+ import { needs } from "@fastagent-sh/fastagent";
2
4
  import { slackTransport } from "@fastagent-sh/fastagent/slack";
3
5
 
4
6
  // Send a message or upload a local file to Slack. In a CHAT turn the channel delivers the reply
5
- // itself — do NOT call this to answer a normal chat turn (that posts the message twice). This tool is
6
- // for file uploads, and for turns NO channel is carrying: a scheduled turn (routines/<name>.ts) or a
7
- // self-scheduled wake-up, whose plain reply is not delivered anywhere. The channelId/threadTs come
8
- // from the [slack: …] context line in a chat turn; a scheduled/woken turn has no such line, so its
9
- // prompt must name the target channel id. tools/ is auto-discovered.
7
+ // itself: do NOT call this to answer a normal chat turn (that posts the message twice). This tool is
8
+ // for file uploads, and for messages NO channel is carrying: a schedule's (src/schedules/) or a
9
+ // self-scheduled wake-up's, whose plain reply is not delivered anywhere. The channelId/threadTs come
10
+ // from the [slack: …] context line in a chat turn; a scheduled or woken message has no such line, so
11
+ // its prompt must name the target channel id.
10
12
  //
11
- // Delivery rides the channel's own transport (slackTransport): its token, API base, Markdown splitting
12
- // and rate-limit handling. With no channel mounted (`fastagent routine run` / `invoke`) it is built from
13
- // SLACK_BOT_TOKEN in .env.
14
-
15
- export default defineTool({
16
- description:
17
- "Upload one local file to Slack (`path`), or send a message (`text`) for a turn NO channel is " +
18
- "carrying — a scheduled or self-scheduled (wake) turn. In a normal chat turn the channel already " +
19
- "delivers your reply, so do NOT call this to answer (it would post the message twice). Pass exactly " +
20
- "one of `text`/`path`. channelId/threadTs come from the [slack: …] context line in a chat turn; a " +
21
- "scheduled/woken turn has no context line, so name the destination in your instruction. A user ID " +
22
- "(U…) as channelId messages that user's DM; a file upload needs the DM channel ID (D…) the text send reports.",
23
- input: z.object({
24
- channelId: z.string().describe("target Slack channel ID, or a user ID (U…) to message that user's DM"),
25
- text: z.string().optional().describe("standard Markdown message text"),
26
- path: z.string().optional().describe("absolute path of a local file to upload"),
27
- title: z.string().optional().describe("file title (file mode only)"),
28
- initialComment: z.string().optional().describe("message posted with the file (file mode only)"),
29
- threadTs: z.string().optional().describe("thread parent timestamp, if replying in a thread"),
30
- }),
31
- async execute({ channelId, text, path, title, initialComment, threadTs }, ctx) {
32
- if ((text === undefined) === (path === undefined)) throw new Error("pass exactly one of `text` or `path`");
33
- const slack = slackTransport(ctx.cwd);
34
- const target = { channelId, threadTs };
35
- if (text !== undefined) {
36
- if (title !== undefined || initialComment !== undefined) {
37
- throw new Error("`title`/`initialComment` are file-mode only");
13
+ // Delivery rides the channel's own transport (slackTransport): its token, API base, Markdown
14
+ // splitting and rate-limit handling. With no channel mounted, it is built from SLACK_BOT_TOKEN.
15
+ export default needs(["SLACK_BOT_TOKEN"], ({ SLACK_BOT_TOKEN }) =>
16
+ defineTool({
17
+ name: "slack_send",
18
+ description:
19
+ "Upload one local file to Slack (`path`), or send a message (`text`) for a message NO channel is " +
20
+ "carrying: a scheduled or self-scheduled one. In a normal chat turn the channel already " +
21
+ "delivers your reply, so do NOT call this to answer (it would post the message twice). Pass exactly " +
22
+ "one of `text`/`path`. channelId/threadTs come from the [slack: …] context line in a chat turn; a " +
23
+ "scheduled or woken message has no context line, so name the destination in your instruction. A user ID " +
24
+ "(U…) as channelId messages that user's DM; a file upload needs the DM channel ID (D…) the text send reports.",
25
+ parameters: Type.Object({
26
+ channelId: Type.String({ description: "target Slack channel ID, or a user ID (U…) to message that user's DM" }),
27
+ text: Type.Optional(Type.String({ description: "standard Markdown message text" })),
28
+ path: Type.Optional(Type.String({ description: "absolute path of a local file to upload" })),
29
+ title: Type.Optional(Type.String({ description: "file title (file mode only)" })),
30
+ initialComment: Type.Optional(Type.String({ description: "message posted with the file (file mode only)" })),
31
+ threadTs: Type.Optional(Type.String({ description: "thread parent timestamp, if replying in a thread" })),
32
+ }),
33
+ execute: async ({ channelId, text, path, title, initialComment, threadTs }) => {
34
+ if ((text === undefined) === (path === undefined)) throw new Error("pass exactly one of `text` or `path`");
35
+ const slack = slackTransport(SLACK_BOT_TOKEN);
36
+ const target = { channelId, threadTs };
37
+ let said: string;
38
+ if (text !== undefined) {
39
+ if (title !== undefined || initialComment !== undefined) {
40
+ throw new Error("`title`/`initialComment` are file-mode only");
41
+ }
42
+ const sent = await slack.sendMarkdown(target, text);
43
+ // A user-id target reports the DM channel Slack resolved it to: the id a file upload needs.
44
+ said =
45
+ sent.channelId === channelId
46
+ ? `sent message to Slack channel ${channelId} (ts ${sent.ts})`
47
+ : `sent message to Slack user ${channelId} in DM channel ${sent.channelId} (ts ${sent.ts})`;
48
+ } else {
49
+ const file = await slack.uploadFile(target, path as string, { title, initialComment });
50
+ said = `uploaded ${file.name} to Slack channel ${channelId} (file ${file.id})`;
38
51
  }
39
- const sent = await slack.sendMarkdown(target, text);
40
- // A user-id target reports the DM channel Slack resolved it to — the id a file upload needs.
41
- return sent.channelId === channelId
42
- ? `sent message to Slack channel ${channelId} (ts ${sent.ts})`
43
- : `sent message to Slack user ${channelId} in DM channel ${sent.channelId} (ts ${sent.ts})`;
44
- }
45
- const file = await slack.uploadFile(target, path as string, { title, initialComment });
46
- return `uploaded ${file.name} to Slack channel ${channelId} (file ${file.id})`;
47
- },
48
- });
52
+ return { output: [{ type: "text", text: said }] };
53
+ },
54
+ }),
55
+ );
@@ -0,0 +1,29 @@
1
+ import { Type } from "@earendil-works/pi-ai";
2
+ import { defineTool } from "@earendil-works/pi-durable";
3
+ import { sessionIdOf } from "@fastagent-sh/fastagent";
4
+ import { slackThreads } from "@fastagent-sh/fastagent/slack";
5
+
6
+ // Reads the threads of the Slack channel this message was asked in, and no other channel's: the
7
+ // channel names where the message runs, so the tool takes no channel id. It works in a channel turn
8
+ // served by `fastagent dev`/`start`.
9
+ export default defineTool({
10
+ name: "slack_threads",
11
+ description:
12
+ "Read the threads of the Slack channel you are answering in. Without `threadId`, list its recent threads, most " +
13
+ "recently active first, each with its id; with a `threadId` from that list, read that thread. Use it when a " +
14
+ "question here concerns something discussed in another thread, such as whether a problem raised there was " +
15
+ "resolved: the answer is often what a person wrote in the thread. Only this channel's threads can be read, and " +
16
+ "only in a channel turn.",
17
+ parameters: Type.Object({
18
+ threadId: Type.Optional(
19
+ Type.String({
20
+ description: "a thread id from the list (its first message's ts, like 1712345678.123456); omit it to list",
21
+ }),
22
+ ),
23
+ }),
24
+ execute: async ({ threadId }, api, context) => {
25
+ const threads = slackThreads(await sessionIdOf(api, context));
26
+ const text = threadId === undefined ? await threads.list() : await threads.read(threadId);
27
+ return { output: [{ type: "text", text }] };
28
+ },
29
+ });
@@ -2,6 +2,25 @@ export interface SlackSetupServer {
2
2
  port: number;
3
3
  requestPath: string;
4
4
  redirectPath: string;
5
+ /**
6
+ * The page a person pastes an App Configuration token pair on, for a setup a coding agent runs: the pair never
7
+ * passes through the agent's conversation or any file. Its path is the server's secret, like the OAuth redirect's.
8
+ */
9
+ tokensPath: string;
10
+ /** The pair, once one `check` accepts is pasted; what `check` throws is shown on the page, which stays up. */
11
+ waitForTokens<T>(check: (pair: {
12
+ access: string;
13
+ refresh: string;
14
+ }) => T): Promise<T>;
15
+ /** Where the browser that pasted the pair goes next: Slack's install page. */
16
+ next(url: string): void;
17
+ /**
18
+ * Why the setup stopped, for a browser waiting on the page after the pair: resolves once it has been shown, at once
19
+ * when no browser waits there, or after one refresh window, so a setup can stop without leaving it on a dead page.
20
+ */
21
+ failed(message: string): Promise<void>;
22
+ /** What the setup did, for a browser waiting on that page when the setup ends there (a saved pair replaced), as `failed`. */
23
+ done(message: string): Promise<void>;
5
24
  waitForOAuth(timeoutMs?: number): Promise<{
6
25
  code?: string;
7
26
  state?: string;
@@ -1,10 +1,45 @@
1
1
  import { randomBytes } from "node:crypto";
2
2
  import { createServer } from "node:http";
3
+ const escapeHtml = (text) => text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
4
+ const page = (body, head = "") => `<!doctype html><meta charset=utf-8><meta name=viewport content="width=device-width">${head}` +
5
+ `<title>FastAgent Slack setup</title><body style="font:16px system-ui;max-width:40em;margin:3em auto;padding:0 1em">${body}`;
6
+ function tokensForm(error) {
7
+ return page(`<h1>Slack App Configuration token</h1>` +
8
+ `<ol><li>Open <a href="https://api.slack.com/apps" target=_blank rel=noopener>api.slack.com/apps</a>, then ` +
9
+ `<b>Your App Configuration Tokens</b> → <b>Generate Token</b> for the workspace the agent joins.</li>` +
10
+ `<li>Paste both tokens here.</li></ol>` +
11
+ `<p>FastAgent keeps them on this machine only (owner-readable), uses them to create the app and update its ` +
12
+ `Request URL, and never deploys them.</p>` +
13
+ (error ? `<p style="color:#b00"><b>${escapeHtml(error)}</b></p>` : "") +
14
+ `<form method=post><p><label>Access token<br><input name=access type=password required ` +
15
+ `placeholder="xoxe.xoxp-…" style="width:100%"></label></p><p><label>Refresh token<br><input name=refresh ` +
16
+ `type=password required placeholder="xoxe-…" style="width:100%"></label></p>` +
17
+ `<p><button>Create the Slack app</button></p></form>`);
18
+ }
3
19
  /** Temporary onboarding-only responder. */
4
20
  export async function startSlackSetupServer() {
5
21
  const nonce = randomBytes(24).toString("hex");
6
22
  const requestPath = `/slack/setup/${nonce}`;
7
23
  const redirectPath = `/slack/oauth/${nonce}`;
24
+ const tokensPath = `/slack/tokens/${nonce}`;
25
+ const nextPath = `/slack/next/${nonce}`;
26
+ let takeTokens;
27
+ let pasted = false;
28
+ let after;
29
+ let lastShown;
30
+ /** The last page a browser waiting after the pair sees, and when it has seen it (bounded by one refresh window). */
31
+ const lastPage = (said, failed) => {
32
+ if (!pasted || after !== undefined)
33
+ return Promise.resolve();
34
+ after = { said, failed };
35
+ return new Promise((resolve) => {
36
+ const timer = setTimeout(resolve, 5_000);
37
+ lastShown = () => {
38
+ clearTimeout(timer);
39
+ resolve();
40
+ };
41
+ });
42
+ };
8
43
  let settleOAuth;
9
44
  const oauth = new Promise((resolve) => {
10
45
  settleOAuth = resolve;
@@ -24,21 +59,61 @@ export async function startSlackSetupServer() {
24
59
  settleOAuth = undefined;
25
60
  response.statusCode = 200;
26
61
  response.setHeader("content-type", "text/html; charset=utf-8");
27
- response.end("<!doctype html><title>FastAgent Slack setup</title><p>Slack authorization received. Return to your terminal.</p>");
62
+ response.end(page("<p>Slack authorization received: FastAgent is finishing the install. You can close this page.</p>"));
28
63
  return;
29
64
  }
30
- if (request.method === "POST" && url.pathname === requestPath) {
31
- let body = "";
32
- let tooLarge = false;
33
- request.on("data", (chunk) => {
34
- if (tooLarge)
65
+ if (url.pathname === tokensPath && (request.method === "GET" || request.method === "POST")) {
66
+ response.setHeader("content-type", "text/html; charset=utf-8");
67
+ if (takeTokens === undefined) {
68
+ // Taken already, or not asked for: the page is one-time.
69
+ response.statusCode = 404;
70
+ response.end(page("<p>This page is no longer in use.</p>"));
71
+ return;
72
+ }
73
+ if (request.method === "GET") {
74
+ response.end(tokensForm());
75
+ return;
76
+ }
77
+ readBody(request, (body) => {
78
+ if (body === undefined) {
79
+ response.statusCode = 413;
80
+ response.end(page("<p>Too large.</p>"));
35
81
  return;
36
- body += String(chunk);
37
- if (Buffer.byteLength(body) > 64 * 1024)
38
- tooLarge = true;
82
+ }
83
+ const form = new URLSearchParams(body);
84
+ try {
85
+ takeTokens?.({ access: (form.get("access") ?? "").trim(), refresh: (form.get("refresh") ?? "").trim() });
86
+ }
87
+ catch (error) {
88
+ response.end(tokensForm(error.message));
89
+ return;
90
+ }
91
+ response.statusCode = 303;
92
+ response.setHeader("location", nextPath);
93
+ response.end();
39
94
  });
40
- request.on("end", () => {
41
- if (tooLarge) {
95
+ return;
96
+ }
97
+ if (request.method === "GET" && url.pathname === nextPath) {
98
+ response.setHeader("content-type", "text/html; charset=utf-8");
99
+ if (after === undefined) {
100
+ response.end(page("<p>Creating the Slack app…</p>", '<meta http-equiv="refresh" content="2">'));
101
+ }
102
+ else if ("url" in after) {
103
+ response.statusCode = 303;
104
+ response.setHeader("location", after.url);
105
+ response.end();
106
+ }
107
+ else {
108
+ const style = after.failed ? ' style="color:#b00"' : "";
109
+ response.end(page(`<p${style}><b>${escapeHtml(after.said)}</b></p><p>Return to your agent.</p>`));
110
+ lastShown?.();
111
+ }
112
+ return;
113
+ }
114
+ if (request.method === "POST" && url.pathname === requestPath) {
115
+ readBody(request, (body) => {
116
+ if (body === undefined) {
42
117
  response.statusCode = 413;
43
118
  response.end("payload too large\n");
44
119
  return;
@@ -78,6 +153,20 @@ export async function startSlackSetupServer() {
78
153
  port: address.port,
79
154
  requestPath,
80
155
  redirectPath,
156
+ tokensPath,
157
+ waitForTokens: (check) => new Promise((resolve) => {
158
+ takeTokens = (pair) => {
159
+ const taken = check(pair);
160
+ takeTokens = undefined;
161
+ pasted = true;
162
+ resolve(taken);
163
+ };
164
+ }),
165
+ next(url) {
166
+ after = { url };
167
+ },
168
+ failed: (message) => lastPage(message, true),
169
+ done: (message) => lastPage(message, false),
81
170
  async waitForOAuth(timeoutMs = 10 * 60_000) {
82
171
  let timer;
83
172
  const timeout = new Promise((_, reject) => {
@@ -97,3 +186,16 @@ export async function startSlackSetupServer() {
97
186
  }),
98
187
  };
99
188
  }
189
+ /** A request's body, or `undefined` past 64 KB: setup traffic is small, and anything larger is not ours. */
190
+ function readBody(request, then) {
191
+ let body = "";
192
+ let tooLarge = false;
193
+ request.on("data", (chunk) => {
194
+ if (tooLarge)
195
+ return;
196
+ body += String(chunk);
197
+ if (Buffer.byteLength(body) > 64 * 1024)
198
+ tooLarge = true;
199
+ });
200
+ request.on("end", () => then(tooLarge ? undefined : body));
201
+ }