@fastagent-sh/fastagent 0.21.0 → 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 -43
  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,17 +1,4 @@
1
- /**
2
- * A minimal, DETERMINISTIC single-file ZIP writer — just enough to package the forwarder Lambda.
3
- *
4
- * WHY NOT A DEPENDENCY: this runs in the deploy path of a tool whose whole promise is "no build
5
- * step"; pulling an archiver in for ~40 lines of well-specified format would be the wrong trade.
6
- * WHY NOT `zip(1)`: it is absent on plenty of machines (and on Windows), and its output embeds the
7
- * current time — the forwarder's S3 key is CONTENT-HASHED, so identical source must produce
8
- * identical bytes or every deploy would look like a code change to CloudFormation.
9
- *
10
- * STORE (no compression) on purpose: the payload is a few KB of JavaScript, and the format's stored
11
- * form is trivially verifiable. Fixed 1980-01-01 timestamps keep the output byte-stable.
12
- */
1
+ /** A minimal, DETERMINISTIC single-file ZIP writer — just enough to package the forwarder Lambda. */
13
2
  import { Buffer } from "node:buffer";
14
- /**
15
- * Build a ZIP archive containing exactly one stored entry. Byte-identical for identical inputs.
16
- */
3
+ /** Build a ZIP archive containing exactly one stored entry. */
17
4
  export declare function zipSingleFile(name: string, content: Buffer): Buffer;
@@ -1,23 +1,10 @@
1
- /**
2
- * A minimal, DETERMINISTIC single-file ZIP writer — just enough to package the forwarder Lambda.
3
- *
4
- * WHY NOT A DEPENDENCY: this runs in the deploy path of a tool whose whole promise is "no build
5
- * step"; pulling an archiver in for ~40 lines of well-specified format would be the wrong trade.
6
- * WHY NOT `zip(1)`: it is absent on plenty of machines (and on Windows), and its output embeds the
7
- * current time — the forwarder's S3 key is CONTENT-HASHED, so identical source must produce
8
- * identical bytes or every deploy would look like a code change to CloudFormation.
9
- *
10
- * STORE (no compression) on purpose: the payload is a few KB of JavaScript, and the format's stored
11
- * form is trivially verifiable. Fixed 1980-01-01 timestamps keep the output byte-stable.
12
- */
1
+ /** A minimal, DETERMINISTIC single-file ZIP writer — just enough to package the forwarder Lambda. */
13
2
  import { Buffer } from "node:buffer";
14
3
  import { crc32 } from "node:zlib";
15
4
  /** MS-DOS epoch (1980-01-01 00:00:00) — the earliest the format can express, and a constant. */
16
5
  const DOS_DATE = 0x0021;
17
6
  const DOS_TIME = 0x0000;
18
- /**
19
- * Build a ZIP archive containing exactly one stored entry. Byte-identical for identical inputs.
20
- */
7
+ /** Build a ZIP archive containing exactly one stored entry. */
21
8
  export function zipSingleFile(name, content) {
22
9
  const nameBytes = Buffer.from(name, "utf8");
23
10
  const sum = crc32(content);
@@ -49,8 +36,7 @@ export function zipSingleFile(name, content) {
49
36
  centralHeader.writeUInt16LE(0, 32); // file comment length
50
37
  centralHeader.writeUInt16LE(0, 34); // disk number start
51
38
  centralHeader.writeUInt16LE(0, 36); // internal attributes
52
- // External attributes: regular file, rw-r--r-- in the high 16 bits. `>>> 0` because JS bitwise
53
- // operators are 32-bit SIGNED — the shifted value is negative without it.
39
+ // External attributes: regular file, rw-r--r-- in the high 16 bits.
54
40
  centralHeader.writeUInt32LE((0o100644 << 16) >>> 0, 38);
55
41
  centralHeader.writeUInt32LE(0, 42); // local header offset (single entry: always 0)
56
42
  const centralSize = centralHeader.byteLength + nameBytes.byteLength;
@@ -0,0 +1,21 @@
1
+ import type { DeployReport } from "./preflight.ts";
2
+ /** Workspace-relative paths the build context may hold and the image must not. */
3
+ export interface BuildContextPaths {
4
+ /** The resolved machinery dirs the generated ignore excludes by path (ContainerInput.machineryPaths). */
5
+ machineryPaths: string[];
6
+ /** Credential files that exist right now — a kept ignore file that lets one through bakes it. */
7
+ leakCandidates: string[];
8
+ /** The state root, when it exists inside the context. */
9
+ stateShips?: string;
10
+ /** The node_modules dirs that exist, the agent's and the workspace's. */
11
+ depDirs: string[];
12
+ }
13
+ export declare function buildContextPaths(workspace: string, agentDir: string, agentPrefix: string, authPath: string): Promise<BuildContextPaths>;
14
+ /** Interrogate BOTH ignore files deploy emits, when the author kept them. */
15
+ export declare function checkKeptIgnoreFiles(ctx: {
16
+ workspace: string;
17
+ agentDir: string;
18
+ agentPrefix: string;
19
+ force: boolean;
20
+ paths: BuildContextPaths;
21
+ }, report: DeployReport): Promise<void>;
@@ -0,0 +1,137 @@
1
+ /**
2
+ * What a deploy's BUILD CONTEXT (the workspace) holds that must not ship, and whether an ignore file the author kept
3
+ * still keeps it out. A kept workspace-root `.dockerignore` silently replaces the generated one's protections, so the
4
+ * pre-flight asks it the same questions the generated one answers by construction.
5
+ */
6
+ import { readdir } from "node:fs/promises";
7
+ import { basename, isAbsolute, join, relative, sep } from "node:path";
8
+ import ignore from "ignore";
9
+ import { exists, readTextIfExists, resolveSecretsDir, resolveStateRoot } from "../paths.js";
10
+ import { isGeneratedDockerignore } from "./container.js";
11
+ /**
12
+ * "Would docker's packer drop this path?" — built from a `.dockerignore`'s text via the `ignore` matcher (the same
13
+ * library the workspace ignore files use), so `!` negation and last-match-wins are the library's problem, not ours.
14
+ */
15
+ function dockerignoreMatcher(text) {
16
+ const anchored = text
17
+ .split("\n")
18
+ .map((raw) => {
19
+ const line = raw.trim();
20
+ if (line === "" || line.startsWith("#"))
21
+ return line;
22
+ const negated = line.startsWith("!");
23
+ const pattern = negated ? line.slice(1) : line;
24
+ if (pattern.startsWith("/") || pattern.startsWith("**/"))
25
+ return line;
26
+ return `${negated ? "!" : ""}/${pattern}`;
27
+ })
28
+ .join("\n");
29
+ const matcher = ignore({ ignorecase: false }).add(anchored);
30
+ return (path) => matcher.ignores(path);
31
+ }
32
+ export async function buildContextPaths(workspace, agentDir, agentPrefix, authPath) {
33
+ const inContext = (p) => {
34
+ const rel = relative(workspace, p);
35
+ return rel === "" || rel.startsWith("..") || isAbsolute(rel) ? undefined : rel.split(sep).join("/");
36
+ };
37
+ // The secrets DIR is the unit of RESPONSIBILITY, but never the unit of the leak QUESTION below.
38
+ const secretsRel = inContext(resolveSecretsDir(agentDir));
39
+ const authRel = inContext(authPath);
40
+ const authElsewhere = authRel !== undefined && (secretsRel === undefined || !authRel.startsWith(`${secretsRel}/`));
41
+ const secretPaths = [...(secretsRel ? [secretsRel] : []), ...(authElsewhere ? [authRel] : [])];
42
+ // ONE rule for every checked path: a file that is not there cannot be baked, so gating on it would be a refusal
43
+ // about a spelling rather than about what would ship (an agent that has never run `login` has no auth.json).
44
+ const present = async (rels) => {
45
+ const found = [];
46
+ for (const rel of rels)
47
+ if (await exists(join(workspace, rel)))
48
+ found.push(rel);
49
+ return found;
50
+ };
51
+ // State gets the same treatment (a custom in-tree FASTAGENT_STATE_DIR is invisible to the name-based `**/.state`),
52
+ // at warn level.
53
+ const stateRel = inContext(resolveStateRoot(agentDir));
54
+ // Existence gates the WARNING, never the generated exclude (same split as secretPaths vs leakCandidates).
55
+ const stateShips = stateRel !== undefined && (await exists(join(workspace, stateRel))) ? stateRel : undefined;
56
+ // The `.env` family at the two levels fastagent is RESPONSIBLE for: the agent dir and the workspace root.
57
+ const dotEnvFiles = async (relDir) => {
58
+ const names = await readdir(join(workspace, relDir || ".")).catch(() => []);
59
+ // POSIX separators, like every other context-relative path here (`inContext`).
60
+ return names
61
+ .filter((n) => (n === ".env" || n.startsWith(".env.")) && n !== ".env.example")
62
+ .map((n) => join(relDir, n).split(sep).join("/"));
63
+ };
64
+ const envFiles = (await Promise.all([...new Set(["", agentPrefix])].map(dotEnvFiles))).flat();
65
+ // Everything ACTUALLY inside the secrets dir, minus the two tracked scaffolds the image ships on purpose (they
66
+ // carry no values; the generated ignore re-includes them by name).
67
+ const secretDirFiles = async (dirRel) => {
68
+ const entries = await readdir(join(workspace, dirRel), { withFileTypes: true }).catch(() => []);
69
+ const files = [];
70
+ for (const entry of entries) {
71
+ if (entry.name === ".gitignore" || entry.name === ".env.example")
72
+ continue;
73
+ if (entry.isDirectory())
74
+ files.push(...(await secretDirFiles(`${dirRel}/${entry.name}`)));
75
+ else
76
+ files.push(`${dirRel}/${entry.name}`);
77
+ }
78
+ return files;
79
+ };
80
+ const leakCandidates = [
81
+ ...(secretsRel ? await secretDirFiles(secretsRel) : []),
82
+ ...(await present(authElsewhere && authRel !== undefined ? [authRel] : [])),
83
+ ...envFiles,
84
+ ];
85
+ // Same existence rule: a node_modules that is not there cannot be uploaded.
86
+ const depDirs = await present([...new Set([`${agentPrefix}node_modules`, "node_modules"])]);
87
+ const machineryPaths = [...secretPaths, ...(stateRel ? [stateRel] : [])];
88
+ return { machineryPaths, leakCandidates, stateShips, depDirs };
89
+ }
90
+ /** Interrogate BOTH ignore files deploy emits, when the author kept them. */
91
+ export async function checkKeptIgnoreFiles(ctx, report) {
92
+ const { workspace, agentDir, agentPrefix, force } = ctx;
93
+ const { leakCandidates, stateShips, depDirs } = ctx.paths;
94
+ for (const rel of [".dockerignore", `${agentPrefix}Dockerfile.dockerignore`]) {
95
+ const kept = await readTextIfExists(join(workspace, rel));
96
+ if (kept === undefined)
97
+ continue;
98
+ // One WE generated is regenerated by this very run under --force, so checking the stale content on disk would
99
+ // gate a deploy on a file about to be replaced.
100
+ const keptIsOurs = isGeneratedDockerignore(kept);
101
+ if (force && keptIsOurs)
102
+ continue;
103
+ const remedy = (lines) => keptIsOurs
104
+ ? `Re-run with --force to regenerate it.`
105
+ : `Add ${lines.map((p) => `\`${p}\``).join(" and ")} before deploying (the same lines the generated ${rel} writes).`;
106
+ const excluded = dockerignoreMatcher(kept);
107
+ // Asked as a DIRECTORY (trailing slash), which is what it is.
108
+ if (excluded(`${basename(agentDir)}/`)) {
109
+ const text = `your ${rel} (kept) excludes \`${basename(agentDir)}\` — the build context would ship WITHOUT the ` +
110
+ `agent entirely (the deployed box has no persona/config and crash-loops). Remove that rule ` +
111
+ `before deploying.`;
112
+ report.issue(text);
113
+ }
114
+ // Resolved paths, not spellings: dockerignore patterns are root-anchored (unlike .gitignore), so a bare
115
+ // `.secrets` line does not cover `fastagent/.secrets`.
116
+ const leaks = leakCandidates.filter((p) => !excluded(p));
117
+ if (leaks.length > 0) {
118
+ const text = `your ${rel} (kept) does not exclude ${leaks.map((p) => `\`${p}\``).join(", ")} — the build ` +
119
+ `context would BAKE SECRETS INTO THE IMAGE. ${remedy(leaks.map((p) => `/${p}`))}`;
120
+ report.issue(text);
121
+ }
122
+ if (stateShips && !excluded(`${stateShips}/sessions`)) {
123
+ report.warn(`your ${rel} (kept) does not exclude \`${stateShips}\` — the build machine's sessions/channel state would ship in the image. ${remedy([`/${stateShips}`])}`);
124
+ }
125
+ // Both the agent's own node_modules and the workspace's.
126
+ const unexcludedDeps = depDirs.filter((p) => !excluded(`${p}/.package-lock.json`));
127
+ if (unexcludedDeps.length > 0) {
128
+ report.warn(`your ${rel} does not exclude ${unexcludedDeps.map((p) => `\`${p}\``).join(" or ")} — the ` +
129
+ `build machine's deps (native binaries for YOUR OS) would be uploaded and clobber the image's ` +
130
+ `freshly-installed ones. ${remedy(unexcludedDeps.map((p) => `/${p}`))}`);
131
+ }
132
+ if (excluded(".git/HEAD")) {
133
+ report.note(`your ${rel} excludes .git — the baked copy ships WITHOUT history/remote, so the agent ` +
134
+ `cannot pull/commit/push it; it must \`git clone\` its repo in the workspace instead (or remove the .git line).`);
135
+ }
136
+ }
137
+ }
@@ -1,54 +1,25 @@
1
- /**
2
- * HOW A RUNNING CHANNEL IS REACHED — the URL-neutral half of "point this channel at the agent": its
3
- * default route, whether anything can set that URL end-to-end, and the words an operator needs when
4
- * nothing can. Only the base URL varies, which is the argument every function here takes — a deploy
5
- * host's (`<app>.fly.dev`, a minted Railway domain, a Function URL) or `dev --tunnel`'s Quick Tunnel.
6
- * It lives under `deploy/` because that is where its answers are mostly consumed and where the gate
7
- * policy it composes with lives; the serving path reads the same answers through `tunnel.ts`.
8
- *
9
- * It exists because that knowledge belongs to the CHANNEL and was written per HOST: three runbook
10
- * plans, three `--run` drivers, a path table in the docker plan and the `--tunnel` announcer each
11
- * hand-wrote the same five-branch if-chain. A rule spelled seven times drifts, and it did — the
12
- * long-connection exception reached only the feishu/lark branches, so a long-connection Telegram
13
- * deploy printed `setWebhook` in its runbook, which makes `getUpdates` return 409 and stops the
14
- * channel the operator just deployed.
15
- *
16
- * Every function here takes the {@link DeclaredChannel} list and filters it ITSELF. Taking a
17
- * pre-filtered list would put the rule back at the call sites, which is where it drifted from: the
18
- * `--tunnel` announcer trusted its caller that way and had a default that passed every channel,
19
- * long-connection ones included.
20
- *
21
- * A host adds its base URL and its own asides; it does not restate which channels have a webhook.
22
- */
1
+ /** HOW A RUNNING CHANNEL IS REACHED — the URL-neutral half of "point this channel at the agent". */
23
2
  import type { DeclaredChannel } from "../channels/discover.ts";
24
3
  import type { RegistrationOutcome } from "../channels/registration.ts";
25
4
  import type { ChannelKind } from "../scaffold/add-channel.ts";
26
- /** The registrars a host can drive. `telegram` is always available (fastagent holds the token and the
27
- * URL); the others are optional so a caller without their credentials falls back to a manual step. */
28
5
  export interface Registrars {
29
6
  telegram: (baseUrl: string) => Promise<RegistrationOutcome>;
30
7
  slack?: (baseUrl: string) => Promise<RegistrationOutcome>;
31
8
  feishu?: (baseUrl: string, kind: "feishu" | "lark") => Promise<RegistrationOutcome>;
32
9
  }
33
10
  /**
34
- * The first-party channels this deployment must point at a URL: webhook ingress, and a kind this tool
35
- * knows how to instruct. ONE answer for every host and every path — the runbooks, the `--run`
36
- * drivers, the docker ingress note, `--tunnel`. A custom channel is skipped here and reported by the
37
- * pre-flight instead: its URL is its author's to set.
11
+ * The first-party channels this deployment must point at a URL: webhook ingress, and a kind this tool knows how to
12
+ * instruct.
38
13
  */
39
14
  export declare function webhookKinds(channels: readonly DeclaredChannel[]): ChannelKind[];
40
15
  /** Default route keys for {@link webhookKinds}, in the same order (the docker plan's ingress note). */
41
16
  export declare function webhookPaths(channels: readonly DeclaredChannel[]): string[];
42
- /** The runbook block for every channel that needs a URL set by hand, `baseUrl` spelled the host's way
43
- * (a literal `https://app.fly.dev`, or a placeholder like `<your-domain>` the operator fills in). */
44
- export declare function webhookRunbook(baseUrl: string, channels: readonly DeclaredChannel[]): string[];
45
17
  /**
46
- * Point every channel that has a webhook at `baseUrl`, reporting what each one ended as. All channels
47
- * are attempted — one failure does not skip the rest — and a channel whose registrar the caller did
48
- * not wire reports `manual` with the operator's instruction. The two callers differ in what they do
49
- * with the outcomes, not in how they are produced: a deploy gates on them, a long-running serve
50
- * cannot ({@link registerWebhooks} is the gating half).
18
+ * The runbook block for every channel that needs a URL set by hand, `baseUrl` spelled the host's way (a literal
19
+ * `https://app.fly.dev`, or a placeholder like `<your-domain>` the operator fills in).
51
20
  */
21
+ export declare function webhookRunbook(baseUrl: string, channels: readonly DeclaredChannel[]): string[];
22
+ /** Point every channel that has a webhook at `baseUrl`, reporting what each one ended as. */
52
23
  export declare function pointChannelsAt(input: {
53
24
  baseUrl: string;
54
25
  channels: readonly DeclaredChannel[];
@@ -58,11 +29,27 @@ export declare function pointChannelsAt(input: {
58
29
  kind: ChannelKind;
59
30
  outcome: RegistrationOutcome;
60
31
  }[]>;
32
+ /** Poll a public `/health` until it answers 200; false on timeout. Injected by tests. */
33
+ export type PublicHealthProbe = (healthUrl: string) => Promise<boolean>;
61
34
  /**
62
- * {@link pointChannelsAt} plus the shared gate policy, for a command that EXITS: an exit 0 claims the
63
- * deployment is reachable, so a failed registration has to become a non-zero one. Returns the gate
64
- * message, or undefined when nothing gates.
35
+ * The readiness floor a host clears BEFORE {@link registerWebhooks}: `setWebhook` does not verify that anything
36
+ * answers the URL, so a deploy whose app crash-loops would otherwise point a live channel at a dead address and
37
+ * report success. The channels that DO verify (Slack/Feishu challenge) would fail here too, but as "registration
38
+ * failed" — a diagnosis that hides the actual cause.
39
+ *
40
+ * Asked ONLY when this deployment actually has a webhook to point. A definition with none (schedules only, the
41
+ * built-in `POST /invoke`, or long-connection channels) needs no inbound reachability at all, and demanding it would
42
+ * invent a failure for a deploy run from a network that cannot reach the platform's edge.
65
43
  */
44
+ export declare function publicHealthGate(input: {
45
+ baseUrl: string;
46
+ channels: readonly DeclaredChannel[];
47
+ log: (msg: string) => void;
48
+ /** How THIS host is inspected and re-run — the only per-host words in the gate. */
49
+ inspectHint: string;
50
+ probe?: PublicHealthProbe;
51
+ }): Promise<string | undefined>;
52
+ /** {@link pointChannelsAt} plus the shared gate policy, for a command that EXITS. */
66
53
  export declare function registerWebhooks(input: {
67
54
  baseUrl: string;
68
55
  channels: readonly DeclaredChannel[];
@@ -1,4 +1,5 @@
1
1
  import { registrationGate } from "./registration-gate.js";
2
+ import { waitForHealth } from "../channels/wait-health.js";
2
3
  const feishuCloud = (kind, label) => ({
3
4
  path: `/${kind}`,
4
5
  register: (r, baseUrl) => r.feishu?.(baseUrl, kind),
@@ -22,17 +23,6 @@ const INGRESS = {
22
23
  ` -d url=${baseUrl}/telegram -d secret_token=<TELEGRAM_SECRET_TOKEN>`,
23
24
  ],
24
25
  },
25
- github: {
26
- path: "/webhook",
27
- // The env-var name is the part a first-time operator cannot guess, and this line is all `--tunnel`
28
- // prints — there is no runbook beside it to carry the detail.
29
- manual: (baseUrl) => `github: set the webhook in the repo (Settings → Webhooks) → ${baseUrl}/webhook (content type application/json, secret = GITHUB_WEBHOOK_SECRET)`,
30
- runbook: (baseUrl) => [
31
- `# Set the GitHub webhook (repo Settings → Webhooks). Default route POST /webhook; if you remapped`,
32
- `# it in channels/github.ts, use your path:`,
33
- `# Payload URL = ${baseUrl}/webhook, content type application/json, secret = GITHUB_WEBHOOK_SECRET`,
34
- ],
35
- },
36
26
  slack: {
37
27
  path: "/slack",
38
28
  register: (r, baseUrl) => r.slack?.(baseUrl),
@@ -47,10 +37,8 @@ const INGRESS = {
47
37
  lark: feishuCloud("lark", "Lark"),
48
38
  };
49
39
  /**
50
- * The first-party channels this deployment must point at a URL: webhook ingress, and a kind this tool
51
- * knows how to instruct. ONE answer for every host and every path — the runbooks, the `--run`
52
- * drivers, the docker ingress note, `--tunnel`. A custom channel is skipped here and reported by the
53
- * pre-flight instead: its URL is its author's to set.
40
+ * The first-party channels this deployment must point at a URL: webhook ingress, and a kind this tool knows how to
41
+ * instruct.
54
42
  */
55
43
  export function webhookKinds(channels) {
56
44
  const declared = new Set(channels.filter((c) => c.ingress === "webhook").map((c) => c.name));
@@ -60,24 +48,20 @@ export function webhookKinds(channels) {
60
48
  export function webhookPaths(channels) {
61
49
  return webhookKinds(channels).map((kind) => INGRESS[kind].path);
62
50
  }
63
- /** The runbook block for every channel that needs a URL set by hand, `baseUrl` spelled the host's way
64
- * (a literal `https://app.fly.dev`, or a placeholder like `<your-domain>` the operator fills in). */
51
+ /**
52
+ * The runbook block for every channel that needs a URL set by hand, `baseUrl` spelled the host's way (a literal
53
+ * `https://app.fly.dev`, or a placeholder like `<your-domain>` the operator fills in).
54
+ */
65
55
  export function webhookRunbook(baseUrl, channels) {
66
56
  return webhookKinds(channels).flatMap((kind) => INGRESS[kind].runbook(baseUrl));
67
57
  }
68
- /**
69
- * Point every channel that has a webhook at `baseUrl`, reporting what each one ended as. All channels
70
- * are attempted — one failure does not skip the rest — and a channel whose registrar the caller did
71
- * not wire reports `manual` with the operator's instruction. The two callers differ in what they do
72
- * with the outcomes, not in how they are produced: a deploy gates on them, a long-running serve
73
- * cannot ({@link registerWebhooks} is the gating half).
74
- */
58
+ /** Point every channel that has a webhook at `baseUrl`, reporting what each one ended as. */
75
59
  export async function pointChannelsAt(input) {
76
60
  const outcomes = [];
77
61
  for (const kind of webhookKinds(input.channels)) {
78
62
  const ingress = INGRESS[kind];
79
63
  // Calling IS the question: a channel whose registrar the caller did not wire returns undefined.
80
- const running = ingress.register?.(input.registrars, input.baseUrl);
64
+ const running = ingress.register(input.registrars, input.baseUrl);
81
65
  if (!running) {
82
66
  input.log(ingress.manual(input.baseUrl));
83
67
  outcomes.push({ kind, outcome: "manual" }); // a human's step — re-surfaced after registrar output
@@ -89,10 +73,55 @@ export async function pointChannelsAt(input) {
89
73
  return outcomes;
90
74
  }
91
75
  /**
92
- * {@link pointChannelsAt} plus the shared gate policy, for a command that EXITS: an exit 0 claims the
93
- * deployment is reachable, so a failed registration has to become a non-zero one. Returns the gate
94
- * message, or undefined when nothing gates.
76
+ * How long a just-deployed public URL gets to answer `/health`: the FIRST boot seeds the whole workspace onto the
77
+ * volume before it binds a port, so this budget covers a copy on slow host storage, not a listen.
95
78
  */
79
+ const PUBLIC_HEALTH_TIMEOUT_MS = 180_000;
80
+ /** How often the wait announces itself, so a crash-looping app is not three silent minutes. */
81
+ const WAIT_NOTICE_MS = 15_000;
82
+ /**
83
+ * The readiness floor a host clears BEFORE {@link registerWebhooks}: `setWebhook` does not verify that anything
84
+ * answers the URL, so a deploy whose app crash-loops would otherwise point a live channel at a dead address and
85
+ * report success. The channels that DO verify (Slack/Feishu challenge) would fail here too, but as "registration
86
+ * failed" — a diagnosis that hides the actual cause.
87
+ *
88
+ * Asked ONLY when this deployment actually has a webhook to point. A definition with none (schedules only, the
89
+ * built-in `POST /invoke`, or long-connection channels) needs no inbound reachability at all, and demanding it would
90
+ * invent a failure for a deploy run from a network that cannot reach the platform's edge.
91
+ */
92
+ export async function publicHealthGate(input) {
93
+ if (webhookKinds(input.channels).length === 0)
94
+ return undefined;
95
+ const healthUrl = `${input.baseUrl}/health`;
96
+ input.log(`waiting for ${healthUrl} (up to ${PUBLIC_HEALTH_TIMEOUT_MS / 1000}s)…`);
97
+ const probe = input.probe ?? ((url) => waitForHealth(url, PUBLIC_HEALTH_TIMEOUT_MS, 500, announce(input.log)));
98
+ if (await probe(healthUrl))
99
+ return undefined;
100
+ // The infrastructure is up and only the webhook is missing, so hand over the manual route rather than leaving
101
+ // `re-run` as the only way out (a Fly re-run repeats the remote build).
102
+ for (const line of webhookRunbook(input.baseUrl, input.channels))
103
+ input.log(line);
104
+ return (`the deployed agent did not become healthy at ${healthUrl}, so no webhook was registered — ${input.inspectHint}. ` +
105
+ `To point the channels by hand instead, use the lines above.`);
106
+ }
107
+ /**
108
+ * Keep the wait visible. This occupies `waitForHealth`'s liveness slot without answering it: neither Fly nor Railway
109
+ * has a liveness question cheaper than the wait it would shorten (each is a CLI round trip, unlike docker's local
110
+ * `compose ps`), so the hook reports progress and never cuts the budget short.
111
+ */
112
+ function announce(log) {
113
+ const started = Date.now();
114
+ let last = started;
115
+ return async () => {
116
+ const now = Date.now();
117
+ if (now - last >= WAIT_NOTICE_MS) {
118
+ last = now;
119
+ log(`still waiting for /health (${Math.round((now - started) / 1000)}s)…`);
120
+ }
121
+ return true;
122
+ };
123
+ }
124
+ /** {@link pointChannelsAt} plus the shared gate policy, for a command that EXITS. */
96
125
  export async function registerWebhooks(input) {
97
126
  const reg = registrationGate(input.log, input.retryHint);
98
127
  for (const { kind, outcome } of await pointChannelsAt(input))
@@ -2,25 +2,19 @@ export interface Artifact {
2
2
  path: string;
3
3
  content: string;
4
4
  }
5
- /** Fixed first-line prefix of a fastagent-generated Dockerfile — the ONE source both the generator (the
6
- * `head` below) and the "is this ours?" check (cli deploy's kept-Dockerfile apt warn) share, so they
7
- * can't drift into detecting a marker the generator no longer writes. */
5
+ /** Fixed first-line prefix of a fastagent-generated Dockerfile. */
8
6
  export declare const GENERATED_DOCKERFILE_MARKER = "# Generated by `fastagent deploy`";
9
- /** The ONE predicate for "did fastagent generate this Dockerfile?" — a generated file opens with the
10
- * marker, so a hand-written one (or a file where the marker was dropped to take ownership) reads false.
11
- * Both the "keep vs flag-as-stale" decision and the apt warning share this; a split would let them
12
- * disagree on the same file (e.g. a marker not at byte 0). */
7
+ /** The ONE predicate for "did fastagent generate this Dockerfile?". */
13
8
  export declare function isGeneratedDockerfile(content: string): boolean;
14
- /** Did fastagent generate this `.dockerignore`? (Same ownership rule as {@link isGeneratedDockerfile}.) */
15
9
  export declare function isGeneratedDockerignore(content: string): boolean;
16
10
  export interface ContainerInput {
11
+ /** Stable for one release; generated once by the deploy preflight, never on container restart. */
12
+ releaseId: string;
17
13
  /** Whether the agent has a package.json (a code agent); else a pure markdown/skills agent. */
18
14
  hasPackageJson: boolean;
19
- /** The package manager the generated image targets: `bun` (packageManager: bun / a bun lockfile) gets an
20
- * `oven/bun` base + `bun install` + `bun run fastagent`; otherwise npm on `node:22-slim`. */
15
+ /** The package manager the generated image targets. */
21
16
  runtime: "node" | "bun";
22
- /** Whether the runtime's lockfile exists (package-lock.json for npm, bun.lock/bun.lockb for bun) — a
23
- * frozen/ci install needs it; without it the build resolves versions at build time (not reproducible). */
17
+ /** Whether the runtime's lockfile exists (package-lock.json for npm, bun.lock/bun.lockb for bun). */
24
18
  hasLockfile: boolean;
25
19
  /** For `runtime: "bun"`, the `FROM oven/bun:<tag>` version (from packageManager `bun@x`), else `"1"`. */
26
20
  bunVersion?: string;
@@ -28,30 +22,22 @@ export interface ContainerInput {
28
22
  version: string;
29
23
  /** Extra apt packages (fastagent.config deploy.apt) baked in for the agent's tools — git, ripgrep, …. */
30
24
  apt?: string[];
31
- /** Where deploy's artifacts and the agent's own files sit, relative to the BUILD CONTEXT (which is
32
- * always the workspace): the agent directory's name plus a slash when it sits INSIDE the workspace,
33
- * `""` when the agent IS the workspace. ONE derived value — an earlier shape branched on a `nested`
34
- * boolean at fifteen separate sites, and each site was a chance for the two to disagree. */
25
+ /**
26
+ * The model this deployment resolved, recorded in the release manifest — set only when the deployed environment's
27
+ * VALUE FILE named it (a `config.model` needs nothing: the config ships too). The manifest is the carrier because
28
+ * it is rebuilt and rewritten on every deploy, so the answer cannot go stale, and nothing on the way in can
29
+ * interpolate the operator's shell.
30
+ */
31
+ modelSpec?: string;
32
+ /**
33
+ * Where deploy's artifacts and the agent's own files sit, relative to the BUILD CONTEXT (which is always the
34
+ * workspace).
35
+ */
35
36
  agentPrefix: string;
36
- /** Workspace-relative machinery paths the NAME-based excludes below would miss — a
37
- * `FASTAGENT_SECRETS_DIR`/`FASTAGENT_STATE_DIR` pointed at an in-tree directory not called
38
- * `.secrets`/`.state`. Preflight resolves them; excluding by PATH is what keeps `COPY . .` from
39
- * baking a credential (or the build machine's sessions). */
37
+ /** Workspace-relative machinery paths the NAME-based excludes below would miss. */
40
38
  machineryPaths?: string[];
41
- /** Whether the baked workspace ships a `.git` (preflight fact). When true, preflight has already
42
- * merged "git" into `apt` (the write-back loop needs history + binary together); the plans word
43
- * their runbook's freshness/write-back guidance from the same fact. */
39
+ /** Whether the baked workspace ships a `.git` (preflight fact). */
44
40
  shipsGit?: boolean;
45
41
  }
46
- /**
47
- * The Dockerfile + ignore artifacts — spread into any host's artifact list. Everything is prefixed with
48
- * {@link ContainerInput.agentPrefix}, so a NESTED agent's Dockerfile lands under `fastagent/` (never
49
- * colliding with one the workspace already owns) while an agent that IS the workspace lands it at the root, where it IS
50
- * the agent's own file. The ignore ships in TWO forms because context packing is host-CLI-owned and
51
- * inconsistent: (1) a ROOT `.dockerignore` — the only form flyctl/railway's own context packers
52
- * reliably read (kept if the workspace already has one — preflight then checks the machinery/secret
53
- * excludes it must carry) — and (2) a per-Dockerfile `Dockerfile.dockerignore` for plain docker/buildx
54
- * builds. When the agent sits inside the workspace that root file is the ONE write deploy makes outside the agent dir, and
55
- * only at deploy time — without it the host CLI's packer would bake `.secrets/` into the image.
56
- */
42
+ /** The Dockerfile + ignore artifacts — spread into any host's artifact list. */
57
43
  export declare function containerArtifacts(input: ContainerInput): Artifact[];