@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,91 +1,98 @@
1
- /**
2
- * The wire between the forwarder Lambda and the container, in ONE place: the envelope every
3
- * trigger arrives as, the reply a webhook rides back in, the presigned-URL pair the state snapshot
4
- * uses, the wake-alarm request, and the forwarder's reserved paths. The forwarder itself is JavaScript
5
- * (`deploy/agentcore/forwarder.js`) and cannot import this, so `agentcore-forwarder.test.ts` pins its
6
- * literals to these — a rename here fails there, not on a live box.
7
- *
8
- * Pure types and constants: the adapter, the state sync, the wake sink and the deploy driver all
9
- * read it, and none of them may pull the others in for it.
10
- */
11
1
  /** Paths the forwarder answers ITSELF — never forwarded to a channel route. */
12
2
  export declare const RESERVED_PATHS: {
13
3
  /** The deploy driver's post-deploy verification (ingress secret). */
14
4
  readonly probe: "/__fastagent/probe";
15
5
  /** The container's wake-alarm mirror callback (wake secret). */
16
6
  readonly wakeAlarm: "/__fastagent/wake-alarm";
17
- /** Re-mint the state snapshot's presigned URLs with current Lambda credentials (ingress secret). */
18
- readonly stateUrls: "/__fastagent/state-urls";
19
7
  };
20
- /** Presigned S3 URLs for the one snapshot object, minted per envelope by the forwarder. */
21
- export interface StateUrls {
22
- getUrl: string;
23
- putUrl: string;
24
- /** Authenticated forwarder callback that re-mints URLs with current Lambda credentials. */
25
- refresh?: {
26
- url: string;
27
- auth: string;
28
- };
29
- }
30
- /** Every kind the container's `POST /invocations` dispatches on. Listed as a value so the forwarder
31
- * pin test can check each one is spelled the same on the other side. */
32
- export declare const ENVELOPE_KINDS: readonly ["webhook", "schedule-fire", "invoke", "wake-poke", "checkpoint", "probe"];
33
- /** What the forwarder Lambda / EventBridge deliver in the `/invocations` payload. Every kind may
34
- * carry `wake` — the forwarder's self-resolved public URL, which the adapter persists so the wake
35
- * ALARM sink (schedule/wake-alarm.ts) can call back without the URL being baked anywhere. */
8
+ /** Every kind the container's `POST /invocations` dispatches on. */
9
+ export declare const ENVELOPE_KINDS: readonly ["webhook", "routine-fire", "routine-run", "invoke", "wake-poke", "probe"];
10
+ /**
11
+ * Did this envelope come from the FORWARDER, rather than from some other principal holding
12
+ * `bedrock-agentcore:InvokeAgentRuntime`?
13
+ *
14
+ * WHAT IT ACTUALLY GUARDS, in one line: only the forwarder may tell the container where the forwarder is. That URL
15
+ * (`envelope.wake.url`) is where the container later POSTs its wake alarms, carrying `FASTAGENT_WAKE_SECRET` — so a
16
+ * principal who could set it would redirect that callback and collect the secret along with every pending wake-up.
17
+ *
18
+ * The other thing it gates — the non-`invoke` kinds — is DEPTH, not a boundary. Every one of them is weaker than
19
+ * the `invoke` the same caller may already send: `routine-fire` runs a prompt the definition wrote down,
20
+ * `webhook` reaches only channel routes that verify their own platform's signature, `wake-poke` wakes, `probe`
21
+ * reports. IAM is what decides who gets to ask at all.
22
+ *
23
+ * ONE reading, because the deferred wrapper answers a probe before the adapter exists and would otherwise spell the
24
+ * same rule twice (channels/agentcore-service.ts).
25
+ */
26
+ export declare function fromForwarder(envelope: {
27
+ auth?: unknown;
28
+ } | undefined, expected: string | undefined): boolean;
29
+ /** What the forwarder Lambda / EventBridge deliver in the `/invocations` payload. */
36
30
  export type AgentcoreEnvelope = {
37
- /** Shared secret proving this envelope came from the forwarder (FASTAGENT_INGRESS_SECRET). The
38
- * public `invoke` data plane neither has nor needs it — and may not carry the fields below. */
31
+ /** Shared secret proving this envelope came from the forwarder (FASTAGENT_INGRESS_SECRET). */
39
32
  auth?: string;
40
33
  wake?: {
41
34
  url: string;
42
35
  };
43
- state?: StateUrls;
44
36
  } & ({
45
37
  kind: "webhook";
46
- /** Original webhook request line, verbatim. `path` must be absolute ("/telegram"). */
38
+ /** Original webhook request line, verbatim. */
47
39
  method: string;
48
40
  path: string;
49
- /** Original raw query string (no leading `?`) — "verbatim" includes it; a channel reading
50
- * `request.url.searchParams` must see what the webhook sender sent. */
41
+ /**
42
+ * Original raw query string (no leading `?`) — "verbatim" includes it; a channel reading
43
+ * `request.url.searchParams` must see what the webhook sender sent.
44
+ */
51
45
  query?: string;
52
46
  /** Original headers — signature material (secret tokens, Feishu signatures) rides here. */
53
47
  headers?: Record<string, string>;
54
48
  /** Original body, base64 (webhook bodies are JSON but the tunnel must be byte-exact). */
55
49
  bodyB64?: string;
56
50
  } | {
57
- kind: "schedule-fire";
51
+ kind: "routine-fire";
52
+ name: string;
53
+ /**
54
+ * The instant EventBridge scheduled this fire for — the clock's NAME for the occurrence, stable across its
55
+ * redeliveries (measured: 3 attempts of one fire, byte-identical payload). The container dedupes on it and
56
+ * never recomputes it.
57
+ */
58
+ occurrence: string;
59
+ } | {
60
+ /**
61
+ * Run a declared routine BY NAME — the `POST /run` contract on the host that publishes no routes.
62
+ *
63
+ * On the IAM door, like `invoke`, and for the same reason: the forwarder never emits this, so it can only
64
+ * come from a direct `InvokeAgentRuntime` call, where AWS has already said who the caller is. That makes
65
+ * by-name running STRICTER here than on a host with a public URL, where the same route is anonymous.
66
+ *
67
+ * Separate from `routine-fire` because they are different contracts, not two spellings: a fire names an
68
+ * OCCURRENCE our own clock produced and claims it (dedup across redeliveries, a fire history, the overlap
69
+ * policy); this names only the work (schedule/run.ts).
70
+ */
71
+ kind: "routine-run";
58
72
  name: string;
59
- /** The cron instant this fire is FOR (ISO) — the slot-idempotency key. */
60
- slot: string;
61
73
  } | {
62
74
  kind: "invoke";
63
75
  session: string;
64
76
  text: string;
65
77
  }
66
- /** An EventBridge wake-up poke: the invocation ITSELF is the payload — it wakes the container,
67
- * whose boot drain / 30s wake pump then fires whatever is due. The handler only acks. */
78
+ /**
79
+ * An EventBridge wake-up poke: the invocation ITSELF is the payload — it wakes the container, whose boot drain / 30s
80
+ * wake pump then fires whatever is due.
81
+ */
68
82
  | {
69
83
  kind: "wake-poke";
70
84
  }
71
- /** Pre-stop checkpoint (`--run`, right before stop-runtime-session): push the state snapshot NOW.
72
- * A stop cuts an in-flight turn, and its durable turn intent — written pre-ACK by every replaying
73
- * channel — lives on a mount the version update is about to erase. Flushing first is what makes
74
- * "channels with replay re-run it" true rather than aspirational. */
75
- | {
76
- kind: "checkpoint";
77
- }
78
- /** The deploy driver's post-deploy verification (relayed by the forwarder's reserved probe path,
79
- * which answers on EVERY forwarder topology — schedule-only URLs refuse ordinary public traffic).
80
- * Runs restore + channel construction end to end and answers a TRANSPORT-200 structured verdict
81
- * `{ ok, error? }`: the ordinary webhook path folds a non-200 transport into an opaque 502 at the
82
- * forwarder, which would strip exactly the diagnostics this probe exists to carry. */
85
+ /**
86
+ * The deploy driver's post-deploy verification (relayed by the forwarder's reserved probe path, which answers on
87
+ * EVERY forwarder topology — schedule-only URLs refuse ordinary public traffic).
88
+ */
83
89
  | {
84
90
  kind: "probe";
85
91
  });
86
- /** The webhook envelope's reply: the channel's real HTTP response, ridden inside a transport-200
87
- * body so the forwarder can re-emit it verbatim (AgentCore folds a container non-2xx into its own
88
- * 424 RuntimeClientError). */
92
+ /**
93
+ * The webhook envelope's reply: the channel's real HTTP response, ridden inside a transport-200 body so the forwarder
94
+ * can re-emit it verbatim (AgentCore folds a container non-2xx into its own 424 RuntimeClientError).
95
+ */
89
96
  export interface WebhookReply {
90
97
  status: number;
91
98
  headers: Record<string, string>;
@@ -101,12 +108,14 @@ export interface WakeAlarmRequest {
101
108
  secret: string;
102
109
  alarms: WakeAlarm[];
103
110
  }
104
- /** What EventBridge hands the forwarder for a cron slot; `slot` is `<aws.scheduler.scheduled-time>`.
105
- * Its sibling, the wake-alarm poke `{ wakePoke: true }`, is minted and read inside the forwarder
106
- * alone (`syncAlarms` writes it, the handler reads it), so it has no type here. */
111
+ /**
112
+ * What EventBridge hands the forwarder when a cron rule fires: which schedule, and which occurrence of it.
113
+ * `occurrence` is `<aws.scheduler.scheduled-time>` — the clock names its own fire, which is the only thing that
114
+ * survives a retry (schedule/run.ts).
115
+ */
107
116
  export interface ScheduleFireEvent {
108
117
  scheduleFire: {
109
118
  name: string;
110
- slot: string;
119
+ occurrence: string;
111
120
  };
112
121
  }
@@ -1,22 +1,30 @@
1
- /**
2
- * The wire between the forwarder Lambda and the container, in ONE place: the envelope every
3
- * trigger arrives as, the reply a webhook rides back in, the presigned-URL pair the state snapshot
4
- * uses, the wake-alarm request, and the forwarder's reserved paths. The forwarder itself is JavaScript
5
- * (`deploy/agentcore/forwarder.js`) and cannot import this, so `agentcore-forwarder.test.ts` pins its
6
- * literals to these — a rename here fails there, not on a live box.
7
- *
8
- * Pure types and constants: the adapter, the state sync, the wake sink and the deploy driver all
9
- * read it, and none of them may pull the others in for it.
10
- */
1
+ /** The wire between the forwarder Lambda and the container, in ONE place. */
2
+ import { secretEquals } from "./secret.js";
11
3
  /** Paths the forwarder answers ITSELF — never forwarded to a channel route. */
12
4
  export const RESERVED_PATHS = {
13
5
  /** The deploy driver's post-deploy verification (ingress secret). */
14
6
  probe: "/__fastagent/probe",
15
7
  /** The container's wake-alarm mirror callback (wake secret). */
16
8
  wakeAlarm: "/__fastagent/wake-alarm",
17
- /** Re-mint the state snapshot's presigned URLs with current Lambda credentials (ingress secret). */
18
- stateUrls: "/__fastagent/state-urls",
19
9
  };
20
- /** Every kind the container's `POST /invocations` dispatches on. Listed as a value so the forwarder
21
- * pin test can check each one is spelled the same on the other side. */
22
- export const ENVELOPE_KINDS = ["webhook", "schedule-fire", "invoke", "wake-poke", "checkpoint", "probe"];
10
+ /** Every kind the container's `POST /invocations` dispatches on. */
11
+ export const ENVELOPE_KINDS = ["webhook", "routine-fire", "routine-run", "invoke", "wake-poke", "probe"];
12
+ /**
13
+ * Did this envelope come from the FORWARDER, rather than from some other principal holding
14
+ * `bedrock-agentcore:InvokeAgentRuntime`?
15
+ *
16
+ * WHAT IT ACTUALLY GUARDS, in one line: only the forwarder may tell the container where the forwarder is. That URL
17
+ * (`envelope.wake.url`) is where the container later POSTs its wake alarms, carrying `FASTAGENT_WAKE_SECRET` — so a
18
+ * principal who could set it would redirect that callback and collect the secret along with every pending wake-up.
19
+ *
20
+ * The other thing it gates — the non-`invoke` kinds — is DEPTH, not a boundary. Every one of them is weaker than
21
+ * the `invoke` the same caller may already send: `routine-fire` runs a prompt the definition wrote down,
22
+ * `webhook` reaches only channel routes that verify their own platform's signature, `wake-poke` wakes, `probe`
23
+ * reports. IAM is what decides who gets to ask at all.
24
+ *
25
+ * ONE reading, because the deferred wrapper answers a probe before the adapter exists and would otherwise spell the
26
+ * same rule twice (channels/agentcore-service.ts).
27
+ */
28
+ export function fromForwarder(envelope, expected) {
29
+ return secretEquals(envelope?.auth, expected);
30
+ }
@@ -1,51 +1,37 @@
1
- /**
2
- * The AgentCore serving assembly — the same product as `mountAgentService`, built differently
3
- * because the host is.
4
- *
5
- * Two facts drive every difference. There is no public URL (the adapter's `POST /invocations` is the
6
- * only ingress, and cron slots arrive through it from an external clock, so no resident timers), and
7
- * **the state mount at boot is PRE-RESTORE** — empty after every version update. Discovering channels
8
- * eagerly would therefore cache that emptiness (thread participation, delivery dedup, pending turns)
9
- * and then clobber the restored files with it, so channels are constructed lazily on the first
10
- * envelope. Everywhere else the state root is durable at boot and a broken channel fails startup.
11
- *
12
- * That is a different assembly, not a flag on the shared one: handler shape, discovery timing, clock
13
- * source, long-connection support and shutdown all differ. What it is NOT is a different product —
14
- * it returns the same {@link AgentService}, so `start` picks an assembly once and everything after
15
- * that point is common.
16
- */
1
+ /** The AgentCore serving assembly — the same product as `mountAgentService`, built differently because the host is. */
17
2
  import type { Agent } from "../agent.ts";
18
3
  import type { Routes } from "../channel.ts";
19
- import type { LoadedSchedule } from "../schedule/schedule.ts";
4
+ import type { LoadedRoutine } from "../schedule/routine.ts";
20
5
  import { type AgentService, type MountableAgent } from "../service.ts";
21
6
  import { type AgentcoreAdapterOptions } from "./agentcore.ts";
7
+ import type { ChannelHandler } from "../channel.ts";
8
+ /**
9
+ * Runtime filesystems appear on invocation, so even opening the definition must be deferred — in the TWO stages that
10
+ * differ in what a retry would cost.
11
+ */
12
+ export declare function deferAgentcoreService<T>(stages: {
13
+ prepare: () => Promise<T>;
14
+ assemble: (prepared: T) => Promise<AgentService>;
15
+ }): {
16
+ handler: ChannelHandler;
17
+ close: () => Promise<void>;
18
+ };
22
19
  export interface MountAgentcoreServiceOptions {
23
20
  /** Wrap the opened agent before anything binds to it (the CLI's turn trace). */
24
21
  wrapAgent?: (agent: Agent) => Agent;
25
- /** Runs once the state snapshot is restored. The wake-alarm reconcile passes through here because
26
- * its sink is a PROCESS-global: the process entry owns that, not a service that can be closed. */
22
+ /** The process entry owns the global wake-alarm sink; activation reconciles it once. */
27
23
  onStateReady?: () => void;
28
24
  }
29
- /** Is this process running inside the AgentCore Runtime? Set by the generated deploy artifacts. */
30
- export declare function isAgentcoreRuntime(): boolean;
31
25
  export declare function mountAgentcoreService(opened: MountableAgent, options?: MountAgentcoreServiceOptions): Promise<AgentService>;
32
26
  /**
33
- * Mount the AgentCore Runtime adapter (`POST /invocations` + `GET /ping`) — the deployed container's
34
- * ONLY reachable surface (channels/agentcore.ts). Wired by `start` when `FASTAGENT_AGENTCORE=1` (set
35
- * by the generated deploy artifacts, never by hand).
36
- *
37
- * The adapter IS the surface: the agent's channels live in a table INSIDE the envelope dispatch, a
38
- * separate namespace from these two paths, so a channel route named `/invocations` is reached
39
- * through the Function URL as itself and cannot shadow anything.
27
+ * Mount the AgentCore Runtime adapter (`POST /invocations` + `GET /ping`) — the deployed container's ONLY reachable
28
+ * surface (channels/agentcore.ts).
40
29
  */
41
30
  export declare function mountAgentcore(options: {
42
31
  agent: Agent;
43
32
  stateRoot: string;
44
- schedules: readonly LoadedSchedule[];
33
+ routines: readonly LoadedRoutine[];
45
34
  onStateReady?: () => void;
46
- /** Cancels the adapter's process-global registrations on close. */
47
- signal?: AbortSignal;
48
- /** The channel surface, constructed on the first envelope AFTER the state-snapshot restore — never
49
- * at boot, where the mount is pre-restore (channels/agentcore.ts). */
35
+ /** The channel surface, initialized once by the adapter on trusted ingress. */
50
36
  channels: AgentcoreAdapterOptions["channels"];
51
37
  }): Routes;
@@ -1,102 +1,219 @@
1
+ import { fromForwarder } from "./agentcore-protocol.js";
1
2
  import { log } from "../log.js";
2
- import { fireScheduleOnce } from "../schedule/scheduler.js";
3
- import { assertNoControlPlaneCollision, mountSessionControl, routesFor, startSchedules, } from "../service.js";
4
- import { UnknownScheduleError, agentcoreRoutes } from "./agentcore.js";
5
- import { createStateSync } from "./agentcore-state.js";
6
- import { activeWork } from "./busy.js";
3
+ import { loadServingRoutines, routesFor, startSchedules } from "../service.js";
4
+ import { agentcoreRoutes, agentcorePing } from "./agentcore.js";
5
+ import * as Effect from "effect/Effect";
6
+ import { runRoutineByName, unknownRoutine } from "../schedule/run.js";
7
+ import { fireRoutineOnce } from "../schedule/scheduler.js";
8
+ import { text } from "./respond.js";
9
+ import { activeWork, beginWork } from "./busy.js";
7
10
  import { router } from "./serve.js";
8
- /** Is this process running inside the AgentCore Runtime? Set by the generated deploy artifacts. */
9
- export function isAgentcoreRuntime() {
10
- return process.env.FASTAGENT_AGENTCORE === "1";
11
+ import { readBodyCapped } from "./body.js";
12
+ import { MAX_ENVELOPE_BYTES } from "./agentcore-limits.js";
13
+ /**
14
+ * Runtime filesystems appear on invocation, so even opening the definition must be deferred — in the TWO stages that
15
+ * differ in what a retry would cost.
16
+ */
17
+ export function deferAgentcoreService(stages) {
18
+ let service;
19
+ let prepared;
20
+ let assembling;
21
+ let closed = false;
22
+ let closing;
23
+ const ping = agentcorePing(() => activeWork() > 0);
24
+ const initialize = async () => {
25
+ const done = beginWork();
26
+ try {
27
+ prepared ??= stages.prepare().catch((error) => {
28
+ prepared = undefined; // nothing was taken, so the next envelope may ask again
29
+ throw error;
30
+ });
31
+ const taken = await prepared;
32
+ // Shutdown may have arrived while the workspace was being taken.
33
+ if (closed)
34
+ throw new Error("service closed during initialization");
35
+ assembling ??= stages.assemble(taken);
36
+ service = await assembling;
37
+ return service;
38
+ }
39
+ finally {
40
+ done();
41
+ }
42
+ };
43
+ return {
44
+ handler: async (request) => {
45
+ if (closed)
46
+ return new Response("service closed\n", { status: 503 });
47
+ if (service)
48
+ return service.handler(request);
49
+ const path = new URL(request.url).pathname;
50
+ if (path === "/ping" && request.method === "GET")
51
+ return ping(request);
52
+ if (path !== "/invocations" || request.method !== "POST")
53
+ return new Response("not found\n", { status: 404 });
54
+ let ready;
55
+ // Only the INITIALIZATION is caught here.
56
+ try {
57
+ ready = await initialize();
58
+ }
59
+ catch (error) {
60
+ const message = `initialization failed: ${String(error)}`;
61
+ log.error(`[agentcore] ${message}`);
62
+ const body = await readBodyCapped(request, MAX_ENVELOPE_BYTES);
63
+ if (!("tooLarge" in body)) {
64
+ let envelope;
65
+ try {
66
+ envelope = JSON.parse(body.text);
67
+ }
68
+ catch {
69
+ // Invalid envelopes retain the initialization error.
70
+ }
71
+ if (envelope?.kind === "probe" && fromForwarder(envelope, process.env.FASTAGENT_INGRESS_SECRET)) {
72
+ return Response.json({ ok: false, error: message });
73
+ }
74
+ }
75
+ return new Response(`${message}\n`, { status: 503 });
76
+ }
77
+ if (closed)
78
+ return new Response("service closed\n", { status: 503 });
79
+ return ready.handler(request);
80
+ },
81
+ close() {
82
+ closed = true;
83
+ closing ??= (async () => {
84
+ // A failed assembly already reached its caller.
85
+ await (await assembling?.catch(() => undefined))?.close();
86
+ })();
87
+ return closing;
88
+ },
89
+ };
11
90
  }
12
91
  export async function mountAgentcoreService(opened, options = {}) {
13
92
  const { agentDir, workspace, stateRoot, sessionControl } = opened;
14
93
  const agent = options.wrapAgent?.(opened.agent) ?? opened.agent;
15
- // The control plane mounts over an EMPTY route surface: the lazy channels join it later, and the
16
- // collision rule runs again then (below) against what they actually brought.
17
- const withControl = mountSessionControl({}, sessionControl, { agent });
18
- const scheduled = await startSchedules(agentDir, agent, stateRoot, opened.selfSchedule, {
19
- externalClock: true,
20
- });
94
+ // NO control plane on this host, and `sessionControl: true` cannot change that.
95
+ //
96
+ // The only public way in is the forwarder's Function URL, which relays an arbitrary `rawPath` verbatim as a
97
+ // webhook envelope (deploy/agentcore/forwarder.js) and attaches the ingress secret ITSELF — so every anonymous
98
+ // caller arrives as trusted ingress. A channel route survives that because the platform's signature is checked
99
+ // inside it; `/control/*` has no such check, and mounting it here made `GET /control/sessions` and
100
+ // `DELETE /control/sessions/{id}` answerable from the public URL with no credential at all.
101
+ //
102
+ // It is NOT that the two callers cannot be told apart. AWS already separates them and we already use that:
103
+ // `InvokeAgentRuntime` is IAM-gated, the forwarder builds its own envelopes and emits exactly four kinds, so a
104
+ // kind it never sends can only have come from a direct IAM call — which is how `kind: "invoke"` runs a turn here
105
+ // without the ingress secret. Adding `kind: "control"` on the same footing is small, and is the recipe if this is
106
+ // ever wanted.
107
+ //
108
+ // It is not built because nothing asks for it: `connectSessionControl` has no caller in this repo, and the
109
+ // envelope is request/response with a buffered body (see the webhook reply), so the one route a GUI actually
110
+ // renders from — the long-lived `GET /control/sessions/{id}/events` stream — could not ride it anyway. Half a
111
+ // control plane, for nobody, on a third transport.
112
+ // Every config key that cannot mean anything on this host says so. Silence here is how an operator concludes a
113
+ // setting took effect — `sessionControl` was the one that already warned, and the other two were just as inert.
114
+ if (opened.http?.cors) {
115
+ log.warn("[fastagent] agentcore: http.cors has no effect here — no browser reaches this container. Its ingress is the " +
116
+ "forwarder's Function URL (webhooks) and the Runtime's IAM-gated API, neither of which is a page.");
117
+ }
118
+ if (opened.http?.invoke !== undefined) {
119
+ log.warn("[fastagent] agentcore: http.invoke has no effect here — this host serves the Runtime's POST /invocations " +
120
+ "contract instead of our own /invoke, and reaching it already requires bedrock-agentcore:InvokeAgentRuntime.");
121
+ }
122
+ if (opened.http?.run !== undefined) {
123
+ log.warn("[fastagent] agentcore: http.run has no effect here — routines fire through the forwarder's " +
124
+ "routine-fire envelope, which is gated by the ingress secret rather than served as an anonymous route.");
125
+ }
126
+ if (opened.publishControl) {
127
+ log.warn("[fastagent] agentcore: sessionControl is ON but /control/* is NOT served here — this host's only public " +
128
+ "ingress relays anonymous traffic as trusted, so the plane is not assembled behind it " +
129
+ "(docs/design/session-control.md §14)");
130
+ }
131
+ // Started here, not deferred to an envelope.
132
+ const routines = await loadServingRoutines(agentDir);
133
+ const scheduled = startSchedules(agent, stateRoot, routines, { externalClock: true });
21
134
  const lazyChannels = async () => {
22
- const lazy = await routesFor(agentDir, agent, stateRoot, sessionControl, { builtinInvoke: false });
135
+ const lazy = await routesFor(agentDir, agent, stateRoot, sessionControl, { http: { invoke: false } });
23
136
  if (lazy.longConnections.length > 0) {
24
137
  throw new Error(`long-connection channel(s) ${lazy.longConnections.map((c) => c.name).join(", ")} cannot serve on ` +
25
138
  `AgentCore (scale-to-zero severs resident connections) — use the channel's webhook form`);
26
139
  }
27
- // The SAME rule mountSessionControl applies, through the same function: its check ran against an
28
- // empty base at boot, so it has to run again once the channels are real.
29
- for (const plane of withControl.mounts)
30
- assertNoControlPlaneCollision(lazy.routes, plane);
31
- return { routes: lazy.routes, mounts: withControl.mounts };
140
+ // CHANNELS ONLY — nothing unverified rides the public relay. `lazy.unverified` (here just `GET /health`) is dropped
141
+ // with the control plane, for the same reason: what arrives through that URL is anonymous.
142
+ return { routes: lazy.selfVerifying };
32
143
  };
33
- // The adapter registers process-global listeners; this is what takes them down on close.
34
- const closed = new AbortController();
35
144
  const adapterRoutes = mountAgentcore({
36
- signal: closed.signal,
37
145
  agent,
38
146
  stateRoot,
39
- schedules: scheduled.schedules,
147
+ routines: scheduled.routines,
40
148
  onStateReady: options.onStateReady,
41
149
  channels: lazyChannels,
42
150
  });
43
- const handler = router(adapterRoutes, withControl.mounts);
151
+ // SELF-VERIFYING, not unverified: both paths are reached only through `InvokeAgentRuntime`, which AWS gates with
152
+ // IAM. Putting them in the other table applied our JSON body gate to `POST /invocations` (whose content type is
153
+ // AWS's to set) and reported two IAM-protected paths as authenticating nobody.
154
+ const handler = router({ selfVerifying: adapterRoutes });
44
155
  log.info(`[fastagent] agentcore: serving POST /invocations + GET /ping (FASTAGENT_AGENTCORE=1)`);
45
156
  return {
46
157
  handler,
47
158
  agent,
48
- // The adapter IS the surface here; the channel routes arrive lazily BEHIND it. Reporting `{}`
49
- // would make the startup line claim nothing is served.
159
+ // The adapter IS the surface here; the channel routes arrive lazily BEHIND it.
50
160
  routes: adapterRoutes,
51
161
  agentDir,
52
162
  workspace,
53
- // Unknown at boot by design — a channel list here would be the pre-restore emptiness.
54
- channels: { routes: [], longConnections: [], builtinInvoke: false },
55
- schedules: scheduled.schedules,
163
+ // Channels remain lazy until the adapter receives trusted ingress.
164
+ channels: { routes: [], longConnections: [] },
165
+ // NONE: the adapter's two paths are IAM-gated and the channels behind them verify their own platform.
166
+ unverifiedRoutes: [],
167
+ // No `controlPrefix`: it is not served here, so nothing may report a prefix a caller could dial.
168
+ routines: scheduled.routines,
56
169
  ready: Promise.resolve(), // nothing to open: no port of our own, no resident connections
57
- ...(withControl.control ? { control: withControl.control } : {}),
58
170
  async close() {
59
- // UNTESTED, deliberately noted: no test observes these timers being cleared. Installing fake
60
- // timers early enough to count them deadlocks the assembly's own IO. What IS tested is that
61
- // close() runs and is idempotent; the stop itself rides on scheduler.stop()'s own tests.
62
171
  scheduled.stop();
63
- closed.abort();
64
172
  },
65
173
  };
66
174
  }
67
175
  /**
68
- * Mount the AgentCore Runtime adapter (`POST /invocations` + `GET /ping`) — the deployed container's
69
- * ONLY reachable surface (channels/agentcore.ts). Wired by `start` when `FASTAGENT_AGENTCORE=1` (set
70
- * by the generated deploy artifacts, never by hand).
71
- *
72
- * The adapter IS the surface: the agent's channels live in a table INSIDE the envelope dispatch, a
73
- * separate namespace from these two paths, so a channel route named `/invocations` is reached
74
- * through the Function URL as itself and cannot shadow anything.
176
+ * Mount the AgentCore Runtime adapter (`POST /invocations` + `GET /ping`) — the deployed container's ONLY reachable
177
+ * surface (channels/agentcore.ts).
75
178
  */
76
179
  export function mountAgentcore(options) {
77
- const { agent, stateRoot, schedules, onStateReady, channels, signal } = options;
180
+ const { agent, stateRoot, routines, onStateReady, channels } = options;
181
+ // OCCURRENCE SEMANTICS, because this host's clock is ours: `deploy` wrote the EventBridge rule and the forwarder
182
+ // relays its instant behind the ingress secret, so the slot is a real grid point of that schedule and a claim
183
+ // means something (dedup across redeliveries, a fire history, the overlap policy). `POST /run` — the
184
+ // unauthenticated API — is NOT this and is not served here at all.
185
+ const fireRoutine = async (name, occurrence) => {
186
+ const routine = routines.find((r) => r.name === name);
187
+ if (!routine)
188
+ return unknownRoutine(name, routines);
189
+ const outcome = await Effect.runPromise(fireRoutineOnce({ agent, stateRoot, routine, slot: occurrence }).pipe(Effect.mapError((e) => e.cause))).catch((cause) => {
190
+ // `fireRoutineOnce`'s only failure is a claim-state fault, which happens BEFORE any claim exists — the
191
+ // occurrence is unburned, so the forwarder's throw makes EventBridge retry it, which is the right answer.
192
+ log.error(`[schedule] firing "${name}" for ${occurrence.toISOString()} failed: ${String(cause)}`);
193
+ return undefined;
194
+ });
195
+ if (outcome === undefined) {
196
+ return text(`routine "${name}": claim state unavailable, nothing was claimed — retry\n`, 500);
197
+ }
198
+ return Response.json({ slot: occurrence.toISOString(), ...outcome });
199
+ };
200
+ // BY NAME, on the IAM door. `POST /run`'s contract, through the only ingress this host has for a caller AWS has
201
+ // already identified — which makes it stricter here than the anonymous route other hosts publish. A routine
202
+ // without a cron is reachable ONLY this way on this host, and that is the point: nothing is unreachable.
203
+ const runRoutine = async (name) => {
204
+ const routine = routines.find((r) => r.name === name);
205
+ if (!routine)
206
+ return unknownRoutine(name, routines);
207
+ return Response.json(await runRoutineByName(agent, routine));
208
+ };
78
209
  return agentcoreRoutes({
79
210
  channels,
80
211
  agent,
81
212
  stateRoot,
82
213
  isBusy: () => activeWork() > 0,
83
- // Cross-deploy durability: AgentCore wipes the state mount on every runtime version update, so
84
- // the state root is restored from (and pushed to) an S3 snapshot through presigned URLs the
85
- // forwarder mints per envelope. Always wired on this path — the platform gives no other way to
86
- // keep an agent's memory across a deploy.
87
- stateSync: createStateSync({ stateRoot }),
88
- // What separates a forwarder envelope from any IAM principal's InvokeAgentRuntime call. Absent =
89
- // no forwarder in this topology, so only the public `invoke` kind is servable.
214
+ // What separates a forwarder envelope from any IAM principal's InvokeAgentRuntime call.
90
215
  ingressSecret: process.env.FASTAGENT_INGRESS_SECRET,
91
216
  onStateReady,
92
- ...(signal ? { signal } : {}),
93
- fire: schedules.length === 0
94
- ? undefined
95
- : (name, slot) => {
96
- const schedule = schedules.find((s) => s.name === name);
97
- if (!schedule)
98
- throw new UnknownScheduleError(name);
99
- return fireScheduleOnce({ agent, stateRoot, schedule, slot });
100
- },
217
+ ...(routines.length > 0 ? { fireRoutine, runRoutine } : {}),
101
218
  });
102
219
  }