@fastagent-sh/fastagent 0.21.1 → 0.22.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 (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 +209 -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 +153 -228
  365. package/dist/session.js +33 -55
  366. package/dist/tunnel.d.ts +8 -36
  367. package/dist/tunnel.js +24 -65
  368. package/dist/version.d.ts +2 -2
  369. package/dist/version.js +2 -2
  370. package/package.json +10 -16
  371. package/dist/channels/agentcore-state.d.ts +0 -77
  372. package/dist/channels/agentcore-state.js +0 -276
  373. package/dist/channels/github/github.d.ts +0 -31
  374. package/dist/channels/github/github.js +0 -89
  375. package/dist/channels/github/scaffold/channel.ts +0 -25
  376. package/dist/cli/commands/attach.d.ts +0 -108
  377. package/dist/cli/commands/attach.js +0 -637
  378. package/dist/cli/commands/fire.d.ts +0 -7
  379. package/dist/cli/commands/fire.js +0 -38
  380. package/dist/cli/commands/schedule.d.ts +0 -12
  381. package/dist/cli/commands/schedule.js +0 -87
  382. package/dist/github.d.ts +0 -3
  383. package/dist/github.js +0 -2
  384. package/dist/scaffold/templates/fastagent.config.mjs +0 -23
  385. package/dist/schedule/audit.d.ts +0 -18
  386. package/dist/schedule/audit.js +0 -57
  387. package/dist/schedule/schedule.d.ts +0 -35
  388. package/dist/schedule/schedule.js +0 -23
@@ -1,13 +1,13 @@
1
1
  /**
2
- * `fastagent add <channel>|skill` — scaffold channel glue (`channels/<kind>.ts`) or vendor an Agent
3
- * Skills skill. slack/feishu/lark additionally CREATE OR RESUME the platform app.
2
+ * `fastagent add <channel>|skill` — scaffold channel glue (`channels/<kind>.ts`) or vendor an Agent Skills skill.
3
+ * slack/feishu/lark additionally CREATE OR RESUME the platform app.
4
4
  */
5
5
  import { randomBytes } from "node:crypto";
6
6
  import { readFile } from "node:fs/promises";
7
7
  import { join, relative, resolve } from "node:path";
8
8
  import { isCancel, select } from "@clack/prompts";
9
9
  import { onboardFeishuCloudApp } from "../add-feishu.js";
10
- import { dotEnvPath, loadDotEnv } from "../../env.js";
10
+ import { dotEnvPath, enterAgentEnv } from "../../env.js";
11
11
  import { resolveStateRoot, SECRETS_DIRNAME, isUnderDir, displayPath } from "../../paths.js";
12
12
  import { detectRuntime, readPackageJson } from "../../runtime.js";
13
13
  import { appendChannelDotEnv, appendChannelEnv, assertChannelReady, channelExists, channelSetup, scaffoldChannel, scaffoldCompanionTools, } from "../../scaffold/add-channel.js";
@@ -15,27 +15,20 @@ import { vendorSkill } from "../../scaffold/vendor-skill.js";
15
15
  import { failStartup, failUsage, placementOrExit } from "../fail.js";
16
16
  /** `fastagent add <kind> [dir]`: scaffold `channels/<kind>.ts` — the adapter import plus a starter `on()`. */
17
17
  export async function runAddChannel(channelKind, dirArg, opts) {
18
- // The channel (glue + companion tool + secrets) is agent surface — everything lands in the
19
- // AGENT DIR (`fastagent/`), the same place dev/start discover channels/.
20
- // Flag conflicts first: a bad combination is a USAGE error (exit 2), and reporting it must not depend
21
- // on the directory being an agent (a runtime/environment failure, exit 1) — the same order start/tool
22
- // follow.
18
+ // The channel (glue + companion tool + secrets) is agent surface — everything lands in the AGENT DIR
19
+ // (`fastagent/`), the same place dev/start discover channels/.
23
20
  if (opts.replaceConfig && opts.onboard === false) {
24
21
  failUsage("--replace-config replaces onboarding credentials; it cannot be combined with --no-onboard");
25
22
  }
26
23
  const { agentDir: target } = placementOrExit(resolve(dirArg));
27
- loadDotEnv(target); // onboarding state follows the same FASTAGENT_STATE_DIR as serving/deploy
28
- // Paths are printed to someone standing in their CWD, usually the workspace, while every file
29
- // belongs to the AGENT dir — prefix them, or they point at nothing. `displayPath` owns the
30
- // relative-vs-absolute policy (shared with `init`). The `.env` label names the file actually
31
- // WRITTEN (FASTAGENT_SECRETS_DIR relocates it, possibly out of the agent), never the default spelling.
24
+ enterAgentEnv(target); // onboarding state follows the same FASTAGENT_STATE_DIR as serving/deploy
25
+ // Paths are printed to someone standing in their CWD, usually the workspace, while every file belongs to the AGENT
26
+ // dir — prefix them, or they point at nothing.
32
27
  const agentFromCwd = displayPath(process.cwd(), target);
33
28
  const inAgent = (p) => (agentFromCwd === undefined ? p : join(agentFromCwd, p));
34
29
  const envPath = dotEnvPath(target);
35
30
  const envLabel = isUnderDir(envPath, target) ? inAgent(relative(target, envPath)) : envPath;
36
- // An existing channel file is authored glue: kept, never rewritten. The add continues past it — the
37
- // companion tools below are refreshed, and slack/feishu/lark resume a failed/cancelled app or OAuth
38
- // flow — so re-running `add <kind>` never requires hand-deleting the glue.
31
+ // An existing channel file is authored glue: kept, never rewritten.
39
32
  const file = join(target, "channels", `${channelKind}.ts`);
40
33
  const existsAlready = await channelExists(target, channelKind).catch(failStartup);
41
34
  const ingress = await resolveIngress(channelKind, file, existsAlready, opts.ingress);
@@ -48,16 +41,14 @@ export async function runAddChannel(channelKind, dirArg, opts) {
48
41
  await scaffoldChannel(target, channelKind, { ingress }).catch(failStartup);
49
42
  console.error(`[fastagent] created ${relative(target, file)}`);
50
43
  }
51
- // Companion tools are the package's, not authored glue: written on every add, so an upgraded
52
- // package reaches an existing agent by re-running `add <kind>` (slack: `--no-onboard` skips the prompts).
44
+ // Companion tools are the package's, not authored glue.
53
45
  for (const tool of await scaffoldCompanionTools(target, channelKind).catch(failStartup)) {
54
46
  console.error(`[fastagent] wrote ${relative(target, tool)}`);
55
47
  }
56
48
  if (await appendChannelEnv(target, channelKind, ingress).catch(failStartup)) {
57
49
  console.error(`[fastagent] added ${channelKind} env vars to ${inAgent(join(SECRETS_DIRNAME, ".env.example"))}`);
58
50
  }
59
- // Stateful app onboarding is re-runnable after the scaffold boundary. Slack's internal-app path
60
- // persists its manifest/OAuth recovery state separately and writes runtime secrets directly.
51
+ // Stateful app onboarding is re-runnable after the scaffold boundary.
61
52
  let created;
62
53
  if (channelKind === "slack" && opts.onboard !== false) {
63
54
  const { onboardSlackInternalApp } = await import("../add-slack.js");
@@ -77,16 +68,14 @@ export async function runAddChannel(channelKind, dirArg, opts) {
77
68
  const env = setup.env;
78
69
  const steps = channelKind === "slack" && opts.onboard !== false
79
70
  ? [
80
- // No line about the Request URL: the `dev --tunnel` line below is the whole instruction, and
81
- // FastAgent sets that URL itself when the agent first runs.
71
+ // No line about the Request URL: the `dev --tunnel` line below is the whole instruction, and FastAgent sets
72
+ // that URL itself when the agent first runs.
82
73
  "invite the app to each channel it should read",
83
74
  "the agent can send messages or files by calling the scaffolded {tools}/slack-send.ts tool",
84
75
  ]
85
76
  : setup.steps;
86
77
  const generated = Object.fromEntries(env.filter((e) => e.generate).map((e) => [e.name, randomBytes(24).toString("hex")]));
87
- // Kind-neutral: every channel's generated secrets get the same treatment (github's webhook secret is
88
- // the same class of value as telegram's); guided Lark credentials ride the same write as overwrites.
89
- // Feishu's irreversible credentials were already staged inside add-feishu.ts before bootstrap.
78
+ // Kind-neutral: every channel's generated secrets get the same treatment.
90
79
  const dotEnv = await appendChannelDotEnv(target, channelKind, { ...generated, ...created }, Object.keys(created ?? {}), ingress).catch(failStartup);
91
80
  if (dotEnv.unprotectedSecretsDir) {
92
81
  console.error(`[fastagent] note: ${dotEnv.unprotectedSecretsDir} (FASTAGENT_SECRETS_DIR) now holds a secret and has ` +
@@ -96,15 +85,16 @@ export async function runAddChannel(channelKind, dirArg, opts) {
96
85
  if (dotEnv.written.length > 0) {
97
86
  console.error(`[fastagent] wrote ${dotEnv.written.join(", ")} to ${envLabel}`);
98
87
  }
99
- const install = detectRuntime(target, await readPackageJson(target)).runtime === "bun" ? "bun install" : "npm install";
88
+ const install = detectRuntime(target, await readPackageJson(target).catch(failStartup)).runtime === "bun"
89
+ ? "bun install"
90
+ : "npm install";
100
91
  console.error(` next steps:`);
101
92
  console.error(` ${agentFromCwd === undefined ? install : `(cd ${agentFromCwd} && ${install})`} # if @fastagent-sh/fastagent is not installed yet`);
102
93
  for (const e of env) {
103
94
  if (dotEnv.alreadySet.includes(e.name))
104
95
  continue; // the user already has it — nothing to do
105
96
  if (dotEnv.written.includes(e.name)) {
106
- // Written, but its hint may still carry an action (github: paste the same value into the webhook
107
- // UI) — keep the variable visible instead of silently absorbing it.
97
+ // Written, but its hint may still carry an action — keep the variable visible instead of silently absorbing it.
108
98
  console.error(` ${e.name} — ${e.generate ? "generated and " : ""}written to ${envLabel} # ${e.hint}`);
109
99
  continue;
110
100
  }
@@ -112,8 +102,8 @@ export async function runAddChannel(channelKind, dirArg, opts) {
112
102
  const action = e.required ? "set" : "optionally set";
113
103
  console.error(` ${action} ${e.name}${value} in ${envLabel} # ${e.hint}`);
114
104
  }
115
- // Steps carry `{channel}`/`{tools}` path placeholders (their filenames are the scaffold's private
116
- // knowledge) — resolve them to the real agent-dir-relative locations here.
105
+ // Steps carry `{channel}`/`{tools}` path placeholders (their filenames are the scaffold's private knowledge) —
106
+ // resolve them to the real agent-dir-relative locations here.
117
107
  for (const s of steps) {
118
108
  console.error(` ${s.replace("{channel}", inAgent(relative(target, file))).replace("{tools}", inAgent("tools"))}`);
119
109
  }
@@ -123,14 +113,11 @@ export async function runAddChannel(channelKind, dirArg, opts) {
123
113
  else if (channelKind === "slack") {
124
114
  console.error(` fastagent dev --tunnel # ${opts.onboard === false ? "serve locally + print the URL to paste into the Slack app's Event Subscriptions" : "start the agent — then message it in Slack"}`);
125
115
  }
126
- else if (channelKind === "github") {
127
- console.error(` fastagent dev --tunnel # serve locally + print the URL for manual GitHub webhook setup`);
128
- }
129
116
  else if (channelKind !== "lark") {
130
117
  console.error(` fastagent dev --tunnel # serve locally + a public URL, auto-registering the webhook`);
131
118
  }
132
- // App-creation flows leave platform/tunnel sockets behind that would otherwise hold the one-shot
133
- // scaffold command open after all durable boundaries have completed — exit crisply.
119
+ // App-creation flows leave platform/tunnel sockets behind that would otherwise hold the one-shot scaffold command
120
+ // open after all durable boundaries have completed.
134
121
  process.exit(0);
135
122
  }
136
123
  async function resolveIngress(kind, file, existsAlready, raw) {
@@ -167,9 +154,7 @@ async function resolveIngress(kind, file, existsAlready, raw) {
167
154
  }
168
155
  const answer = await select({
169
156
  message: `How should ${kind === "feishu" ? "Feishu" : "Lark"} deliver events?`,
170
- // The default must match the non-interactive branch above. Webhook is the cheaper side to be
171
- // wrong on: its credentials are a superset of websocket's (App ID/Secret plus the Verification
172
- // Token), and that token has no read API — a websocket app moving to webhook must re-acquire it.
157
+ // The default must match the non-interactive branch above.
173
158
  initialValue: "webhook",
174
159
  options: [
175
160
  {
@@ -228,8 +213,7 @@ async function resolveGroupBehavior(kind, raw) {
228
213
  export async function runAddSkill(source, dirArg, opts) {
229
214
  const { agentDir: target } = placementOrExit(resolve(dirArg));
230
215
  if (!source) {
231
- // A missing source is a usage error (exit 2), but the guide is worth more than a bare
232
- // missing-argument line — the common path (writing your own skill) needs no command at all.
216
+ // A missing source is a usage error (exit 2), but the guide is worth more than a bare missing-argument line.
233
217
  failUsage(`add a skill — two ways:\n` +
234
218
  ` 1. write your own (vibe): create skills/<name>/SKILL.md with name + description\n` +
235
219
  ` frontmatter; it's auto-discovered. No command needed — this is the common path.\n` +
@@ -240,6 +224,7 @@ export async function runAddSkill(source, dirArg, opts) {
240
224
  ` --update overwrites an existing skill (re-fetch from source); review with git diff`);
241
225
  }
242
226
  // Skills are agent surface — vendored into the agent dir's `skills/`.
227
+ enterAgentEnv(target); // a git-ref source is a network fetch (giget uses global fetch)
243
228
  const { name, description, dest, hasScripts, diagnostics, overwritten } = await vendorSkill(target, source, {
244
229
  update: opts.update ?? false,
245
230
  }).catch(failStartup);
@@ -1,4 +1,3 @@
1
1
  export declare function runChat(dirArg: string, opts: {
2
2
  model?: string;
3
- authPath?: string;
4
3
  }): Promise<void>;
@@ -2,14 +2,13 @@
2
2
  import { failStartup } from "../fail.js";
3
3
  import { enterAgentCommand } from "../shared.js";
4
4
  export async function runChat(dirArg, opts) {
5
- // Chat authenticates through fastagent's credential store like every other command (the shared
6
- // session builder injects it — see engines/pi/session-builder.ts), so the first-run picker and its
7
- // inline login apply here too.
5
+ // Chat authenticates through fastagent's credential store like every other command (the shared session builder
6
+ // injects it — see engines/pi/session-builder.ts), so the first-run picker and its inline login apply here too.
8
7
  const placement = await enterAgentCommand(dirArg, opts);
9
- // Run the chat process AT the workspace: pi resolves a session's cwd as `header.cwd ?? process.cwd()`,
10
- // so aligning process.cwd() with the workspace keeps a cwd-less session on it. Paths are absolute.
8
+ // Run the chat process AT the workspace: pi resolves a session's cwd as `header.cwd ?? process.cwd()`, so aligning
9
+ // process.cwd() with the workspace keeps a cwd-less session on it.
11
10
  process.chdir(placement.workspace);
12
11
  // Lazy-import: chat pulls pi's interactive TUI module graph; headless start/dev never need it.
13
12
  const { runPiChat } = await import("../../engines/pi/chat.js");
14
- await runPiChat(placement.workspace, { model: opts.model, authPath: opts.authPath }).catch(failStartup);
13
+ await runPiChat(placement.agentDir, { model: opts.model }).catch(failStartup);
15
14
  }
@@ -1,16 +1,15 @@
1
1
  /**
2
- * `deploy agentcore`: one CloudFormation stack (runtime + forwarder Lambda + EventBridge schedules);
3
- * no public URL and no resident process — see deploy/agentcore/plan.ts for the topology decisions.
2
+ * `deploy agentcore`: one CloudFormation stack (runtime + forwarder Lambda behind a Function URL + EventBridge
3
+ * schedules); no resident process.
4
4
  */
5
5
  import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
6
6
  import { tmpdir } from "node:os";
7
7
  import { basename, join } from "node:path";
8
- import { MAX_WEBHOOK_BODY_BYTES } from "../../../channels/agentcore-limits.js";
9
- import { FORWARDER_FILE, TEMPLATE_FILE, agentcoreName, isGeneratedAgentcoreTemplate, planAgentcoreDeploy, } from "../../../deploy/agentcore/plan.js";
8
+ import { FORWARDER_FILE, TEMPLATE_FILE, agentcoreName, forwarderLogGroup, ingressSessionId, isGeneratedAgentcoreTemplate, planAgentcoreDeploy, } from "../../../deploy/agentcore/plan.js";
10
9
  import { deployAgentcoreRun } from "../../../deploy/agentcore/run.js";
11
10
  import { spawnRunner } from "../../../deploy/runner.js";
12
- import { exists } from "../../../paths.js";
13
- import { loadSchedules } from "../../../schedule/discover.js";
11
+ import { SECRET_FILE_MODE, exists } from "../../../paths.js";
12
+ import { loadRoutines } from "../../../schedule/discover.js";
14
13
  import { failStartup } from "../../fail.js";
15
14
  import { carryCredentials, gateOnModelCredential, registrarsFor } from "./shared.js";
16
15
  /** A copy/paste-safe POSIX shell argument for the command hints deploy prints. */
@@ -21,10 +20,9 @@ export const agentcoreHost = {
21
20
  isOurs: (path, content) => path.endsWith(TEMPLATE_FILE) && isGeneratedAgentcoreTemplate(content),
22
21
  async deploy(ctx) {
23
22
  const { opts, agentDir, workspace, config, channels, longConnectionChannels, pre, write } = ctx;
24
- const { modelAuth, modelKeyInDefinition, authPath, container, extraSecrets } = pre;
25
- // Long-connection channels are STRUCTURALLY unsupported: the connection is the ingress, and a
26
- // reclaimed session has nothing to wake it — events in the gap are silently lost. `--run` gates
27
- // (deploying a channel that can't connect); generate-only warns and prints the runbook.
23
+ const { modelAuth, modelKeyInDefinition, authPath, container, declaredSecrets, values, valueFile } = pre;
24
+ // Long-connection channels are STRUCTURALLY unsupported: the connection is the ingress, and a reclaimed session
25
+ // has nothing to wake it.
28
26
  if (longConnectionChannels.length > 0) {
29
27
  const msg = `long-connection channel (${longConnectionChannels.map((c) => c.name).join(", ")}) cannot run on AgentCore — there is no ` +
30
28
  `resident process to hold the connection, and nothing wakes a reclaimed session. Switch the channel ` +
@@ -33,18 +31,23 @@ export const agentcoreHost = {
33
31
  failStartup(new Error(`deploy stopped: ${msg}`));
34
32
  console.error(`[fastagent] warn: ${msg}`);
35
33
  }
36
- // selfSchedule is fully supported: pending wake-ups are mirrored into one-shot EventBridge
37
- // schedules via the forwarder (the wake-alarm mechanism — see deploy/agentcore/plan.ts).
38
- // Schedules feed EventBridge rules — parsed facts (cron/tz), not just file names, so a bad file
39
- // must surface here (a schedule silently missing its rule would never fire).
40
- const loaded = await loadSchedules(agentDir).catch(failStartup);
34
+ // Host capability limit, stated before the image is built rather than left to a line in CloudWatch.
35
+ if (config.sessionControl === true) {
36
+ console.error(`[fastagent] warn: sessionControl: true has no effect on AgentCore — /control/* is not served here. This ` +
37
+ `host's only public ingress relays anonymous traffic to the container as trusted (the forwarder holds the ` +
38
+ `ingress secret and attaches it), and the plane has nothing of its own to verify. Steer sessions from a ` +
39
+ `host that serves it behind your own auth (docs/design/session-control.md §14).`);
40
+ }
41
+ // Wake-ups are a default capability here as everywhere: pending ones are mirrored into one-shot EventBridge
42
+ // schedules via the forwarder (the wake-alarm mechanism — see deploy/agentcore/plan.ts), which is why every stack
43
+ // has one.
44
+ const loaded = await loadRoutines(agentDir).catch(failStartup);
41
45
  if (loaded.failures.length > 0) {
42
- failStartup(new Error(`deploy stopped: cannot load schedules: ${loaded.failures.map((x) => `${x.label}: ${x.message}`).join("; ")}`));
46
+ failStartup(new Error(`deploy stopped: cannot load routines: ${loaded.failures.map((x) => `${x.label}: ${x.message}`).join("; ")}`));
43
47
  }
44
48
  const acName = agentcoreName(basename(workspace));
45
49
  // Every derived AWS name embeds acName; the tightest ceiling is the Lambda function name
46
- // (`fastagent-<name>-forwarder` ≤ 64 chars). Gate the base instead of silently truncating —
47
- // truncation would break the redeploy identity (a renamed stack starts blank state).
50
+ // (`fastagent-<name>-forwarder` ≤ 64 chars).
48
51
  if (acName.length > 40) {
49
52
  failStartup(new Error(`deploy stopped: the directory name maps to "${acName}" (${acName.length} chars) — AWS resource ` +
50
53
  `names derived from it exceed their limits past 40 chars. Deploy from a shorter directory name.`));
@@ -53,42 +56,35 @@ export const agentcoreHost = {
53
56
  name: acName,
54
57
  modelAuth,
55
58
  channels,
56
- extraSecrets,
57
- schedules: loaded.schedules.map((s) => ({ name: s.name, cron: s.cron, tz: s.tz })),
58
- selfSchedule: !!config.selfSchedule,
59
+ secrets: pre.secrets,
60
+ // ONLY THE ONES WITH A CRON become rules. A routine without one is reached by NAME, through this host's
61
+ // IAM-gated `routine-run` envelope (channels/agentcore.ts) rather than a route — so it needs no rule and
62
+ // loses nothing by not having one.
63
+ schedules: loaded.routines.flatMap((r) => r.cron === undefined ? [] : [{ name: r.name, cron: r.cron, ...(r.tz !== undefined ? { tz: r.tz } : {}) }]),
64
+ idleTimeoutSeconds: config.deploy?.agentcore?.idleTimeoutSeconds,
59
65
  ...container,
60
66
  });
61
67
  for (const u of plan.untranslatableSchedules) {
62
- // Same discipline as Fly's kept-toml time-trigger gate: a deploy whose schedule silently never
63
- // fires is worse than a stopped deploy — nothing fails visibly when the instant passes.
68
+ // Same discipline as Fly's kept-toml time-trigger gate: a deploy whose schedule silently never fires is worse
69
+ // than a stopped deploy.
64
70
  const msg = `schedule "${u.name}" cannot be expressed as an EventBridge rule — ${u.reason}`;
65
71
  if (opts.run)
66
72
  failStartup(new Error(`deploy stopped: ${msg}`));
67
73
  console.error(`[fastagent] warn: ${msg} — it will NOT fire on this deployment`);
68
74
  }
69
- // Host capability limit, stated at plan time. GitHub's own webhook contract is 25 MiB, but a
70
- // Lambda Function URL request caps at 6 MB — so on this host a large payload cannot arrive at
71
- // all. Better a sentence here than an opaque 502 the first time someone pushes a big diff.
72
- if (channels.some((channel) => channel.name === "github")) {
73
- console.error(`[fastagent] note: on AgentCore a webhook body is capped at ~${Math.round(MAX_WEBHOOK_BODY_BYTES / (1 << 20))} MiB ` +
74
- `(the forwarder's Function URL limit); the GitHub channel accepts 25 MiB on a resident host, so the largest ` +
75
- `payloads are rejected here rather than delivered`);
76
- }
77
- // The template IS the topology (EventBridge rules, wake wiring, secrets) — a kept generated
78
- // template that no longer matches the definition would deploy a stack silently missing the
79
- // difference (a new schedule with no rule never fires: the exact miss the gate above stops).
80
- // A hand-written template (marker removed) is the operator's own — kept, never gated.
75
+ // The template IS the topology (EventBridge rules, wake wiring, secrets).
81
76
  const templateArtifact = plan.artifacts.find((a) => a.path.endsWith(TEMPLATE_FILE));
82
77
  const templateHome = join(workspace, templateArtifact?.path ?? TEMPLATE_FILE);
83
78
  if (!opts.force && templateArtifact && (await exists(templateHome))) {
84
79
  const existing = await readFile(templateHome, "utf8");
85
80
  if (isGeneratedAgentcoreTemplate(existing) && existing !== templateArtifact.content) {
86
- const msg = `${templateArtifact.path} no longer matches this definition (channels/schedules/selfSchedule changed) — ` +
87
- `the kept template would silently drop the difference. Pass --force to regenerate (hand edits are lost), ` +
88
- `or delete the file.`;
89
- if (opts.run)
90
- failStartup(new Error(`deploy stopped: ${msg}`));
91
- console.error(`[fastagent] warn: ${msg}`);
81
+ // WHY it matters here, not WHETHER to stop: the template IS the topology, so a kept one drops the
82
+ // difference rather than degrading visibly. Stopping a `--run` on a stale artifact is the dispatcher's
83
+ // rule (deploy.ts) for every host and every path — stating it a second time here is how the two
84
+ // wordings drift apart.
85
+ console.error(`[fastagent] warn: ${templateArtifact.path} no longer matches this definition ` +
86
+ `(channels/routines or a deploy.agentcore setting changed) — the kept template would ` +
87
+ `silently drop the difference.`);
92
88
  }
93
89
  }
94
90
  await write(plan.artifacts, {
@@ -105,7 +101,9 @@ export const agentcoreHost = {
105
101
  modelKeyInDefinition,
106
102
  authPath,
107
103
  channels,
108
- extraSecrets,
104
+ declaredSecrets,
105
+ values,
106
+ valueFile,
109
107
  topology: plan.topology,
110
108
  });
111
109
  }
@@ -113,33 +111,23 @@ export const agentcoreHost = {
113
111
  return;
114
112
  },
115
113
  };
116
- /**
117
- * `deploy agentcore --run`: drive aws + docker to completion. Mirrors the fly driver — same
118
- * credential carry via {@link carryCredentials}, same runner seam (spawned `aws` + `docker`, cwd = the
119
- * workspace so the build context is the agent). The AgentCore-specific sequence (identity → buildx →
120
- * ECR → CloudFormation → outputs → webhooks) lives in {@link deployAgentcoreRun}; the params temp
121
- * file (secret values off argv) is created here — 0600, removed after the run either way.
122
- */
114
+ /** `deploy agentcore --run`: drive aws + docker to completion. */
123
115
  async function runDeployAgentcore(params) {
124
116
  const { agentDir, workspace, agentPrefix, name, channels, topology } = params;
125
117
  const { secrets, missingSecrets, needsModelCredential } = await carryCredentials(params);
126
- // The wake-alarm shared secret (container ↔ forwarder). Minted fresh each run — both sides receive
127
- // the SAME parameter, so rotation is atomic; it never needs to be remembered locally.
128
- if (topology.wakeAlarms)
129
- secrets.FASTAGENT_WAKE_SECRET = crypto.randomUUID();
130
- // The forwarder→runtime ingress secret: what makes an envelope the forwarder's rather than any IAM
131
- // principal's. Minted fresh each run — both sides receive the SAME parameter, so rotation is atomic.
132
- if (topology.forwarder)
133
- secrets.FASTAGENT_INGRESS_SECRET = crypto.randomUUID();
118
+ // The wake-alarm shared secret (container ↔ forwarder).
119
+ secrets.FASTAGENT_WAKE_SECRET = crypto.randomUUID();
120
+ // The forwarder→runtime ingress secret: what makes an envelope the forwarder's rather than any IAM principal's.
121
+ secrets.FASTAGENT_INGRESS_SECRET = crypto.randomUUID();
134
122
  gateOnModelCredential(needsModelCredential);
135
- // The params temp dir holds the ONE file carrying secret values (file:// parameter-overrides —
136
- // never argv); 0700/0600 and removed after the run, success or gate.
123
+ // The params temp dir holds the ONE file carrying secret values (file:// parameter-overrides — never argv);
124
+ // 0700/0600 and removed after the run, success or gate.
137
125
  const paramsDir = await mkdtemp(join(tmpdir(), "fastagent-agentcore-"));
138
126
  try {
139
127
  const outcome = await deployAgentcoreRun({
140
128
  name,
141
129
  templatePath: `${agentPrefix}${TEMPLATE_FILE}`,
142
- dockerfilePath: agentPrefix ? `${agentPrefix}Dockerfile` : undefined,
130
+ dockerfilePath: `${agentPrefix}Dockerfile`,
143
131
  tag: new Date()
144
132
  .toISOString()
145
133
  .replace(/[-:.TZ]/g, "")
@@ -147,11 +135,12 @@ async function runDeployAgentcore(params) {
147
135
  region: process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION,
148
136
  secrets,
149
137
  missingSecrets,
138
+ valueFile: params.valueFile,
150
139
  channels,
151
140
  topology,
152
141
  }, spawnRunner("aws", workspace), spawnRunner("docker", workspace), (m) => console.error(`[fastagent] ${m}`), async (content) => {
153
142
  const path = join(paramsDir, "params.json");
154
- await writeFile(path, content, { mode: 0o600 });
143
+ await writeFile(path, content, { mode: SECRET_FILE_MODE });
155
144
  return path;
156
145
  }, async (bytes) => {
157
146
  const path = join(paramsDir, "forwarder.zip");
@@ -165,11 +154,17 @@ async function runDeployAgentcore(params) {
165
154
  console.error(`[fastagent] webhook ingress → ${outcome.url}`);
166
155
  const logsDir = shellArg(workspace);
167
156
  console.error(`[fastagent] runtime logs → fastagent logs agentcore ${logsDir} --follow`);
168
- if (topology.forwarder) {
169
- console.error(`[fastagent] forwarder logs → fastagent logs agentcore ${logsDir} --source forwarder --follow`);
170
- }
157
+ console.error(`[fastagent] forwarder logs → fastagent logs agentcore ${logsDir} --source forwarder --follow`);
158
+ // `--run` never prints the runbook, and this is the ONE step in it that nothing else will remind anyone of: a
159
+ // log group is created by whatever writes it, CloudWatch keeps log data indefinitely, and these logs are where
160
+ // a failed turn's reason lives. The runtime's group name is only known after discovery, so it is named by the command that
161
+ // resolves it — spelled out, because the line above it may be the forwarder's.
162
+ console.error(`[fastagent] logs are kept FOREVER until you say otherwise: aws logs put-retention-policy ` +
163
+ `--log-group-name <the group \`fastagent logs agentcore ${logsDir} --follow\` resolves> --retention-in-days 14`);
164
+ console.error(`[fastagent] ...and for the forwarder: aws logs put-retention-policy ` +
165
+ `--log-group-name ${forwarderLogGroup(name)} --retention-in-days 14`);
171
166
  console.error(`[fastagent] invoke: aws bedrock-agentcore invoke-agent-runtime --agent-runtime-arn ${outcome.runtimeArn} \\\n` +
172
- ` --runtime-session-id "my-conversation-000000000000000000" \\\n` +
167
+ ` --runtime-session-id "${ingressSessionId(name)}" \\\n` +
173
168
  ` --payload '{"kind":"invoke","session":"cli","text":"hello"}' --cli-binary-format raw-in-base64-out /dev/stdout`);
174
169
  }
175
170
  finally {
@@ -1,15 +1,13 @@
1
- /**
2
- * `deploy docker`: one app service + loopback port + state volume, as a user-owned Compose file.
3
- * `--tunnel` shapes the generated topology with an optional Quick Tunnel service; `--run` alone
4
- * decides whether Docker receives side effects.
5
- */
6
- import { basename, join } from "node:path";
1
+ /** `deploy docker`: one app service + loopback port + state volume, as a user-owned Compose file. */
2
+ import { mkdir, writeFile } from "node:fs/promises";
3
+ import { basename, dirname, join, relative, resolve } from "node:path";
7
4
  import { webhookPaths } from "../../../deploy/channel-ingress.js";
8
5
  import { composeHasTunnelService, isGeneratedCompose, planDockerDeploy, toDockerProjectName, } from "../../../deploy/docker/plan.js";
9
6
  import { deployDockerRun } from "../../../deploy/docker/run.js";
10
7
  import { spawnRunner } from "../../../deploy/runner.js";
11
8
  import { openExternalUrl } from "../../../open-url.js";
12
- import { readTextIfExists, resolveStateRoot } from "../../../paths.js";
9
+ import { SECRETS_DIRNAME, SECRET_FILE_MODE, exists, readTextIfExists, resolveStateRoot, } from "../../../paths.js";
10
+ import { dotEnvPath } from "../../../env.js";
13
11
  import { announceWebhooks } from "../../../tunnel.js";
14
12
  import { failStartup } from "../../fail.js";
15
13
  import { carryCredentials } from "./shared.js";
@@ -17,7 +15,37 @@ export const dockerHost = {
17
15
  isOurs: (path, content) => path.endsWith("fastagent.compose.yml") && isGeneratedCompose(content),
18
16
  async deploy(ctx) {
19
17
  const { opts, agentDir, workspace, channels, webhookChannels, pre, write } = ctx;
20
- const { modelAuth, modelKeyInDefinition, authPath, container, port, extraSecrets } = pre;
18
+ const { modelAuth, modelKeyInDefinition, authPath, container, port, declaredSecrets, values, valueFile } = pre;
19
+ // The generated Compose names `<agent>/.secrets/.env` unconditionally, so it has to be there — Compose refuses
20
+ // an `env_file` entry pointing at a missing path, and this floor predates `required: false` (Compose 2.24).
21
+ // Creating it empty is honest: a deployment that declares nothing declares it in an empty file.
22
+ const composeValueFile = join(agentDir, SECRETS_DIRNAME, ".env");
23
+ await mkdir(dirname(composeValueFile), { recursive: true });
24
+ if (!(await exists(composeValueFile)))
25
+ await writeFile(composeValueFile, "", { mode: SECRET_FILE_MODE });
26
+ // The pre-flight read whatever `FASTAGENT_SECRETS_DIR` resolved to; the committed Compose cannot, because a
27
+ // builder's path would not mean the same thing anywhere else. Under `--run` the two files disagreeing is a
28
+ // DETERMINISTIC failure and gates like every other one: the missing-values gate would pass on the file this
29
+ // machine reads while `compose up` starts a container whose declared secrets and model are all absent.
30
+ if (resolve(dotEnvPath(agentDir)) !== resolve(composeValueFile)) {
31
+ const issue = `FASTAGENT_SECRETS_DIR points this run's values at ${valueFile}, but the generated Compose reads ` +
32
+ `${relative(workspace, composeValueFile)} (a committed artifact cannot carry this machine's path), so the ` +
33
+ `container would start with none of them. Unset FASTAGENT_SECRETS_DIR, or put the values in that file.`;
34
+ if (opts.run)
35
+ failStartup(new Error(`deploy stopped: ${issue}`));
36
+ console.error(`[fastagent] warn: ${issue}`);
37
+ }
38
+ // Compose interpolates `$VAR` INSIDE env_file values (`format: raw` needs Compose 2.30), so a credential
39
+ // containing `$` reaches the container rewritten — silently, and differently from what this pre-flight read.
40
+ // No escaping advice: this ONE file is also read literally by `dev`/`start` (`parseEnvContent`) and pushed
41
+ // as-is by every other host, so `$$` would fix Docker by corrupting all of them.
42
+ const dollarValues = [...values].filter(([, value]) => value.includes("$")).map(([name]) => name);
43
+ if (dollarValues.length > 0) {
44
+ console.error(`[fastagent] warn: ${dollarValues.join(", ")} contain "$", which Compose expands when reading ${valueFile} ` +
45
+ `(an undefined name becomes empty), so the container sees a different value. "$$" escapes it for Compose ` +
46
+ `ONLY — \`fastagent dev\`/\`start\` and the other hosts read this file literally and would keep the extra ` +
47
+ `"$". Prefer a value without "$".`);
48
+ }
21
49
  const hasDeclaredChannels = channels.length > 0;
22
50
  const projectName = toDockerProjectName(basename(workspace));
23
51
  const dockerPlan = (tunnel) => planDockerDeploy({
@@ -26,7 +54,8 @@ export const dockerHost = {
26
54
  modelAuth,
27
55
  channels,
28
56
  tunnel,
29
- extraSecrets,
57
+ secrets: pre.secrets,
58
+ valueFile,
30
59
  ...container,
31
60
  });
32
61
  const requestedTunnel = !!opts.tunnel && (!hasDeclaredChannels || webhookChannels.length > 0);
@@ -34,12 +63,12 @@ export const dockerHost = {
34
63
  console.error(`[fastagent] note: --tunnel skipped — every channel uses a long connection`);
35
64
  }
36
65
  let plan = dockerPlan(requestedTunnel);
37
- // An existing Compose file is authoritative: shape its comparison/runbook from the topology on disk,
38
- // regardless of the current flag. `--force` is the explicit reset to the requested generated shape.
66
+ // An existing Compose file is authoritative: shape its comparison/runbook from the topology on disk, regardless
67
+ // of the current flag.
39
68
  const composeFile = join(workspace, plan.composePath);
40
69
  let keptWithoutRequestedTunnel = false;
41
- // Same ownership question as fly.toml: a hand-owned compose file survives --force, so the plan must
42
- // describe the topology that will actually be there.
70
+ // Same ownership question as fly.toml: a hand-owned compose file survives --force, so the plan must describe the
71
+ // topology that will actually be there.
43
72
  const composeText = await readTextIfExists(composeFile).catch(failStartup);
44
73
  if (composeText !== undefined && (!opts.force || !isGeneratedCompose(composeText))) {
45
74
  const existingHasTunnel = composeHasTunnelService(composeText);
@@ -58,7 +87,9 @@ export const dockerHost = {
58
87
  modelKeyInDefinition,
59
88
  authPath,
60
89
  channels,
61
- extraSecrets,
90
+ declaredSecrets,
91
+ values,
92
+ valueFile,
62
93
  });
63
94
  }
64
95
  if (keptWithoutRequestedTunnel) {
@@ -70,9 +101,8 @@ export const dockerHost = {
70
101
  },
71
102
  };
72
103
  /**
73
- * `deploy docker --run`: carry local credentials into Compose's child environment, then reconcile the
74
- * user-owned local topology. Docker owns container/network/volume lifecycle. A Compose tunnel service,
75
- * when present, yields an ephemeral URL that reuses the same webhook announcer as `dev --tunnel`.
104
+ * `deploy docker --run`: carry local credentials into Compose's child environment, then reconcile the user-owned local
105
+ * topology.
76
106
  */
77
107
  async function runDeployDocker(params) {
78
108
  const { agentDir, workspace, composeFile, port, requireTunnel, channels } = params;
@@ -82,6 +112,7 @@ async function runDeployDocker(params) {
82
112
  port,
83
113
  secrets,
84
114
  missingSecrets,
115
+ valueFile: params.valueFile,
85
116
  needsModelCredential,
86
117
  requireTunnel,
87
118
  announce: (tunnelUrl) => announceWebhooks(agentDir, tunnelUrl, channels, {
@@ -90,9 +121,7 @@ async function runDeployDocker(params) {
90
121
  }),
91
122
  }, spawnRunner("docker", workspace), (message) => console.error(`[fastagent] ${message}`));
92
123
  const compose = `docker compose -f ${composeFile}`;
93
- // Compose reached "up" iff the driver could report where it is. The gates before that point
94
- // (no Docker CLI, no daemon, missing secrets) must not be preceded by `docker compose logs`
95
- // instructions for containers that do not exist; the health-check gate names its own logs command.
124
+ // Compose reached "up" iff the driver could report where it is.
96
125
  const isUp = outcome.ok || outcome.url !== undefined || outcome.tunnelUrl !== undefined;
97
126
  if (outcome.url)
98
127
  console.error(`[fastagent] running → ${outcome.url}`);
@@ -100,9 +129,7 @@ async function runDeployDocker(params) {
100
129
  console.error(`[fastagent] logs: ${compose} logs -f agent`);
101
130
  console.error(`[fastagent] stop: ${compose} down (state volume is kept)`);
102
131
  }
103
- // BEFORE failStartup: a registration gate says "re-run this deploy", and a re-run rebuilds the
104
- // tunnel service — the operator needs to know the URL will be a different one, or they will read
105
- // the retry as re-registering the same address.
132
+ // BEFORE failStartup: a registration gate says "re-run this deploy", and a re-run rebuilds the tunnel service.
106
133
  if (outcome.tunnelUrl) {
107
134
  console.error(`[fastagent] note: Quick Tunnel URLs are ephemeral — after the tunnel container/Docker daemon ` +
108
135
  `restarts, re-run this deploy so webhooks receive the new URL`);