@fastagent-sh/fastagent 0.21.1 → 0.22.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (388) hide show
  1. package/README.md +9 -12
  2. package/dist/agent.d.ts +24 -37
  3. package/dist/agent.js +7 -15
  4. package/dist/atomic-write.d.ts +13 -17
  5. package/dist/atomic-write.js +18 -29
  6. package/dist/bind.d.ts +12 -22
  7. package/dist/bind.js +18 -27
  8. package/dist/channel.d.ts +11 -33
  9. package/dist/channels/agentcore-limits.d.ts +2 -13
  10. package/dist/channels/agentcore-limits.js +2 -13
  11. package/dist/channels/agentcore-protocol.d.ts +67 -58
  12. package/dist/channels/agentcore-protocol.js +23 -15
  13. package/dist/channels/agentcore-service.d.ts +19 -33
  14. package/dist/channels/agentcore-service.js +176 -59
  15. package/dist/channels/agentcore.d.ts +39 -33
  16. package/dist/channels/agentcore.js +90 -224
  17. package/dist/channels/body.d.ts +15 -2
  18. package/dist/channels/body.js +24 -2
  19. package/dist/channels/busy.d.ts +1 -21
  20. package/dist/channels/busy.js +2 -39
  21. package/dist/channels/control.d.ts +17 -62
  22. package/dist/channels/control.js +74 -182
  23. package/dist/channels/define-channel.d.ts +39 -0
  24. package/dist/channels/define-channel.js +9 -0
  25. package/dist/channels/discover.d.ts +10 -31
  26. package/dist/channels/discover.js +37 -31
  27. package/dist/channels/feishu/bootstrap-token.d.ts +1 -8
  28. package/dist/channels/feishu/bootstrap-token.js +7 -23
  29. package/dist/channels/feishu/card.d.ts +14 -28
  30. package/dist/channels/feishu/card.js +16 -30
  31. package/dist/channels/feishu/cloud.d.ts +1 -5
  32. package/dist/channels/feishu/context-buffer.d.ts +4 -23
  33. package/dist/channels/feishu/context-buffer.js +8 -31
  34. package/dist/channels/feishu/crypto.d.ts +10 -5
  35. package/dist/channels/feishu/crypto.js +12 -19
  36. package/dist/channels/feishu/feishu-api.d.ts +2 -1
  37. package/dist/channels/feishu/feishu-api.js +5 -4
  38. package/dist/channels/feishu/feishu.d.ts +4 -10
  39. package/dist/channels/feishu/feishu.js +113 -227
  40. package/dist/channels/feishu/invoke-turn.d.ts +5 -4
  41. package/dist/channels/feishu/invoke-turn.js +36 -54
  42. package/dist/channels/feishu/model.d.ts +4 -21
  43. package/dist/channels/feishu/model.js +1 -8
  44. package/dist/channels/feishu/normalize.d.ts +2 -10
  45. package/dist/channels/feishu/normalize.js +7 -42
  46. package/dist/channels/feishu/parse.d.ts +9 -29
  47. package/dist/channels/feishu/parse.js +8 -26
  48. package/dist/channels/feishu/preview.d.ts +12 -14
  49. package/dist/channels/feishu/preview.js +108 -207
  50. package/dist/channels/feishu/register-app.d.ts +8 -23
  51. package/dist/channels/feishu/register-app.js +7 -20
  52. package/dist/channels/feishu/register-webhook.d.ts +2 -9
  53. package/dist/channels/feishu/register-webhook.js +9 -33
  54. package/dist/channels/feishu/scaffold/channel.ts +27 -19
  55. package/dist/channels/feishu/scaffold/channel.websocket.ts +31 -0
  56. package/dist/channels/feishu/setup-mode.d.ts +11 -36
  57. package/dist/channels/feishu/setup-mode.js +7 -26
  58. package/dist/channels/feishu/ws-ingress.d.ts +1 -2
  59. package/dist/channels/feishu/ws-ingress.js +6 -8
  60. package/dist/channels/http.d.ts +15 -15
  61. package/dist/channels/http.js +19 -9
  62. package/dist/channels/kit/attachment-path.d.ts +4 -5
  63. package/dist/channels/kit/attachment-path.js +10 -34
  64. package/dist/channels/kit/context-buffer.d.ts +14 -21
  65. package/dist/channels/kit/context-buffer.js +25 -38
  66. package/dist/channels/kit/delivery.d.ts +35 -0
  67. package/dist/channels/kit/delivery.js +129 -0
  68. package/dist/channels/kit/event-stream.d.ts +5 -0
  69. package/dist/channels/kit/event-stream.js +29 -0
  70. package/dist/channels/kit/invoke-turn-kit.d.ts +53 -26
  71. package/dist/channels/kit/invoke-turn-kit.js +89 -32
  72. package/dist/channels/kit/preview-kit.d.ts +18 -72
  73. package/dist/channels/kit/preview-kit.js +22 -88
  74. package/dist/channels/kit/signature.d.ts +3 -9
  75. package/dist/channels/kit/signature.js +3 -9
  76. package/dist/channels/kit/state.d.ts +1 -2
  77. package/dist/channels/kit/state.js +5 -14
  78. package/dist/channels/kit/stop-command.d.ts +1 -2
  79. package/dist/channels/kit/stop-command.js +6 -9
  80. package/dist/channels/kit/tasks.d.ts +1 -1
  81. package/dist/channels/kit/tasks.js +8 -21
  82. package/dist/channels/kit/text.d.ts +2 -16
  83. package/dist/channels/kit/text.js +23 -31
  84. package/dist/channels/kit/thread-participants.d.ts +4 -17
  85. package/dist/channels/kit/thread-participants.js +8 -55
  86. package/dist/channels/kit/transport.d.ts +26 -0
  87. package/dist/channels/kit/transport.js +20 -0
  88. package/dist/channels/kit/turn-queue.d.ts +7 -11
  89. package/dist/channels/kit/turn-queue.js +21 -39
  90. package/dist/channels/kit/turn-runner.d.ts +32 -18
  91. package/dist/channels/kit/turn-runner.js +98 -53
  92. package/dist/channels/kit/turn-store.d.ts +32 -32
  93. package/dist/channels/kit/turn-store.js +45 -88
  94. package/dist/channels/lark/lark.d.ts +1 -5
  95. package/dist/channels/lark/onboard.d.ts +3 -12
  96. package/dist/channels/lark/onboard.js +3 -13
  97. package/dist/channels/lark/scaffold/channel.ts +27 -19
  98. package/dist/channels/lark/scaffold/channel.websocket.ts +31 -0
  99. package/dist/channels/registration.d.ts +9 -35
  100. package/dist/channels/registration.js +6 -37
  101. package/dist/channels/secret.js +2 -5
  102. package/dist/channels/serve.d.ts +47 -44
  103. package/dist/channels/serve.js +165 -101
  104. package/dist/channels/slack/config-api.d.ts +4 -27
  105. package/dist/channels/slack/config-api.js +5 -34
  106. package/dist/channels/slack/context-buffer.d.ts +2 -3
  107. package/dist/channels/slack/context-buffer.js +5 -5
  108. package/dist/channels/slack/invoke-turn.d.ts +3 -1
  109. package/dist/channels/slack/invoke-turn.js +25 -34
  110. package/dist/channels/slack/manifest.js +1 -3
  111. package/dist/channels/slack/model.d.ts +1 -2
  112. package/dist/channels/slack/model.js +1 -1
  113. package/dist/channels/slack/onboard.d.ts +4 -3
  114. package/dist/channels/slack/onboard.js +8 -11
  115. package/dist/channels/slack/onboarding-state.d.ts +3 -5
  116. package/dist/channels/slack/onboarding-state.js +9 -6
  117. package/dist/channels/slack/parse.d.ts +4 -13
  118. package/dist/channels/slack/parse.js +7 -28
  119. package/dist/channels/slack/preview.d.ts +12 -2
  120. package/dist/channels/slack/preview.js +292 -269
  121. package/dist/channels/slack/reaction.d.ts +11 -3
  122. package/dist/channels/slack/reaction.js +19 -8
  123. package/dist/channels/slack/register-webhook.js +4 -7
  124. package/dist/channels/slack/scaffold/channel.ts +20 -13
  125. package/dist/channels/slack/scaffold/slack-send.ts +2 -2
  126. package/dist/channels/slack/setup-server.d.ts +1 -5
  127. package/dist/channels/slack/setup-server.js +1 -5
  128. package/dist/channels/slack/shared-api.d.ts +1 -4
  129. package/dist/channels/slack/shared-api.js +2 -13
  130. package/dist/channels/slack/slack-api.d.ts +2 -1
  131. package/dist/channels/slack/slack-api.js +6 -4
  132. package/dist/channels/slack/slack.d.ts +11 -14
  133. package/dist/channels/slack/slack.js +121 -122
  134. package/dist/channels/sse.d.ts +1 -1
  135. package/dist/channels/sse.js +1 -1
  136. package/dist/channels/telegram/context-buffer.d.ts +7 -22
  137. package/dist/channels/telegram/context-buffer.js +6 -23
  138. package/dist/channels/telegram/invoke-turn.d.ts +7 -7
  139. package/dist/channels/telegram/invoke-turn.js +24 -43
  140. package/dist/channels/telegram/parse.d.ts +24 -51
  141. package/dist/channels/telegram/parse.js +32 -84
  142. package/dist/channels/telegram/preview.d.ts +10 -1
  143. package/dist/channels/telegram/preview.js +79 -100
  144. package/dist/channels/telegram/register-webhook.js +5 -23
  145. package/dist/channels/telegram/scaffold/channel.ts +25 -18
  146. package/dist/channels/telegram/scaffold/telegram-send.ts +6 -4
  147. package/dist/channels/telegram/telegram-api.d.ts +4 -3
  148. package/dist/channels/telegram/telegram-api.js +9 -7
  149. package/dist/channels/telegram/telegram.d.ts +4 -18
  150. package/dist/channels/telegram/telegram.js +38 -94
  151. package/dist/channels/telegram/turn-store.d.ts +11 -14
  152. package/dist/channels/telegram/turn-store.js +8 -6
  153. package/dist/channels/wait-health.d.ts +4 -6
  154. package/dist/channels/wait-health.js +10 -14
  155. package/dist/cli/add-feishu.d.ts +2 -12
  156. package/dist/cli/add-feishu.js +24 -71
  157. package/dist/cli/add-slack.d.ts +5 -3
  158. package/dist/cli/add-slack.js +53 -76
  159. package/dist/cli/auth-view.d.ts +2 -16
  160. package/dist/cli/auth-view.js +2 -16
  161. package/dist/cli/commands/add.js +25 -40
  162. package/dist/cli/commands/chat.d.ts +0 -1
  163. package/dist/cli/commands/chat.js +5 -6
  164. package/dist/cli/commands/deploy/agentcore.js +60 -65
  165. package/dist/cli/commands/deploy/docker.js +50 -23
  166. package/dist/cli/commands/deploy/fly.js +23 -61
  167. package/dist/cli/commands/deploy/railway.js +23 -18
  168. package/dist/cli/commands/deploy/shared.d.ts +38 -57
  169. package/dist/cli/commands/deploy/shared.js +40 -59
  170. package/dist/cli/commands/deploy.d.ts +10 -27
  171. package/dist/cli/commands/deploy.js +38 -60
  172. package/dist/cli/commands/destroy.d.ts +4 -0
  173. package/dist/cli/commands/destroy.js +56 -0
  174. package/dist/cli/commands/dev.d.ts +2 -1
  175. package/dist/cli/commands/dev.js +20 -23
  176. package/dist/cli/commands/info.d.ts +0 -2
  177. package/dist/cli/commands/info.js +83 -49
  178. package/dist/cli/commands/init.d.ts +1 -2
  179. package/dist/cli/commands/init.js +16 -55
  180. package/dist/cli/commands/invoke.d.ts +0 -1
  181. package/dist/cli/commands/invoke.js +5 -7
  182. package/dist/cli/commands/login.d.ts +2 -1
  183. package/dist/cli/commands/login.js +33 -38
  184. package/dist/cli/commands/logs.js +3 -4
  185. package/dist/cli/commands/models.js +1 -5
  186. package/dist/cli/commands/routine-run.d.ts +6 -0
  187. package/dist/cli/commands/routine-run.js +47 -0
  188. package/dist/cli/commands/routine.d.ts +25 -0
  189. package/dist/cli/commands/routine.js +122 -0
  190. package/dist/cli/commands/start.d.ts +31 -3
  191. package/dist/cli/commands/start.js +131 -81
  192. package/dist/cli/commands/tool.js +26 -12
  193. package/dist/cli/fail.d.ts +13 -23
  194. package/dist/cli/fail.js +20 -23
  195. package/dist/cli/invoke-stream.d.ts +1 -8
  196. package/dist/cli/kernel.d.ts +6 -38
  197. package/dist/cli/kernel.js +14 -77
  198. package/dist/cli/models-view.d.ts +1 -9
  199. package/dist/cli/models-view.js +11 -22
  200. package/dist/cli/program.d.ts +5 -6
  201. package/dist/cli/program.js +120 -157
  202. package/dist/cli/serve.d.ts +57 -41
  203. package/dist/cli/serve.js +94 -121
  204. package/dist/cli/shared.d.ts +15 -61
  205. package/dist/cli/shared.js +55 -181
  206. package/dist/cli.js +1 -8
  207. package/dist/collect.d.ts +6 -0
  208. package/dist/collect.js +6 -0
  209. package/dist/core.d.ts +4 -1
  210. package/dist/core.js +9 -7
  211. package/dist/declared-secrets.d.ts +55 -0
  212. package/dist/declared-secrets.js +88 -0
  213. package/dist/deploy/agentcore/aws-cli.d.ts +91 -0
  214. package/dist/deploy/agentcore/aws-cli.js +64 -0
  215. package/dist/deploy/agentcore/destroy.d.ts +51 -0
  216. package/dist/deploy/agentcore/destroy.js +273 -0
  217. package/dist/deploy/agentcore/forwarder.js +28 -79
  218. package/dist/deploy/agentcore/logs.d.ts +2 -18
  219. package/dist/deploy/agentcore/logs.js +27 -51
  220. package/dist/deploy/agentcore/plan.d.ts +85 -105
  221. package/dist/deploy/agentcore/plan.js +152 -314
  222. package/dist/deploy/agentcore/run.d.ts +28 -28
  223. package/dist/deploy/agentcore/run.js +227 -306
  224. package/dist/deploy/agentcore/zip.d.ts +2 -15
  225. package/dist/deploy/agentcore/zip.js +3 -17
  226. package/dist/deploy/build-context.d.ts +21 -0
  227. package/dist/deploy/build-context.js +137 -0
  228. package/dist/deploy/channel-ingress.d.ts +26 -39
  229. package/dist/deploy/channel-ingress.js +57 -28
  230. package/dist/deploy/container.d.ts +20 -34
  231. package/dist/deploy/container.js +49 -100
  232. package/dist/deploy/docker/plan.d.ts +12 -11
  233. package/dist/deploy/docker/plan.js +43 -24
  234. package/dist/deploy/docker/run.d.ts +22 -25
  235. package/dist/deploy/docker/run.js +39 -37
  236. package/dist/deploy/fly/plan.d.ts +15 -44
  237. package/dist/deploy/fly/plan.js +41 -73
  238. package/dist/deploy/fly/run.d.ts +16 -51
  239. package/dist/deploy/fly/run.js +42 -95
  240. package/dist/deploy/hosts.d.ts +24 -3
  241. package/dist/deploy/hosts.js +24 -3
  242. package/dist/deploy/preflight.d.ts +47 -41
  243. package/dist/deploy/preflight.js +282 -383
  244. package/dist/deploy/railway/plan.d.ts +16 -42
  245. package/dist/deploy/railway/plan.js +42 -60
  246. package/dist/deploy/railway/run.d.ts +30 -68
  247. package/dist/deploy/railway/run.js +86 -100
  248. package/dist/deploy/registration-gate.d.ts +6 -13
  249. package/dist/deploy/residency.d.ts +37 -0
  250. package/dist/deploy/residency.js +24 -0
  251. package/dist/deploy/runner.d.ts +10 -9
  252. package/dist/deploy/runner.js +7 -18
  253. package/dist/deploy/secrets.d.ts +40 -41
  254. package/dist/deploy/secrets.js +117 -103
  255. package/dist/deploy/workspace.d.ts +50 -0
  256. package/dist/deploy/workspace.js +197 -0
  257. package/dist/dev-supervisor.d.ts +1 -9
  258. package/dist/dev-supervisor.js +21 -64
  259. package/dist/effect-port.d.ts +50 -0
  260. package/dist/effect-port.js +77 -0
  261. package/dist/engines/pi/agent-session-factory.d.ts +50 -69
  262. package/dist/engines/pi/agent-session-factory.js +249 -146
  263. package/dist/engines/pi/auth.d.ts +15 -11
  264. package/dist/engines/pi/auth.js +96 -99
  265. package/dist/engines/pi/chat.d.ts +1 -5
  266. package/dist/engines/pi/chat.js +2 -14
  267. package/dist/engines/pi/config.d.ts +53 -51
  268. package/dist/engines/pi/config.js +83 -92
  269. package/dist/engines/pi/create.d.ts +60 -75
  270. package/dist/engines/pi/create.js +108 -141
  271. package/dist/engines/pi/definition.d.ts +14 -58
  272. package/dist/engines/pi/definition.js +21 -85
  273. package/dist/engines/pi/invoke-session.d.ts +5 -25
  274. package/dist/engines/pi/invoke-session.js +233 -244
  275. package/dist/engines/pi/login.d.ts +95 -35
  276. package/dist/engines/pi/login.js +259 -107
  277. package/dist/engines/pi/machine.d.ts +31 -0
  278. package/dist/engines/pi/machine.js +106 -0
  279. package/dist/engines/pi/models.d.ts +70 -89
  280. package/dist/engines/pi/models.js +188 -81
  281. package/dist/engines/pi/open.d.ts +65 -53
  282. package/dist/engines/pi/open.js +129 -69
  283. package/dist/engines/pi/report.d.ts +3 -12
  284. package/dist/engines/pi/report.js +2 -16
  285. package/dist/engines/pi/search-tools.d.ts +1 -9
  286. package/dist/engines/pi/search-tools.js +26 -57
  287. package/dist/engines/pi/service.d.ts +2 -16
  288. package/dist/engines/pi/service.js +2 -16
  289. package/dist/engines/pi/session-builder.d.ts +5 -9
  290. package/dist/engines/pi/session-builder.js +44 -82
  291. package/dist/engines/pi/session-control.js +286 -269
  292. package/dist/engines/pi/session-effects.d.ts +14 -0
  293. package/dist/engines/pi/session-effects.js +30 -0
  294. package/dist/engines/pi/session-inheritance.d.ts +9 -18
  295. package/dist/engines/pi/session-inheritance.js +103 -160
  296. package/dist/engines/pi/session-markers.d.ts +31 -30
  297. package/dist/engines/pi/session-markers.js +26 -25
  298. package/dist/engines/pi/session-settings.d.ts +6 -22
  299. package/dist/engines/pi/session-settings.js +10 -21
  300. package/dist/engines/pi/session-store.d.ts +26 -92
  301. package/dist/engines/pi/session-store.js +96 -310
  302. package/dist/engines/pi/tool-context.d.ts +17 -45
  303. package/dist/engines/pi/tool-context.js +7 -26
  304. package/dist/engines/pi/tool.d.ts +45 -36
  305. package/dist/engines/pi/tool.js +43 -57
  306. package/dist/engines/pi/turn-kit.d.ts +14 -52
  307. package/dist/engines/pi/turn-kit.js +18 -67
  308. package/dist/engines/pi/wake-tool.d.ts +2 -10
  309. package/dist/engines/pi/wake-tool.js +7 -27
  310. package/dist/env.d.ts +27 -28
  311. package/dist/env.js +45 -50
  312. package/dist/index.js +0 -2
  313. package/dist/lark.d.ts +1 -2
  314. package/dist/lark.js +1 -2
  315. package/dist/loader.d.ts +14 -55
  316. package/dist/loader.js +24 -43
  317. package/dist/log.d.ts +2 -12
  318. package/dist/log.js +3 -24
  319. package/dist/node.d.ts +2 -15
  320. package/dist/node.js +2 -18
  321. package/dist/observe.d.ts +2 -8
  322. package/dist/open-url.d.ts +1 -1
  323. package/dist/open-url.js +1 -1
  324. package/dist/paths.d.ts +70 -134
  325. package/dist/paths.js +165 -232
  326. package/dist/pi.d.ts +4 -6
  327. package/dist/pi.js +3 -3
  328. package/dist/proxy.d.ts +16 -6
  329. package/dist/proxy.js +52 -16
  330. package/dist/runtime.d.ts +18 -12
  331. package/dist/runtime.js +18 -11
  332. package/dist/scaffold/add-channel.d.ts +14 -31
  333. package/dist/scaffold/add-channel.js +48 -112
  334. package/dist/scaffold/init.d.ts +9 -31
  335. package/dist/scaffold/init.js +45 -138
  336. package/dist/scaffold/templates/env.example +6 -4
  337. package/dist/scaffold/templates/fastagent.config.ts +36 -0
  338. package/dist/scaffold/templates.d.ts +3 -3
  339. package/dist/scaffold/templates.js +5 -8
  340. package/dist/scaffold/vendor-skill.d.ts +2 -5
  341. package/dist/scaffold/vendor-skill.js +19 -29
  342. package/dist/schedule/cron.d.ts +9 -5
  343. package/dist/schedule/cron.js +9 -11
  344. package/dist/schedule/discover.d.ts +12 -12
  345. package/dist/schedule/discover.js +43 -34
  346. package/dist/schedule/routine.d.ts +43 -0
  347. package/dist/schedule/routine.js +32 -0
  348. package/dist/schedule/run.d.ts +55 -0
  349. package/dist/schedule/run.js +146 -0
  350. package/dist/schedule/scheduler.d.ts +44 -55
  351. package/dist/schedule/scheduler.js +291 -265
  352. package/dist/schedule/state.d.ts +105 -10
  353. package/dist/schedule/state.js +293 -19
  354. package/dist/schedule/wake-alarm.d.ts +10 -23
  355. package/dist/schedule/wake-alarm.js +91 -121
  356. package/dist/schedule/wakeups.d.ts +23 -39
  357. package/dist/schedule/wakeups.js +33 -75
  358. package/dist/secrets-gate.d.ts +32 -0
  359. package/dist/secrets-gate.js +33 -0
  360. package/dist/service.d.ts +137 -98
  361. package/dist/service.js +250 -252
  362. package/dist/session-remote.d.ts +33 -44
  363. package/dist/session-remote.js +203 -192
  364. package/dist/session.d.ts +166 -229
  365. package/dist/session.js +33 -55
  366. package/dist/tunnel.d.ts +8 -36
  367. package/dist/tunnel.js +24 -65
  368. package/dist/version.d.ts +2 -2
  369. package/dist/version.js +2 -2
  370. package/package.json +10 -16
  371. package/dist/channels/agentcore-state.d.ts +0 -77
  372. package/dist/channels/agentcore-state.js +0 -276
  373. package/dist/channels/github/github.d.ts +0 -31
  374. package/dist/channels/github/github.js +0 -89
  375. package/dist/channels/github/scaffold/channel.ts +0 -25
  376. package/dist/cli/commands/attach.d.ts +0 -108
  377. package/dist/cli/commands/attach.js +0 -637
  378. package/dist/cli/commands/fire.d.ts +0 -7
  379. package/dist/cli/commands/fire.js +0 -38
  380. package/dist/cli/commands/schedule.d.ts +0 -12
  381. package/dist/cli/commands/schedule.js +0 -87
  382. package/dist/github.d.ts +0 -3
  383. package/dist/github.js +0 -2
  384. package/dist/scaffold/templates/fastagent.config.mjs +0 -23
  385. package/dist/schedule/audit.d.ts +0 -18
  386. package/dist/schedule/audit.js +0 -57
  387. package/dist/schedule/schedule.d.ts +0 -35
  388. package/dist/schedule/schedule.js +0 -23
@@ -1,89 +1,49 @@
1
1
  /**
2
- * Helpers shared across command modules: interactivity gates, port parsing, the startup auth report,
3
- * first-run model resolution, and the login terminal IO. Bodies moved verbatim from cli.ts; the
4
- * module-scoped flag access (`values.*`) became parameters.
2
+ * Helpers shared across command modules: interactivity gates, port parsing, the startup auth report, first-run model
3
+ * resolution, and the login terminal IO.
5
4
  */
6
5
  import { readFile, writeFile } from "node:fs/promises";
7
6
  import { relative, resolve } from "node:path";
8
7
  import { autocomplete, isCancel, log as clackLog, password, select, text as clackText } from "@clack/prompts";
9
8
  import { buildModelPickerOptions } from "./models-view.js";
10
9
  import { fastagentCredentialStore } from "../engines/pi/auth.js";
11
- import { isValidPort, listModels, loadConfig, providerOf, resolveAuthPath, resolveModel, resolveModelSpec, rewriteConfigModel, } from "../engines/pi/config.js";
10
+ import { isValidPort, listModels, loadConfig, providerOf, resolveAuthLayers, resolveModelSpec, rewriteConfigModel, } from "../engines/pi/config.js";
12
11
  import { LoginCancelled, loginFlow } from "../engines/pi/login.js";
13
- import { createPiModelRuntime, createPiModels, probeApiKey, probeAuthSource, providerAuthStatuses, } from "../engines/pi/models.js";
12
+ import { readMachine, withMachine } from "../engines/pi/machine.js";
13
+ import { createPiModelRuntime, probeAuthSource, providerAuthStatuses } from "../engines/pi/models.js";
14
14
  import { formatAuthReport } from "./auth-view.js";
15
15
  import { CODING_TOOL_NAMES } from "../engines/pi/create.js";
16
- import { reportModuleLoadFailures } from "../loader.js";
17
16
  import { reportFindingsIfChanged, reportToolCollisions } from "../engines/pi/report.js";
18
- import { workspaceHint } from "../paths.js";
19
17
  import { log } from "../log.js";
20
- import { loadDotEnv } from "../env.js";
18
+ import { enterAgentEnv } from "../env.js";
21
19
  import { openExternalUrl } from "../open-url.js";
22
- import { installProxyFetch } from "../proxy.js";
23
20
  import { bindAddress, isBindAddress } from "../bind.js";
24
21
  import { failStartup, failUsage, placementOrExit } from "./fail.js";
25
- /**
26
- * How every command that runs the model enters its agent directory, in the one order that works:
27
- * placement decides whose `.env` to read; `.env` may carry the proxy and the provider keys the
28
- * picker's auth probe needs; the picker runs last. Six commands wrote these steps out by hand, and
29
- * one carried the order as a comment. The result is the placement every later step reads.
30
- */
22
+ /** How every command that runs the model enters its agent directory, in the one order that works. */
31
23
  export async function enterAgentCommand(dirArg, opts) {
32
24
  const placement = placementOrExit(resolve(dirArg));
33
- loadDotEnv(placement.agentDir);
34
- installProxyFetch();
25
+ enterAgentEnv(placement.agentDir);
35
26
  await resolveFirstRunModel(placement.agentDir, opts);
36
27
  return placement;
37
28
  }
38
- /**
39
- * The padded label writer for the STARTUP report (`dev`/`start`, stderr via the log level). Hand-spaced
40
- * labels drift out of alignment the moment a longer one appears — which is exactly what happened when
41
- * `codingTools:` joined `workspace:`/`config:`/`model:`/`state:`. `info` keeps its own writer on purpose: its report is
42
- * stdout DATA (pipeable, its own label set, its own width), not a log line — the shared thing is the
43
- * policy (pad, never hand-space), not a constant.
44
- *
45
- * Private to this module: {@link reportAssembly} is the report, and a command reaching past it for a
46
- * line of its own is how the two copies of that report came to differ.
47
- */
29
+ /** The padded label writer for the STARTUP report (`dev`/`start`, stderr via the log level). */
48
30
  function reportLine(label, value) {
49
31
  log.info(`[fastagent] ${`${label}:`.padEnd(13)}${value}`);
50
32
  }
51
- /** The workspace hint under the `agent:`/`workspace:` pair, when there is one ({@link workspaceHint}):
52
- * you pointed at the agent, and the project around it is probably what you meant. A hint, so it renders
53
- * as one and is silent otherwise. */
54
- function reportWorkspaceHint(hint) {
55
- if (hint)
56
- reportLine("hint", hint);
57
- }
58
- /**
59
- * What `dev` and `start` say about the directory they just opened, in the order they say it.
60
- *
61
- * ONE function because it is one report: the two commands wrote it out line by line, and the copies
62
- * had already diverged over which lines exist at all — `dev` naming the config file, `start` naming
63
- * state and sessions. A line added to one of two hand-written copies is invisible in the other.
64
- *
65
- * The divergence itself is PRESERVED, not resolved: `start`'s `state:`/`sessions:` pair introduces the
66
- * persistence warnings that follow it in production posture, and `dev`'s `config:` has no counterpart
67
- * there. Both are passed as explicit extras by the caller, so the asymmetry is visible at the call
68
- * site rather than buried in two copies of a list. Whether it is RIGHT is a separate question from
69
- * whether it has one owner.
70
- *
71
- * Findings (skill collisions, definition diagnostics) CLOSE the report — they are about the assembly
72
- * just printed. What a command says next is its own posture talk, not report: `start`'s persistence
73
- * notes now follow them rather than precede them.
74
- */
33
+ /** What `dev` and `start` say about the directory they just opened, in the order they say it. */
75
34
  export async function reportAssembly(a, extras = {}) {
76
35
  reportLine("agent", a.agentDir);
77
36
  reportLine("workspace", a.workspace);
78
- reportWorkspaceHint(workspaceHint(a));
79
37
  for (const [label, value] of extras.beforeModel ?? [])
80
38
  reportLine(label, value);
81
39
  reportLine("model", `${a.modelSpec}${a.config.thinkingLevel ? ` (thinking: ${a.config.thinkingLevel})` : ""}`);
82
- await reportAuth(a.agentDir, a.modelSpec, a.authPath);
40
+ await reportAuth(a.agentDir, a.modelSpec, a.auth);
83
41
  reportLine("context", a.definition.contextFiles.map((f) => f.path).join(", ") || "(none)");
84
42
  if (a.definition.persona)
85
43
  reportLine("persona", "persona.md");
86
- reportLine("skills", a.definition.skills.map((s) => s.name).join(", ") || "(none)");
44
+ // What this agent HAS — the definition's skills and the ones its machine lends (machine.ts).
45
+ const skills = withMachine(a.definition.skills, (await readMachine(a.workspace)).skills);
46
+ reportLine("skills", skills.map((s) => s.name).join(", ") || "(none)");
87
47
  reportLine("codingTools", CODING_TOOL_NAMES.join(", "));
88
48
  if (a.toolNames.length > 0)
89
49
  reportLine("tools", a.toolNames.join(", "));
@@ -91,7 +51,6 @@ export async function reportAssembly(a, extras = {}) {
91
51
  reportLine("deferred", `${a.deferredToolNames.join(", ")} (activated via search_tools)`);
92
52
  }
93
53
  reportToolCollisions(a.toolCollisions);
94
- reportModuleLoadFailures(a.toolFailures);
95
54
  for (const [label, value] of extras.afterTools ?? [])
96
55
  reportLine(label, value);
97
56
  reportFindingsIfChanged(a.definition.dir, a.definition);
@@ -100,12 +59,7 @@ export async function reportAssembly(a, extras = {}) {
100
59
  export function isInteractive() {
101
60
  return Boolean(process.stdin.isTTY && process.stdout.isTTY);
102
61
  }
103
- /**
104
- * Parse + range-check a port string (CLI flag or env). Empty/whitespace is "not set" → undefined, so
105
- * the `??` chain falls through instead of binding port 0 (`Number("")` is 0). The exit code follows
106
- * RESPONSIBILITY, not the layer that discovers the problem: a bad `--port` is a usage error (2), a
107
- * bad `PORT` env is broken runtime configuration (1).
108
- */
62
+ /** Parse + range-check a port string (CLI flag or env). */
109
63
  export function parsePort(value, source, from) {
110
64
  if (value === undefined)
111
65
  return undefined;
@@ -121,9 +75,8 @@ export function parsePort(value, source, from) {
121
75
  return Number(trimmed);
122
76
  }
123
77
  /**
124
- * Parse a `--bind` address: empty/whitespace is "not set" → undefined (the `??` chain falls through to
125
- * config, then all interfaces). An unbindable string is a USAGE error (2) — caught here rather than as
126
- * a node bind failure, or worse, as a "the interface you bound" diagnostic downstream.
78
+ * Parse a `--bind` address: empty/whitespace is "not set" → undefined (the `??` chain falls through to config, then
79
+ * all interfaces).
127
80
  */
128
81
  export function parseBind(value) {
129
82
  const trimmed = value?.trim();
@@ -133,36 +86,33 @@ export function parseBind(value) {
133
86
  failUsage(`invalid --bind "${value}": must be an IP address or "localhost"`);
134
87
  return bindAddress(trimmed); // a name never travels past this point — see bind.ts
135
88
  }
136
- /** Report which source provides the model's credentials, surfacing a remediation hint at startup. Non-blocking.
137
- * Probes through the AGENT's model surface (`agentDir` carries its models.json), so a custom endpoint is
138
- * reported like any built-in rather than as an unknown provider. */
139
- export async function reportAuth(agentDir, modelSpec, authPath) {
89
+ /** Report which source provides the model's credentials, surfacing a remediation hint at startup. */
90
+ export async function reportAuth(agentDir, modelSpec, auth) {
140
91
  const provider = providerOf(modelSpec);
141
- const models = await createPiModelRuntime({ agentDir, authPath }).catch(failStartup);
92
+ const { path: authPath, fallback: fallbackAuthPath } = auth;
93
+ const models = await createPiModelRuntime({ agentDir, auth }).catch(failStartup);
142
94
  const source = await probeAuthSource(models, modelSpec);
143
- // Only when nothing satisfies auth do we read the store (refresh-FREE) to tell "nothing stored" from
144
- // "stored but unusable" — see formatAuthReport for why. store.read warns on a corrupt file itself.
145
- const stored = source === undefined
146
- ? await fastagentCredentialStore(authPath)
147
- .read(provider)
148
- .catch(() => undefined)
149
- : undefined;
150
- const report = formatAuthReport(provider, authPath, source, stored);
95
+ // One refresh-FREE read per layer, serving both questions below. A read failure is already warned about by the
96
+ // store itself; this line degrades to "nothing stored" rather than taking down the startup report.
97
+ const readFrom = (path) => fastagentCredentialStore(path)
98
+ .read(provider)
99
+ .catch(() => undefined);
100
+ const inPrimary = await readFrom(authPath);
101
+ const inFallback = inPrimary || fallbackAuthPath === undefined ? undefined : await readFrom(fallbackAuthPath);
102
+ // Name the layer the credential actually came from: "which file do I edit" is the question this line answers, and
103
+ // a global credential lives in a file the agent dir does not contain. With NEITHER layer holding it, the answer is
104
+ // the primary — that is the file the `fastagent login` this report recommends writes.
105
+ const found = fallbackAuthPath !== undefined && inFallback ? fallbackAuthPath : authPath;
106
+ // Only when nothing satisfies auth does the stored credential matter: it tells "nothing stored" from "stored but
107
+ // unusable".
108
+ const stored = source === undefined ? (inPrimary ?? inFallback) : undefined;
109
+ const report = formatAuthReport(provider, found, source, stored);
151
110
  log.info(`[fastagent] ${report.line}`);
152
111
  if (report.warn)
153
112
  log.warn(`[fastagent] ${report.warn}`);
154
113
  }
155
114
  /**
156
- * First-run model resolution for every assembly command (dev/start/invoke/fire/chat/deploy): ONE
157
- * funnel, no dead ends. When no model is set (flag/env/config) and we're on a TTY, show the FULL
158
- * catalog annotated per provider — ready (with the credential source, so which account pays is
159
- * visible at the decision point) or login-required — and, when the choice needs auth, run the login
160
- * flow INLINE instead of exiting with "run `fastagent login` and come back". A no-op when a model is
161
- * already set; on a non-TTY (CI, a piped stdin), with `--no-input`, on cancel, or on a failed login
162
- * it stays quiet and lets the caller raise its own clear error (`missing model`, or deploy's
163
- * model-travel gate). The pick is exported to FASTAGENT_MODEL so a spawned `dev` worker inherits it,
164
- * and best-effort written back to the config so the next run is quiet. `agentDir` is the resolved
165
- * AGENT DIR (resolvePlacement().agentDir) — config and auth both live there.
115
+ * First-run model resolution for every assembly command (dev/start/invoke/fire/chat/deploy): ONE funnel, no dead ends.
166
116
  */
167
117
  async function resolveFirstRunModel(agentDir, options = {}) {
168
118
  const { config, path: configPath } = await loadConfig(agentDir).catch(failStartup);
@@ -172,31 +122,24 @@ async function resolveFirstRunModel(agentDir, options = {}) {
172
122
  return; // --no-input: never prompt (clig) — the opener raises the clear error
173
123
  if (!isInteractive())
174
124
  return; // CI/deploy: the opener throws the actionable missing-model error
175
- const authPath = resolveAuthPath(agentDir, options.authPath);
176
- // The picker lists the AGENT's surface: built-ins plus whatever its models.json declares, so a
177
- // self-hosted endpoint is pickable on first run instead of being invisible until hand-set.
178
- const models = await createPiModelRuntime({ agentDir, authPath }).catch(failStartup);
179
- const chosen = await pickWithCredentials(models, authPath);
125
+ const auth = resolveAuthLayers(agentDir);
126
+ // The picker lists the AGENT's surface: built-ins plus whatever its models.json declares, so a self-hosted endpoint
127
+ // is pickable on first run instead of being invisible until hand-set.
128
+ const models = await createPiModelRuntime({ agentDir, auth }).catch(failStartup);
129
+ const chosen = await pickWithCredentials(models, auth.path, agentDir);
180
130
  if (chosen === undefined)
181
131
  return; // cancelled (or auth probe failed): the caller raises its clear missing-model error
182
132
  process.env.FASTAGENT_MODEL = chosen; // this process + any spawned dev worker inherits it
183
133
  await persistModelChoice(agentDir, configPath, chosen);
184
134
  }
185
- /**
186
- * The credential-aware pick: full catalog annotated per provider, then the post-pick auth policy —
187
- * remedy warnings for providers no login flow can fix (the choice is KEPT: model validity is
188
- * independent of credentials), or the inline login for the rest. Returns the chosen spec, or
189
- * undefined when the pick should be discarded (picker cancel, login cancel, a failed auth probe).
190
- */
191
- async function pickWithCredentials(models, authPath) {
135
+ /** The credential-aware pick: full catalog annotated per provider, then the post-pick auth policy. */
136
+ async function pickWithCredentials(models, authPath, agentDir) {
192
137
  let statuses;
193
138
  try {
194
139
  statuses = await providerAuthStatuses(models);
195
140
  }
196
141
  catch (error) {
197
- // Per-provider auth throws are captured as `broken` INSIDE providerAuthStatuses; reaching here
198
- // means the enumeration itself failed (getProviders / a provider with no probe-able surface) — a
199
- // system fault. Surface it; the opener then still raises the clear missing-model error.
142
+ // Per-provider auth throws are captured as `broken` INSIDE providerAuthStatuses.
200
143
  log.warn(`[fastagent] could not probe provider auth: ${error.message}`);
201
144
  return undefined;
202
145
  }
@@ -212,9 +155,7 @@ async function pickWithCredentials(models, authPath) {
212
155
  if (status?.state === "ready")
213
156
  return chosen; // usable now — nothing to fix
214
157
  if (status && status.login === "none") {
215
- // No login flow exists for this provider — KEEP the choice and name the remedy. The remedy depends
216
- // on WHY it is not ready: a BROKEN stored credential still owns the provider (env is consulted
217
- // only when nothing is stored — createPiModels), so "set the env var" would not help there.
158
+ // No login flow exists for this provider — KEEP the choice and name the remedy.
218
159
  if (status.state === "broken") {
219
160
  log.warn(`[fastagent] stored auth for "${provider}" is unusable: ${status.message} — fix or remove it in ${authPath}; invokes fail until then`);
220
161
  }
@@ -224,84 +165,21 @@ async function pickWithCredentials(models, authPath) {
224
165
  return chosen;
225
166
  }
226
167
  try {
227
- // Verified against the CHOSEN model — the exact request the agent is about to make; a rejected
228
- // key re-prompts inside the loop, so reaching here means a usable (or at worst unverifiable) key.
229
- await loginWithKeyCheck(provider, authPath, chosen);
168
+ // Verified on this agent's registry, against the chosen model: the request the agent is about to make.
169
+ await loginFlow(terminalLoginIO(), { provider, authPath, agentDir, verifyWith: chosen });
230
170
  console.error(`[fastagent] logged in to ${provider} — saved to ${authPath}`);
231
171
  }
232
172
  catch (error) {
233
173
  if (error instanceof LoginCancelled)
234
174
  return undefined; // user backed out — discard the choice, like a picker cancel
235
- // A FAILED login keeps the choice: the pick persists, the startup auth report names the remedy,
236
- // and a later `fastagent login` fixes auth without re-picking the model.
175
+ // A FAILED login keeps the choice: the pick persists, the startup auth report names the remedy, and a later
176
+ // `fastagent login` fixes auth without re-picking the model.
237
177
  log.warn(`[fastagent] login for "${provider}" failed: ${error.message} — model saved; run \`fastagent login\` to fix auth`);
238
178
  }
239
179
  return chosen;
240
180
  }
241
- /**
242
- * Interactive login with the api_key quick-fail probe closed into a LOOP: a definitively rejected key
243
- * (HTTP 401) deletes the bad credential and RE-PROMPTS immediately — the user's hands are on the
244
- * keyboard NOW; parking the failure for a later `fastagent login` would waste that. The loop exits on
245
- * a verified/unverifiable key (kept), an OAuth login (completing the flow already proved the
246
- * credential), or cancel (LoginCancelled propagates to the caller's cancel policy). Used by both the
247
- * `login` command and the first-run picker's inline login.
248
- */
249
- export async function loginWithKeyCheck(provider, authPath, spec,
250
- // Test seams: this loop DESTROYS credential state on `rejected`, so its policy (rejected → delete →
251
- // re-ask ONLY the key) is pinned by a test through fake flow/verify; production callers omit both.
252
- seams = {}) {
253
- const flow = seams.flow ?? loginFlow;
254
- const verify = seams.verify ?? verifyApiKeyLogin;
255
- const io = terminalLoginIO();
256
- let method;
257
- for (;;) {
258
- const result = await flow(io, { provider, authPath, method });
259
- if (result.method !== "api_key")
260
- return result;
261
- const verdict = await verify(result.provider, authPath, spec);
262
- if (verdict !== "rejected")
263
- return result;
264
- // Retry re-asks ONLY the key: the provider/method choices weren't the mistake, the keystrokes were.
265
- provider = result.provider;
266
- method = "api_key";
267
- }
268
- }
269
- /**
270
- * Quick-fail check after an api_key login (OAuth needs none — completing the flow already proved the
271
- * credential): probe the stored key with one minimal request against `spec`, or the provider's first
272
- * model. Policy over {@link probeApiKey}'s verdict: `rejected` (definitive HTTP 401) DELETES the
273
- * just-stored credential — a mistyped key must not persist as plausible state — and the caller
274
- * ({@link loginWithKeyCheck}) re-prompts; `unknown` (network, quota, permissions) keeps it and prints
275
- * the provider's message: the key may still be right, and wrongly destroying a good credential costs
276
- * more than keeping a doubtful one.
277
- */
278
- async function verifyApiKeyLogin(provider, authPath, spec) {
279
- // Built-ins only: `login` itself offers built-in providers (login.ts), and a models.json endpoint
280
- // authenticates from its own `apiKey` (env/command), so there is no stored credential to verify here.
281
- const models = createPiModels({ authPath });
282
- const model = spec ? resolveModel(models, spec) : models.getProvider(provider)?.getModels()[0];
283
- if (!model) {
284
- console.error(`[fastagent] cannot verify the key: provider "${provider}" lists no models — kept as stored`);
285
- return "unknown";
286
- }
287
- const label = `${model.provider}/${model.id}`;
288
- console.error(`[fastagent] verifying the key with ${label}…`);
289
- const probe = await probeApiKey(models, model);
290
- if (probe.state === "ok") {
291
- console.error(`[fastagent] key verified — ${label} responded`);
292
- }
293
- else if (probe.state === "rejected") {
294
- await fastagentCredentialStore(authPath).delete(provider);
295
- console.error(`[fastagent] ${provider} rejected the API key (HTTP 401): ${probe.message} — enter it again (or cancel)`);
296
- }
297
- else {
298
- console.error(`[fastagent] could not verify the key with ${label}: ${probe.message} — kept; invokes surface the provider's error`);
299
- }
300
- return probe.state;
301
- }
302
- /** Login terminal IO via @clack/prompts: a searchable list once long, a hidden prompt for keys. Shared
303
- * by the `login` command and the first-run picker's inline login. */
304
- function terminalLoginIO() {
181
+ /** Login terminal IO via @clack/prompts: a searchable list once long, a hidden prompt for keys. */
182
+ export function terminalLoginIO() {
305
183
  return {
306
184
  async select(message, options) {
307
185
  const r = await (options.length > 7 ? autocomplete : select)({ message, options });
@@ -317,15 +195,11 @@ function terminalLoginIO() {
317
195
  openUrl: openExternalUrl,
318
196
  };
319
197
  }
320
- /**
321
- * Best-effort persist the picked model so the next run does not prompt. Rewrites the commented
322
- * `model:` placeholder the scaffold writes / an existing `model:` line, or re-inserts the line into a
323
- * scaffold-shaped config (the hand-deleted-to-reset case); anything else (a hand-shaped config) is
324
- * left untouched with a printed hint. Never throws — persistence is a convenience.
325
- */
198
+ /** Best-effort persist the picked model so the next run does not prompt. */
326
199
  async function persistModelChoice(agentDir, configPath, spec) {
327
200
  const hint = () => console.error(
328
- // No "using it for this run" promise: deploy's model-travel gate rightly ignores the un-persisted pick.
201
+ // The pick lives in THIS process's environment only, so it serves this run and nothing that outlives it: a
202
+ // deployment resolves in the environment being deployed, where this machine's variables do not exist.
329
203
  `[fastagent] picked ${spec} — set \`model: ${JSON.stringify(spec)}\` in your config to persist`);
330
204
  if (!configPath)
331
205
  return hint();
package/dist/cli.js CHANGED
@@ -1,12 +1,5 @@
1
1
  #!/usr/bin/env node
2
- /**
3
- * fastagent CLI — the thin entry. Everything (parsing, help, dispatch, exit-code policy) lives in
4
- * cli/program.ts and is loaded lazily, so `fastagent <cmd>` pays only for the module graph that
5
- * command actually uses (the command registry lazy-imports each implementation in turn).
6
- *
7
- * Process side effects (proxy dispatcher, .env loading, log posture, explicit exits) belong to the
8
- * command modules under cli/commands/ — this file must stay import-free.
9
- */
2
+ /** fastagent CLI — the thin entry. */
10
3
  const { runCli } = await import("./cli/program.js");
11
4
  await runCli(process.argv);
12
5
  export {};
package/dist/collect.d.ts CHANGED
@@ -19,6 +19,12 @@ import type { AgentEvent, Json } from "./agent.ts";
19
19
  * generator's own catch/finally already surfaced the outcome). `throw()` tears down identically
20
20
  * and rethrows the caller's error deterministically instead of poking a completed generator.
21
21
  *
22
+ * NOT `Stream.toAsyncIterable`. Its `return()` does interrupt the in-flight pull and close the scope,
23
+ * but a pull that has ALREADY resolved still hands its value back from the same `next()` — the entry
24
+ * guard only catches later calls. A consumer that cancels while a buffered terminal is in flight would
25
+ * therefore receive that terminal, which is the one thing MUST 3 forbids. The re-check after the await
26
+ * below is what makes the silence deterministic instead of a race.
27
+ *
22
28
  * Cancellation also SILENCES the stream, and that belongs here rather than in each producer: a
23
29
  * generator parked in an await can still reach a `yield` on its way out (an error path that
24
30
  * yields a terminal, say), and that yield satisfies the pending `next()` — handing a terminal
package/dist/collect.js CHANGED
@@ -7,6 +7,12 @@
7
7
  * generator's own catch/finally already surfaced the outcome). `throw()` tears down identically
8
8
  * and rethrows the caller's error deterministically instead of poking a completed generator.
9
9
  *
10
+ * NOT `Stream.toAsyncIterable`. Its `return()` does interrupt the in-flight pull and close the scope,
11
+ * but a pull that has ALREADY resolved still hands its value back from the same `next()` — the entry
12
+ * guard only catches later calls. A consumer that cancels while a buffered terminal is in flight would
13
+ * therefore receive that terminal, which is the one thing MUST 3 forbids. The re-check after the await
14
+ * below is what makes the silence deterministic instead of a race.
15
+ *
10
16
  * Cancellation also SILENCES the stream, and that belongs here rather than in each producer: a
11
17
  * generator parked in an await can still reach a `yield` on its way out (an error path that
12
18
  * yields a terminal, say), and that yield satisfies the pending `next()` — handing a terminal
package/dist/core.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export type { Agent, AgentEvent, ImageRef, Json, Prompt, Scope } from "./agent.ts";
2
+ export { ABORTED_CODE, SESSION_BUSY_CODE } from "./agent.ts";
2
3
  export { collect, AgentFailure, type CollectResult } from "./collect.ts";
3
4
  export type { ModuleLoadFailure } from "./loader.ts";
4
5
  export { createInvokeHandler } from "./channels/http.ts";
@@ -7,4 +8,6 @@ export { ControlRequestError, connectAgent, connectSessionControl, type RemoteEn
7
8
  export { readBodyCapped } from "./channels/body.ts";
8
9
  export { text, textHeaders } from "./channels/respond.ts";
9
10
  export type { ChannelContext, ChannelHandler, ChannelModule, LongConnection, LongConnectionChannelModule, Routes, } from "./channel.ts";
10
- export { defineSchedule, type LoadedSchedule, type Schedule } from "./schedule/schedule.ts";
11
+ export { defineRoutine, type DefineRoutineOptions, type LoadedRoutine, type Routine } from "./schedule/routine.ts";
12
+ export { defineChannel, type DefineChannelOptions } from "./channels/define-channel.ts";
13
+ export type { DeclaredSecret } from "./declared-secrets.ts";
package/dist/core.js CHANGED
@@ -1,12 +1,14 @@
1
+ // VALUES, not types: each caller does something DIFFERENT with these two codes (retry with backoff, skip the
2
+ // occurrence, steer the live run instead of failing), which is why they are named at all. Without an export an
3
+ // embedder copies the literal, and the copy is how the first-event rule goes silently wrong.
4
+ export { ABORTED_CODE, SESSION_BUSY_CODE } from "./agent.js";
1
5
  export { collect, AgentFailure } from "./collect.js";
2
6
  export { createInvokeHandler } from "./channels/http.js";
3
7
  export { ControlRequestError, connectAgent, connectSessionControl, } from "./session-remote.js";
4
8
  export { readBodyCapped } from "./channels/body.js";
5
9
  export { text, textHeaders } from "./channels/respond.js";
6
- // Mounting only. Composing a route table (`router`) and owning a prefix (`PrefixMount`) are how
7
- // `createAgentService` assembles a service — not something a caller has to reproduce.
8
- // Binding to a Node server lives at `/node`: it is the only runtime-specific piece here, and the
9
- // only one that costs a third-party package.
10
- // `defineSchedule` is what a `schedules/*.ts` file is written against. Discovering those files and
11
- // running the clock is what `createAgentService` does with them — parts a caller does not reproduce.
12
- export { defineSchedule } from "./schedule/schedule.js";
10
+ // Mounting only.
11
+ export { defineRoutine } from "./schedule/routine.js";
12
+ // What a `channels/*.ts` file is written against when it needs credentials: declaring them is what
13
+ // lets `deploy` carry them and the serve refuse to start without them.
14
+ export { defineChannel } from "./channels/define-channel.js";
@@ -0,0 +1,55 @@
1
+ /**
2
+ * WHICH ENV VARS THIS AGENT'S CODE NEEDS, in ONE shape, wherever it was declared: `defineTool`, `defineChannel` and
3
+ * `defineRoutine({ secrets })`, each next to the code that reads the value.
4
+ *
5
+ * One list, two consequences:
6
+ * - a serving path ASSERTS the values before it runs, so a missing one is a startup failure naming the file, not a
7
+ * 401 three days later;
8
+ * - `deploy --run` refuses a value file that does not supply one.
9
+ *
10
+ * Agent code does not read `process.env`: each authoring surface hands back exactly the values it declared, so the
11
+ * declaration cannot drift from the read, and a typo is a type error. The values still travel through the process env
12
+ * (a provider SDK reads its own variable), and `deploy` carries the whole value file (deploy/secrets.ts) — a
13
+ * declaration makes a value REQUIRED, it does not decide what travels.
14
+ */
15
+ /** One env var some part of the definition declared, and where that declaration is. */
16
+ export interface DeclaredSecret {
17
+ /** The env-var name. */
18
+ name: string;
19
+ /** Where it was declared: "tools/x-post.ts", "routines/daily-digest.ts", "config.tools" — printed in
20
+ * runbooks and failures, so it must name a place the author can open. */
21
+ source: string;
22
+ }
23
+ /**
24
+ * THE READ: what a code-input module declared, attributed to its file — the one implementation for
25
+ * `tools/`, `routines/` and `channels/`.
26
+ *
27
+ * One function, so every code input gets the same shape check: a `secrets: "FOO"` is that file's load
28
+ * failure, wherever it is. What a caller DOES with a bad declaration differs (push a failure and skip, or throw
29
+ * inside its own per-file try), so the verdict travels back as data rather than as an exception.
30
+ */
31
+ export declare function readSecretDeclaration(moduleDefault: unknown, label: string): {
32
+ secrets: DeclaredSecret[];
33
+ error?: undefined;
34
+ } | {
35
+ secrets?: undefined;
36
+ error: string;
37
+ };
38
+ /** Every owner's declarations as one list — what a path that runs ALL of them (a serve, a deploy)
39
+ * asserts or carries. A path that runs ONE reads that owner's entry instead. */
40
+ export declare function allSecrets(byOwner: ReadonlyMap<string, readonly DeclaredSecret[]>): DeclaredSecret[];
41
+ /** Declared names as a stable list, first declaration winning — the form a carrier iterates. */
42
+ export declare function dedupeSecrets(declared: readonly DeclaredSecret[]): DeclaredSecret[];
43
+ /**
44
+ * The values for a declared list, as the authoring surfaces hand them to author code. A name with no
45
+ * value reads as `""` rather than throwing: the deploy pre-flight IMPORTS these modules on a machine
46
+ * that legitimately holds no secrets, and a throwing accessor would make the channel whose secrets we
47
+ * came to collect unloadable. Presence is guaranteed by the gate (src/secrets-gate.ts) on the paths
48
+ * that run the code, which is the one place that can tell "not set" from "not needed here".
49
+ */
50
+ export declare function secretValues<const S extends readonly string[]>(names: S | undefined, env?: NodeJS.ProcessEnv): Record<S[number], string>;
51
+ /** Declared names with no value in `env` — empty string counts as missing (an unset `.env` line). */
52
+ export declare function missingSecrets(declared: readonly DeclaredSecret[], env?: NodeJS.ProcessEnv): DeclaredSecret[];
53
+ /** "X_API_KEY, X_API_SECRET (tools/x-post.ts); SLACK_TOKEN (routines/digest.ts)" — grouped by the
54
+ * file to open, since that is the unit the author fixes. */
55
+ export declare function describeSecrets(declared: readonly DeclaredSecret[]): string;
@@ -0,0 +1,88 @@
1
+ /**
2
+ * WHICH ENV VARS THIS AGENT'S CODE NEEDS, in ONE shape, wherever it was declared: `defineTool`, `defineChannel` and
3
+ * `defineRoutine({ secrets })`, each next to the code that reads the value.
4
+ *
5
+ * One list, two consequences:
6
+ * - a serving path ASSERTS the values before it runs, so a missing one is a startup failure naming the file, not a
7
+ * 401 three days later;
8
+ * - `deploy --run` refuses a value file that does not supply one.
9
+ *
10
+ * Agent code does not read `process.env`: each authoring surface hands back exactly the values it declared, so the
11
+ * declaration cannot drift from the read, and a typo is a type error. The values still travel through the process env
12
+ * (a provider SDK reads its own variable), and `deploy` carries the whole value file (deploy/secrets.ts) — a
13
+ * declaration makes a value REQUIRED, it does not decide what travels.
14
+ */
15
+ /**
16
+ * What an env-var NAME may look like. Checked at the declaration, not left to the host: these names
17
+ * are written verbatim into a Compose file, a Fly/Railway runbook and a CloudFormation parameter, so
18
+ * a typo (`"X_API_KEY "`, `"my key"`) or a quote/newline produces a broken or wrong artifact whose
19
+ * error surfaces in someone's cloud CLI, pointing at nothing. Case is not dictated — `http_proxy` is
20
+ * a real variable — only the shape a shell and every artifact format agree on.
21
+ */
22
+ const ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
23
+ /** Why an authored `secrets:` is unusable, or undefined when it is fine. A `.js` tool or a wrong type
24
+ * never met TypeScript, and a malformed declaration must fail as a load error rather than be
25
+ * silently dropped — the whole value of the field is that its absence is meaningful. */
26
+ function secretNamesProblem(value) {
27
+ if (value === undefined)
28
+ return undefined;
29
+ if (!Array.isArray(value))
30
+ return "secrets must be an array of env-var names";
31
+ for (const name of value) {
32
+ if (typeof name !== "string" || !ENV_NAME.test(name)) {
33
+ return `secrets must be an array of env-var names — ${JSON.stringify(name)} is not one`;
34
+ }
35
+ }
36
+ return undefined;
37
+ }
38
+ /**
39
+ * THE READ: what a code-input module declared, attributed to its file — the one implementation for
40
+ * `tools/`, `routines/` and `channels/`.
41
+ *
42
+ * One function, so every code input gets the same shape check: a `secrets: "FOO"` is that file's load
43
+ * failure, wherever it is. What a caller DOES with a bad declaration differs (push a failure and skip, or throw
44
+ * inside its own per-file try), so the verdict travels back as data rather than as an exception.
45
+ */
46
+ export function readSecretDeclaration(moduleDefault, label) {
47
+ const declared = moduleDefault?.secrets;
48
+ const problem = secretNamesProblem(declared);
49
+ if (problem !== undefined)
50
+ return { error: `${label}: ${problem}` };
51
+ return { secrets: (declared ?? []).map((name) => ({ name, source: label })) };
52
+ }
53
+ /** Every owner's declarations as one list — what a path that runs ALL of them (a serve, a deploy)
54
+ * asserts or carries. A path that runs ONE reads that owner's entry instead. */
55
+ export function allSecrets(byOwner) {
56
+ return [...byOwner.values()].flat();
57
+ }
58
+ /** Declared names as a stable list, first declaration winning — the form a carrier iterates. */
59
+ export function dedupeSecrets(declared) {
60
+ const seen = new Set();
61
+ return declared.filter((s) => !seen.has(s.name) && seen.add(s.name));
62
+ }
63
+ /**
64
+ * The values for a declared list, as the authoring surfaces hand them to author code. A name with no
65
+ * value reads as `""` rather than throwing: the deploy pre-flight IMPORTS these modules on a machine
66
+ * that legitimately holds no secrets, and a throwing accessor would make the channel whose secrets we
67
+ * came to collect unloadable. Presence is guaranteed by the gate (src/secrets-gate.ts) on the paths
68
+ * that run the code, which is the one place that can tell "not set" from "not needed here".
69
+ */
70
+ export function secretValues(names, env = process.env) {
71
+ const values = {};
72
+ for (const name of names ?? [])
73
+ values[name] = env[name] ?? "";
74
+ return values;
75
+ }
76
+ /** Declared names with no value in `env` — empty string counts as missing (an unset `.env` line). */
77
+ export function missingSecrets(declared, env = process.env) {
78
+ return dedupeSecrets(declared).filter((s) => !env[s.name]);
79
+ }
80
+ /** "X_API_KEY, X_API_SECRET (tools/x-post.ts); SLACK_TOKEN (routines/digest.ts)" — grouped by the
81
+ * file to open, since that is the unit the author fixes. */
82
+ export function describeSecrets(declared) {
83
+ const bySource = new Map();
84
+ for (const { name, source } of dedupeSecrets(declared)) {
85
+ bySource.set(source, [...(bySource.get(source) ?? []), name]);
86
+ }
87
+ return [...bySource].map(([source, names]) => `${names.join(", ")} (${source})`).join("; ");
88
+ }