@fastagent-sh/fastagent 0.21.1 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (388) hide show
  1. package/README.md +9 -12
  2. package/dist/agent.d.ts +24 -37
  3. package/dist/agent.js +7 -15
  4. package/dist/atomic-write.d.ts +13 -17
  5. package/dist/atomic-write.js +18 -29
  6. package/dist/bind.d.ts +12 -22
  7. package/dist/bind.js +18 -27
  8. package/dist/channel.d.ts +11 -33
  9. package/dist/channels/agentcore-limits.d.ts +2 -13
  10. package/dist/channels/agentcore-limits.js +2 -13
  11. package/dist/channels/agentcore-protocol.d.ts +67 -58
  12. package/dist/channels/agentcore-protocol.js +23 -15
  13. package/dist/channels/agentcore-service.d.ts +19 -33
  14. package/dist/channels/agentcore-service.js +176 -59
  15. package/dist/channels/agentcore.d.ts +39 -33
  16. package/dist/channels/agentcore.js +90 -224
  17. package/dist/channels/body.d.ts +15 -2
  18. package/dist/channels/body.js +24 -2
  19. package/dist/channels/busy.d.ts +1 -21
  20. package/dist/channels/busy.js +2 -39
  21. package/dist/channels/control.d.ts +17 -62
  22. package/dist/channels/control.js +74 -182
  23. package/dist/channels/define-channel.d.ts +39 -0
  24. package/dist/channels/define-channel.js +9 -0
  25. package/dist/channels/discover.d.ts +10 -31
  26. package/dist/channels/discover.js +37 -31
  27. package/dist/channels/feishu/bootstrap-token.d.ts +1 -8
  28. package/dist/channels/feishu/bootstrap-token.js +7 -23
  29. package/dist/channels/feishu/card.d.ts +14 -28
  30. package/dist/channels/feishu/card.js +16 -30
  31. package/dist/channels/feishu/cloud.d.ts +1 -5
  32. package/dist/channels/feishu/context-buffer.d.ts +4 -23
  33. package/dist/channels/feishu/context-buffer.js +8 -31
  34. package/dist/channels/feishu/crypto.d.ts +10 -5
  35. package/dist/channels/feishu/crypto.js +12 -19
  36. package/dist/channels/feishu/feishu-api.d.ts +2 -1
  37. package/dist/channels/feishu/feishu-api.js +5 -4
  38. package/dist/channels/feishu/feishu.d.ts +4 -10
  39. package/dist/channels/feishu/feishu.js +113 -227
  40. package/dist/channels/feishu/invoke-turn.d.ts +5 -4
  41. package/dist/channels/feishu/invoke-turn.js +36 -54
  42. package/dist/channels/feishu/model.d.ts +4 -21
  43. package/dist/channels/feishu/model.js +1 -8
  44. package/dist/channels/feishu/normalize.d.ts +2 -10
  45. package/dist/channels/feishu/normalize.js +7 -42
  46. package/dist/channels/feishu/parse.d.ts +9 -29
  47. package/dist/channels/feishu/parse.js +8 -26
  48. package/dist/channels/feishu/preview.d.ts +12 -14
  49. package/dist/channels/feishu/preview.js +108 -207
  50. package/dist/channels/feishu/register-app.d.ts +8 -23
  51. package/dist/channels/feishu/register-app.js +7 -20
  52. package/dist/channels/feishu/register-webhook.d.ts +2 -9
  53. package/dist/channels/feishu/register-webhook.js +9 -33
  54. package/dist/channels/feishu/scaffold/channel.ts +27 -19
  55. package/dist/channels/feishu/scaffold/channel.websocket.ts +31 -0
  56. package/dist/channels/feishu/setup-mode.d.ts +11 -36
  57. package/dist/channels/feishu/setup-mode.js +7 -26
  58. package/dist/channels/feishu/ws-ingress.d.ts +1 -2
  59. package/dist/channels/feishu/ws-ingress.js +6 -8
  60. package/dist/channels/http.d.ts +15 -15
  61. package/dist/channels/http.js +19 -9
  62. package/dist/channels/kit/attachment-path.d.ts +4 -5
  63. package/dist/channels/kit/attachment-path.js +10 -34
  64. package/dist/channels/kit/context-buffer.d.ts +14 -21
  65. package/dist/channels/kit/context-buffer.js +25 -38
  66. package/dist/channels/kit/delivery.d.ts +35 -0
  67. package/dist/channels/kit/delivery.js +129 -0
  68. package/dist/channels/kit/event-stream.d.ts +5 -0
  69. package/dist/channels/kit/event-stream.js +29 -0
  70. package/dist/channels/kit/invoke-turn-kit.d.ts +53 -26
  71. package/dist/channels/kit/invoke-turn-kit.js +89 -32
  72. package/dist/channels/kit/preview-kit.d.ts +18 -72
  73. package/dist/channels/kit/preview-kit.js +22 -88
  74. package/dist/channels/kit/signature.d.ts +3 -9
  75. package/dist/channels/kit/signature.js +3 -9
  76. package/dist/channels/kit/state.d.ts +1 -2
  77. package/dist/channels/kit/state.js +5 -14
  78. package/dist/channels/kit/stop-command.d.ts +1 -2
  79. package/dist/channels/kit/stop-command.js +6 -9
  80. package/dist/channels/kit/tasks.d.ts +1 -1
  81. package/dist/channels/kit/tasks.js +8 -21
  82. package/dist/channels/kit/text.d.ts +2 -16
  83. package/dist/channels/kit/text.js +23 -31
  84. package/dist/channels/kit/thread-participants.d.ts +4 -17
  85. package/dist/channels/kit/thread-participants.js +8 -55
  86. package/dist/channels/kit/transport.d.ts +26 -0
  87. package/dist/channels/kit/transport.js +20 -0
  88. package/dist/channels/kit/turn-queue.d.ts +7 -11
  89. package/dist/channels/kit/turn-queue.js +21 -39
  90. package/dist/channels/kit/turn-runner.d.ts +32 -18
  91. package/dist/channels/kit/turn-runner.js +98 -53
  92. package/dist/channels/kit/turn-store.d.ts +32 -32
  93. package/dist/channels/kit/turn-store.js +45 -88
  94. package/dist/channels/lark/lark.d.ts +1 -5
  95. package/dist/channels/lark/onboard.d.ts +3 -12
  96. package/dist/channels/lark/onboard.js +3 -13
  97. package/dist/channels/lark/scaffold/channel.ts +27 -19
  98. package/dist/channels/lark/scaffold/channel.websocket.ts +31 -0
  99. package/dist/channels/registration.d.ts +9 -35
  100. package/dist/channels/registration.js +6 -37
  101. package/dist/channels/secret.js +2 -5
  102. package/dist/channels/serve.d.ts +47 -44
  103. package/dist/channels/serve.js +165 -101
  104. package/dist/channels/slack/config-api.d.ts +4 -27
  105. package/dist/channels/slack/config-api.js +5 -34
  106. package/dist/channels/slack/context-buffer.d.ts +2 -3
  107. package/dist/channels/slack/context-buffer.js +5 -5
  108. package/dist/channels/slack/invoke-turn.d.ts +3 -1
  109. package/dist/channels/slack/invoke-turn.js +25 -34
  110. package/dist/channels/slack/manifest.js +1 -3
  111. package/dist/channels/slack/model.d.ts +1 -2
  112. package/dist/channels/slack/model.js +1 -1
  113. package/dist/channels/slack/onboard.d.ts +4 -3
  114. package/dist/channels/slack/onboard.js +8 -11
  115. package/dist/channels/slack/onboarding-state.d.ts +3 -5
  116. package/dist/channels/slack/onboarding-state.js +9 -6
  117. package/dist/channels/slack/parse.d.ts +4 -13
  118. package/dist/channels/slack/parse.js +7 -28
  119. package/dist/channels/slack/preview.d.ts +12 -2
  120. package/dist/channels/slack/preview.js +292 -269
  121. package/dist/channels/slack/reaction.d.ts +11 -3
  122. package/dist/channels/slack/reaction.js +19 -8
  123. package/dist/channels/slack/register-webhook.js +4 -7
  124. package/dist/channels/slack/scaffold/channel.ts +20 -13
  125. package/dist/channels/slack/scaffold/slack-send.ts +2 -2
  126. package/dist/channels/slack/setup-server.d.ts +1 -5
  127. package/dist/channels/slack/setup-server.js +1 -5
  128. package/dist/channels/slack/shared-api.d.ts +1 -4
  129. package/dist/channels/slack/shared-api.js +2 -13
  130. package/dist/channels/slack/slack-api.d.ts +2 -1
  131. package/dist/channels/slack/slack-api.js +6 -4
  132. package/dist/channels/slack/slack.d.ts +11 -14
  133. package/dist/channels/slack/slack.js +121 -122
  134. package/dist/channels/sse.d.ts +1 -1
  135. package/dist/channels/sse.js +1 -1
  136. package/dist/channels/telegram/context-buffer.d.ts +7 -22
  137. package/dist/channels/telegram/context-buffer.js +6 -23
  138. package/dist/channels/telegram/invoke-turn.d.ts +7 -7
  139. package/dist/channels/telegram/invoke-turn.js +24 -43
  140. package/dist/channels/telegram/parse.d.ts +24 -51
  141. package/dist/channels/telegram/parse.js +32 -84
  142. package/dist/channels/telegram/preview.d.ts +10 -1
  143. package/dist/channels/telegram/preview.js +79 -100
  144. package/dist/channels/telegram/register-webhook.js +5 -23
  145. package/dist/channels/telegram/scaffold/channel.ts +25 -18
  146. package/dist/channels/telegram/scaffold/telegram-send.ts +6 -4
  147. package/dist/channels/telegram/telegram-api.d.ts +4 -3
  148. package/dist/channels/telegram/telegram-api.js +9 -7
  149. package/dist/channels/telegram/telegram.d.ts +4 -18
  150. package/dist/channels/telegram/telegram.js +38 -94
  151. package/dist/channels/telegram/turn-store.d.ts +11 -14
  152. package/dist/channels/telegram/turn-store.js +8 -6
  153. package/dist/channels/wait-health.d.ts +4 -6
  154. package/dist/channels/wait-health.js +10 -14
  155. package/dist/cli/add-feishu.d.ts +2 -12
  156. package/dist/cli/add-feishu.js +24 -71
  157. package/dist/cli/add-slack.d.ts +5 -3
  158. package/dist/cli/add-slack.js +53 -76
  159. package/dist/cli/auth-view.d.ts +2 -16
  160. package/dist/cli/auth-view.js +2 -16
  161. package/dist/cli/commands/add.js +25 -40
  162. package/dist/cli/commands/chat.d.ts +0 -1
  163. package/dist/cli/commands/chat.js +5 -6
  164. package/dist/cli/commands/deploy/agentcore.js +60 -65
  165. package/dist/cli/commands/deploy/docker.js +50 -23
  166. package/dist/cli/commands/deploy/fly.js +23 -61
  167. package/dist/cli/commands/deploy/railway.js +23 -18
  168. package/dist/cli/commands/deploy/shared.d.ts +38 -57
  169. package/dist/cli/commands/deploy/shared.js +40 -59
  170. package/dist/cli/commands/deploy.d.ts +10 -27
  171. package/dist/cli/commands/deploy.js +38 -60
  172. package/dist/cli/commands/destroy.d.ts +4 -0
  173. package/dist/cli/commands/destroy.js +56 -0
  174. package/dist/cli/commands/dev.d.ts +2 -1
  175. package/dist/cli/commands/dev.js +20 -23
  176. package/dist/cli/commands/info.d.ts +0 -2
  177. package/dist/cli/commands/info.js +83 -49
  178. package/dist/cli/commands/init.d.ts +1 -2
  179. package/dist/cli/commands/init.js +16 -55
  180. package/dist/cli/commands/invoke.d.ts +0 -1
  181. package/dist/cli/commands/invoke.js +5 -7
  182. package/dist/cli/commands/login.d.ts +2 -1
  183. package/dist/cli/commands/login.js +33 -38
  184. package/dist/cli/commands/logs.js +3 -4
  185. package/dist/cli/commands/models.js +1 -5
  186. package/dist/cli/commands/routine-run.d.ts +6 -0
  187. package/dist/cli/commands/routine-run.js +47 -0
  188. package/dist/cli/commands/routine.d.ts +25 -0
  189. package/dist/cli/commands/routine.js +122 -0
  190. package/dist/cli/commands/start.d.ts +31 -3
  191. package/dist/cli/commands/start.js +131 -81
  192. package/dist/cli/commands/tool.js +26 -12
  193. package/dist/cli/fail.d.ts +13 -23
  194. package/dist/cli/fail.js +20 -23
  195. package/dist/cli/invoke-stream.d.ts +1 -8
  196. package/dist/cli/kernel.d.ts +6 -38
  197. package/dist/cli/kernel.js +14 -77
  198. package/dist/cli/models-view.d.ts +1 -9
  199. package/dist/cli/models-view.js +11 -22
  200. package/dist/cli/program.d.ts +5 -6
  201. package/dist/cli/program.js +120 -157
  202. package/dist/cli/serve.d.ts +57 -41
  203. package/dist/cli/serve.js +94 -121
  204. package/dist/cli/shared.d.ts +15 -61
  205. package/dist/cli/shared.js +55 -181
  206. package/dist/cli.js +1 -8
  207. package/dist/collect.d.ts +6 -0
  208. package/dist/collect.js +6 -0
  209. package/dist/core.d.ts +4 -1
  210. package/dist/core.js +9 -7
  211. package/dist/declared-secrets.d.ts +55 -0
  212. package/dist/declared-secrets.js +88 -0
  213. package/dist/deploy/agentcore/aws-cli.d.ts +91 -0
  214. package/dist/deploy/agentcore/aws-cli.js +64 -0
  215. package/dist/deploy/agentcore/destroy.d.ts +51 -0
  216. package/dist/deploy/agentcore/destroy.js +273 -0
  217. package/dist/deploy/agentcore/forwarder.js +28 -79
  218. package/dist/deploy/agentcore/logs.d.ts +2 -18
  219. package/dist/deploy/agentcore/logs.js +27 -51
  220. package/dist/deploy/agentcore/plan.d.ts +85 -105
  221. package/dist/deploy/agentcore/plan.js +152 -314
  222. package/dist/deploy/agentcore/run.d.ts +28 -28
  223. package/dist/deploy/agentcore/run.js +227 -306
  224. package/dist/deploy/agentcore/zip.d.ts +2 -15
  225. package/dist/deploy/agentcore/zip.js +3 -17
  226. package/dist/deploy/build-context.d.ts +21 -0
  227. package/dist/deploy/build-context.js +137 -0
  228. package/dist/deploy/channel-ingress.d.ts +26 -39
  229. package/dist/deploy/channel-ingress.js +57 -28
  230. package/dist/deploy/container.d.ts +20 -34
  231. package/dist/deploy/container.js +49 -100
  232. package/dist/deploy/docker/plan.d.ts +12 -11
  233. package/dist/deploy/docker/plan.js +43 -24
  234. package/dist/deploy/docker/run.d.ts +22 -25
  235. package/dist/deploy/docker/run.js +39 -37
  236. package/dist/deploy/fly/plan.d.ts +15 -44
  237. package/dist/deploy/fly/plan.js +41 -73
  238. package/dist/deploy/fly/run.d.ts +16 -51
  239. package/dist/deploy/fly/run.js +42 -95
  240. package/dist/deploy/hosts.d.ts +24 -3
  241. package/dist/deploy/hosts.js +24 -3
  242. package/dist/deploy/preflight.d.ts +47 -41
  243. package/dist/deploy/preflight.js +282 -383
  244. package/dist/deploy/railway/plan.d.ts +16 -42
  245. package/dist/deploy/railway/plan.js +42 -60
  246. package/dist/deploy/railway/run.d.ts +30 -68
  247. package/dist/deploy/railway/run.js +86 -100
  248. package/dist/deploy/registration-gate.d.ts +6 -13
  249. package/dist/deploy/residency.d.ts +37 -0
  250. package/dist/deploy/residency.js +24 -0
  251. package/dist/deploy/runner.d.ts +10 -9
  252. package/dist/deploy/runner.js +7 -18
  253. package/dist/deploy/secrets.d.ts +40 -41
  254. package/dist/deploy/secrets.js +117 -103
  255. package/dist/deploy/workspace.d.ts +50 -0
  256. package/dist/deploy/workspace.js +197 -0
  257. package/dist/dev-supervisor.d.ts +1 -9
  258. package/dist/dev-supervisor.js +21 -64
  259. package/dist/effect-port.d.ts +50 -0
  260. package/dist/effect-port.js +77 -0
  261. package/dist/engines/pi/agent-session-factory.d.ts +50 -69
  262. package/dist/engines/pi/agent-session-factory.js +249 -146
  263. package/dist/engines/pi/auth.d.ts +15 -11
  264. package/dist/engines/pi/auth.js +96 -99
  265. package/dist/engines/pi/chat.d.ts +1 -5
  266. package/dist/engines/pi/chat.js +2 -14
  267. package/dist/engines/pi/config.d.ts +53 -51
  268. package/dist/engines/pi/config.js +83 -92
  269. package/dist/engines/pi/create.d.ts +60 -75
  270. package/dist/engines/pi/create.js +108 -141
  271. package/dist/engines/pi/definition.d.ts +14 -58
  272. package/dist/engines/pi/definition.js +21 -85
  273. package/dist/engines/pi/invoke-session.d.ts +5 -25
  274. package/dist/engines/pi/invoke-session.js +209 -244
  275. package/dist/engines/pi/login.d.ts +95 -35
  276. package/dist/engines/pi/login.js +259 -107
  277. package/dist/engines/pi/machine.d.ts +31 -0
  278. package/dist/engines/pi/machine.js +106 -0
  279. package/dist/engines/pi/models.d.ts +70 -89
  280. package/dist/engines/pi/models.js +188 -81
  281. package/dist/engines/pi/open.d.ts +65 -53
  282. package/dist/engines/pi/open.js +129 -69
  283. package/dist/engines/pi/report.d.ts +3 -12
  284. package/dist/engines/pi/report.js +2 -16
  285. package/dist/engines/pi/search-tools.d.ts +1 -9
  286. package/dist/engines/pi/search-tools.js +26 -57
  287. package/dist/engines/pi/service.d.ts +2 -16
  288. package/dist/engines/pi/service.js +2 -16
  289. package/dist/engines/pi/session-builder.d.ts +5 -9
  290. package/dist/engines/pi/session-builder.js +44 -82
  291. package/dist/engines/pi/session-control.js +286 -269
  292. package/dist/engines/pi/session-effects.d.ts +14 -0
  293. package/dist/engines/pi/session-effects.js +30 -0
  294. package/dist/engines/pi/session-inheritance.d.ts +9 -18
  295. package/dist/engines/pi/session-inheritance.js +103 -160
  296. package/dist/engines/pi/session-markers.d.ts +31 -30
  297. package/dist/engines/pi/session-markers.js +26 -25
  298. package/dist/engines/pi/session-settings.d.ts +6 -22
  299. package/dist/engines/pi/session-settings.js +10 -21
  300. package/dist/engines/pi/session-store.d.ts +26 -92
  301. package/dist/engines/pi/session-store.js +96 -310
  302. package/dist/engines/pi/tool-context.d.ts +17 -45
  303. package/dist/engines/pi/tool-context.js +7 -26
  304. package/dist/engines/pi/tool.d.ts +45 -36
  305. package/dist/engines/pi/tool.js +43 -57
  306. package/dist/engines/pi/turn-kit.d.ts +14 -52
  307. package/dist/engines/pi/turn-kit.js +18 -67
  308. package/dist/engines/pi/wake-tool.d.ts +2 -10
  309. package/dist/engines/pi/wake-tool.js +7 -27
  310. package/dist/env.d.ts +27 -28
  311. package/dist/env.js +45 -50
  312. package/dist/index.js +0 -2
  313. package/dist/lark.d.ts +1 -2
  314. package/dist/lark.js +1 -2
  315. package/dist/loader.d.ts +14 -55
  316. package/dist/loader.js +24 -43
  317. package/dist/log.d.ts +2 -12
  318. package/dist/log.js +3 -24
  319. package/dist/node.d.ts +2 -15
  320. package/dist/node.js +2 -18
  321. package/dist/observe.d.ts +2 -8
  322. package/dist/open-url.d.ts +1 -1
  323. package/dist/open-url.js +1 -1
  324. package/dist/paths.d.ts +70 -134
  325. package/dist/paths.js +165 -232
  326. package/dist/pi.d.ts +4 -6
  327. package/dist/pi.js +3 -3
  328. package/dist/proxy.d.ts +16 -6
  329. package/dist/proxy.js +52 -16
  330. package/dist/runtime.d.ts +18 -12
  331. package/dist/runtime.js +18 -11
  332. package/dist/scaffold/add-channel.d.ts +14 -31
  333. package/dist/scaffold/add-channel.js +48 -112
  334. package/dist/scaffold/init.d.ts +9 -31
  335. package/dist/scaffold/init.js +45 -138
  336. package/dist/scaffold/templates/env.example +6 -4
  337. package/dist/scaffold/templates/fastagent.config.ts +36 -0
  338. package/dist/scaffold/templates.d.ts +3 -3
  339. package/dist/scaffold/templates.js +5 -8
  340. package/dist/scaffold/vendor-skill.d.ts +2 -5
  341. package/dist/scaffold/vendor-skill.js +19 -29
  342. package/dist/schedule/cron.d.ts +9 -5
  343. package/dist/schedule/cron.js +9 -11
  344. package/dist/schedule/discover.d.ts +12 -12
  345. package/dist/schedule/discover.js +43 -34
  346. package/dist/schedule/routine.d.ts +43 -0
  347. package/dist/schedule/routine.js +32 -0
  348. package/dist/schedule/run.d.ts +55 -0
  349. package/dist/schedule/run.js +146 -0
  350. package/dist/schedule/scheduler.d.ts +44 -55
  351. package/dist/schedule/scheduler.js +291 -265
  352. package/dist/schedule/state.d.ts +105 -10
  353. package/dist/schedule/state.js +293 -19
  354. package/dist/schedule/wake-alarm.d.ts +10 -23
  355. package/dist/schedule/wake-alarm.js +91 -121
  356. package/dist/schedule/wakeups.d.ts +23 -39
  357. package/dist/schedule/wakeups.js +33 -75
  358. package/dist/secrets-gate.d.ts +32 -0
  359. package/dist/secrets-gate.js +33 -0
  360. package/dist/service.d.ts +137 -98
  361. package/dist/service.js +250 -252
  362. package/dist/session-remote.d.ts +33 -44
  363. package/dist/session-remote.js +203 -192
  364. package/dist/session.d.ts +153 -228
  365. package/dist/session.js +33 -55
  366. package/dist/tunnel.d.ts +8 -36
  367. package/dist/tunnel.js +24 -65
  368. package/dist/version.d.ts +2 -2
  369. package/dist/version.js +2 -2
  370. package/package.json +10 -16
  371. package/dist/channels/agentcore-state.d.ts +0 -77
  372. package/dist/channels/agentcore-state.js +0 -276
  373. package/dist/channels/github/github.d.ts +0 -31
  374. package/dist/channels/github/github.js +0 -89
  375. package/dist/channels/github/scaffold/channel.ts +0 -25
  376. package/dist/cli/commands/attach.d.ts +0 -108
  377. package/dist/cli/commands/attach.js +0 -637
  378. package/dist/cli/commands/fire.d.ts +0 -7
  379. package/dist/cli/commands/fire.js +0 -38
  380. package/dist/cli/commands/schedule.d.ts +0 -12
  381. package/dist/cli/commands/schedule.js +0 -87
  382. package/dist/github.d.ts +0 -3
  383. package/dist/github.js +0 -2
  384. package/dist/scaffold/templates/fastagent.config.mjs +0 -23
  385. package/dist/schedule/audit.d.ts +0 -18
  386. package/dist/schedule/audit.js +0 -57
  387. package/dist/schedule/schedule.d.ts +0 -35
  388. package/dist/schedule/schedule.js +0 -23
package/dist/proxy.js CHANGED
@@ -1,23 +1,59 @@
1
- // Namespace import (not `import { install }`): Bun's `undici` is a native shim that omits `install`, and
2
- // a STATIC NAMED import of a missing export fails at link time — crashing `fastagent start` under Bun
3
- // before any code runs. A namespace import binds whatever exports exist; the Bun guard below then never
4
- // touches the Node-only members.
1
+ // Namespace import (not `import { install }`): Bun's `undici` is a native shim that omits `install`, and a STATIC
2
+ // NAMED import of a missing export fails at link time.
5
3
  import * as undici from "undici";
6
- /** The Bun runtime (`process.versions.bun` is set only there). Bun's native fetch already honors the
7
- * proxy env vars and decompresses gzip, and its `undici` shim has no `install()` — so the Node-only
8
- * undici wiring below is both unnecessary and unavailable there. */
4
+ /** The Bun runtime (`process.versions.bun` is set only there). */
9
5
  const isBun = typeof process.versions.bun === "string";
10
6
  /**
11
- * Route fetch through HTTPS_PROXY and keep fetch + dispatcher on the SAME undici implementation. Any
12
- * process that hits a provider or a channel API needs this UNDER NODE: Node's fetch does not honor
13
- * HTTPS_PROXY by itself (so login's OAuth token exchange, model calls, and a webhook setWebhook would go
14
- * direct, bypassing a region proxy), and Node 26's bundled fetch skips gzip decompression without
15
- * install() (empty stopReason:"stop"). A process-global side effect — call it once at a command/entry
16
- * start. No-op under Bun, whose native fetch already does both (and lacks the undici entry points).
7
+ * What is ALWAYS exempt from the proxy. `EnvHttpProxyAgent` proxies EVERYTHING while NO_PROXY is empty — loopback
8
+ * included ("Always proxy if NO_PROXY is not set or empty") — so installing a proxy would otherwise send this process's
9
+ * own local traffic through it: a Docker health probe on 127.0.0.1, a control-plane call to a local serve, an `ssh -L`
10
+ * forward given to a control-plane client's `url`. None of those are what a proxy variable is asking for.
11
+ *
12
+ * APPENDED to a declared NO_PROXY rather than used as its default, because `NO_PROXY=.corp.com` is the normal shape of
13
+ * a corporate environment and it is not a statement about 127.0.0.1: replacing would hand exactly the users who have a
14
+ * proxy back the failure this exists to remove, as a probe timeout rather than an error.
15
+ */
16
+ const LOOPBACK = "localhost,127.0.0.1,::1";
17
+ /**
18
+ * The exemption list the dispatcher is built with: whatever the environment declares, plus {@link LOOPBACK}.
19
+ *
20
+ * Its own function because installing happens ONCE per process, so these branches are unreachable through
21
+ * {@link installProxyFetch} — and the wildcard one is the kind that is silently wrong until someone reads it.
22
+ */
23
+ export function noProxyList(env = process.env) {
24
+ // Mirroring undici's own precedence (lowercase first) so our value cannot disagree with the one it would read.
25
+ // `||`, not `??`: a `NO_PROXY=` line in a .env is an empty string, and undici treats it as "proxy everything".
26
+ const declared = (env.no_proxy || env.NO_PROXY)?.trim();
27
+ if (!declared)
28
+ return LOOPBACK;
29
+ // `*` is undici's "never proxy anything", matched EXACTLY (`#noProxyValue === '*'`). Appending would demote it to a
30
+ // hostname that matches nothing — turning "disable the proxy" into "proxy everything but loopback".
31
+ return declared === "*" ? declared : `${declared},${LOOPBACK}`;
32
+ }
33
+ /** The install is process-global and once is enough; a second call would only leak the first dispatcher (nothing
34
+ * closes it) and re-read the same environment. */
35
+ let installed = false;
36
+ /** Whoever owned `fetch` before this module loaded. A caller that replaced it since (a test's mock, an embedder's
37
+ * instrumentation) meant it, so the swap below leaves it alone — the dispatcher still routes everything that runs
38
+ * on undici's global. */
39
+ const originalFetch = globalThis.fetch;
40
+ /**
41
+ * Point this process's fetch at the proxy the environment declares, with loopback exempt by default.
42
+ *
43
+ * Two jobs in one, and both are needed UNDER NODE: Node's fetch does not honor HTTP(S)_PROXY at all, and Node 26's
44
+ * bundled fetch skips gzip decompression when it dispatches through npm undici (empty `stopReason:"stop"`), so fetch
45
+ * and the dispatcher must come from the SAME undici. No-op under Bun, whose native fetch already does both (and
46
+ * lacks these entry points).
47
+ *
48
+ * Call it through `enterAgentEnv` (env.ts) rather than directly: the proxy may be declared in the agent's own `.env`,
49
+ * so it has to be read first. The only direct callers are the ones with no agent directory to read.
17
50
  */
18
51
  export function installProxyFetch() {
19
- if (isBun)
52
+ if (isBun || installed)
20
53
  return;
21
- undici.setGlobalDispatcher(new undici.EnvHttpProxyAgent());
22
- undici.install();
54
+ undici.setGlobalDispatcher(new undici.EnvHttpProxyAgent({ noProxy: noProxyList() }));
55
+ // Skipping `install` leaves the caller's fetch in place, and with it Node 26's missing gzip decompression above.
56
+ if (globalThis.fetch === originalFetch)
57
+ undici.install();
58
+ installed = true; // last: a throw above must leave the latch open, or the process runs un-proxied forever
23
59
  }
package/dist/runtime.d.ts CHANGED
@@ -1,26 +1,32 @@
1
- /** How to install and run the AGENT: its package.json + lockfile decide, never the surrounding
2
- * workspace's (whose toolchain is the agent's runtime concern, not fastagent's). */
1
+ /**
2
+ * How to install and run the AGENT: its package.json + lockfile decide, never the surrounding workspace's (whose
3
+ * toolchain is the agent's runtime concern, not fastagent's).
4
+ */
3
5
  export interface AgentRuntime {
4
- /** The JS runtime the agent targets — drives the generated Dockerfile base + install/run commands
5
- * and the package-manager hints in `init`/`add`. */
6
+ /**
7
+ * The JS runtime the agent targets — drives the generated Dockerfile base + install/run commands and the
8
+ * package-manager hints in `init`/`add`.
9
+ */
6
10
  runtime: "node" | "bun";
7
- /** For `runtime: "bun"`, the version from package.json's `packageManager: "bun@x"` (undefined if a bun
8
- * lockfile made it bun but no version is pinned). */
11
+ /**
12
+ * For `runtime: "bun"`, the version from package.json's `packageManager: "bun@x"` (undefined if a bun lockfile made
13
+ * it bun but no version is pinned).
14
+ */
9
15
  bunVersion?: string;
10
16
  /** Whether the runtime's lockfile is present (package-lock.json for node, bun.lock/bun.lockb for bun). */
11
17
  hasLockfile: boolean;
12
18
  }
13
19
  /**
14
- * Detect which JS runtime an agent targets: `bun` when package.json's `packageManager` is `bun@…` OR a
15
- * bun lockfile (bun.lock/bun.lockb) is present, else `node`. `pkg` is the already-parsed package.json (or
16
- * `{}` when there is none / it is malformed). One source for the deploy Dockerfile and the CLI hints, so
17
- * they agree on what the agent is.
20
+ * Detect which JS runtime an agent targets: `bun` when package.json's `packageManager` is `bun@…` OR a bun lockfile
21
+ * (bun.lock/bun.lockb) is present, else `node`.
18
22
  */
19
23
  export declare function detectRuntime(dir: string, pkg: {
20
24
  packageManager?: unknown;
21
25
  }): AgentRuntime;
22
- /** Parse `<dir>/package.json`, or `{}` when absent/malformed (a real build surfaces the actual error).
23
- * The already-parsed input to {@link detectRuntime} and the deploy dep/lockfile checks. */
26
+ /**
27
+ * Parse `<dir>/package.json`, or `{}` when there is none (a markdown-only agent). A file that does not parse throws,
28
+ * naming itself: read as `{}` it became "does not list @fastagent-sh/fastagent" and an npm-based image, both wrong.
29
+ */
24
30
  export declare function readPackageJson(dir: string): Promise<{
25
31
  packageManager?: unknown;
26
32
  dependencies?: Record<string, unknown>;
package/dist/runtime.js CHANGED
@@ -2,28 +2,35 @@ import { existsSync } from "node:fs";
2
2
  import { readFile } from "node:fs/promises";
3
3
  import { join } from "node:path";
4
4
  /**
5
- * Detect which JS runtime an agent targets: `bun` when package.json's `packageManager` is `bun@…` OR a
6
- * bun lockfile (bun.lock/bun.lockb) is present, else `node`. `pkg` is the already-parsed package.json (or
7
- * `{}` when there is none / it is malformed). One source for the deploy Dockerfile and the CLI hints, so
8
- * they agree on what the agent is.
5
+ * Detect which JS runtime an agent targets: `bun` when package.json's `packageManager` is `bun@…` OR a bun lockfile
6
+ * (bun.lock/bun.lockb) is present, else `node`.
9
7
  */
10
8
  export function detectRuntime(dir, pkg) {
11
9
  const pm = typeof pkg.packageManager === "string" ? pkg.packageManager : "";
12
10
  const bunLock = existsSync(join(dir, "bun.lock")) || existsSync(join(dir, "bun.lockb"));
13
11
  if (pm.startsWith("bun@") || bunLock) {
14
- // corepack pins as `bun@1.3.13+sha256.<hash>`; the version (a Docker tag for oven/bun) is the part
15
- // BEFORE `+` — keeping the hash would build an invalid `FROM oven/bun:1.3.13+sha256…` tag.
12
+ // corepack pins as `bun@1.3.13+sha256.<hash>`; the version (a Docker tag for oven/bun) is the part BEFORE `+`.
16
13
  return { runtime: "bun", bunVersion: pm.match(/^bun@([^+]+)/)?.[1], hasLockfile: bunLock };
17
14
  }
18
15
  return { runtime: "node", hasLockfile: existsSync(join(dir, "package-lock.json")) };
19
16
  }
20
- /** Parse `<dir>/package.json`, or `{}` when absent/malformed (a real build surfaces the actual error).
21
- * The already-parsed input to {@link detectRuntime} and the deploy dep/lockfile checks. */
17
+ /**
18
+ * Parse `<dir>/package.json`, or `{}` when there is none (a markdown-only agent). A file that does not parse throws,
19
+ * naming itself: read as `{}` it became "does not list @fastagent-sh/fastagent" and an npm-based image, both wrong.
20
+ */
22
21
  export async function readPackageJson(dir) {
22
+ const path = join(dir, "package.json");
23
+ const raw = await readFile(path, "utf8").catch((error) => {
24
+ if (error.code === "ENOENT")
25
+ return undefined;
26
+ throw error;
27
+ });
28
+ if (raw === undefined)
29
+ return {};
23
30
  try {
24
- return JSON.parse(await readFile(join(dir, "package.json"), "utf8"));
31
+ return JSON.parse(raw);
25
32
  }
26
- catch {
27
- return {};
33
+ catch (error) {
34
+ throw new Error(`${path} is not valid JSON: ${error.message}`);
28
35
  }
29
36
  }
@@ -1,19 +1,17 @@
1
1
  import type { FeishuSubscriptionMode } from "../channels/feishu/setup-mode.ts";
2
- export type ChannelKind = "github" | "telegram" | "slack" | "feishu" | "lark";
3
- /** Group-visibility choice shared by the slack/feishu/lark onboarding flows. Each channel keeps its
4
- * own channel-level type (`SlackGroupBehavior`, `FeishuGroupBehavior`) — this is the CLI-side value. */
2
+ export type ChannelKind = "telegram" | "slack" | "feishu" | "lark";
3
+ /** Group-visibility choice shared by the slack/feishu/lark onboarding flows. */
5
4
  export type GroupBehavior = "context" | "mentions";
6
- /** A resolved group-behavior decision plus whether the author actually chose it (flag or prompt).
7
- * A defaulted "context" (non-interactive, no flag) must never drive a sensitive-scope write. */
5
+ /** A resolved group-behavior decision plus whether the author actually chose it (flag or prompt). */
8
6
  export interface GroupBehaviorChoice {
9
7
  behavior: GroupBehavior;
10
8
  explicit: boolean;
11
9
  }
12
- /** An env var a scaffolded channel reads. `generate` = a random-string secret the CLI can pre-fill. */
10
+ /** An env var a scaffolded channel reads. */
13
11
  export interface ChannelEnv {
14
12
  name: string;
15
13
  hint: string;
16
- /** Required for the channel to run. Optional values are deployed when present but never gate deploy. */
14
+ /** Required for the channel to run. */
17
15
  required: boolean;
18
16
  generate?: boolean;
19
17
  }
@@ -25,9 +23,8 @@ export declare function channelSetup(kind: ChannelKind, ingress?: FeishuSubscrip
25
23
  steps: string[];
26
24
  };
27
25
  /**
28
- * Append a channel's env vars (commented placeholders + hints) to `.env.example`, so a developer who
29
- * copies it to `.env` finds the vars already there. No-op when there is no `.env.example` or the block
30
- * is already present. Placeholders only — no real secret lands in the committable template.
26
+ * Append a channel's env vars (commented placeholders + hints) to `.env.example`, so a developer who copies it to
27
+ * `.env` finds the vars already there.
31
28
  */
32
29
  export declare function appendChannelEnv(dir: string, kind: ChannelKind, ingress?: FeishuSubscriptionMode): Promise<boolean>;
33
30
  export interface DotEnvWriteResult {
@@ -35,38 +32,24 @@ export interface DotEnvWriteResult {
35
32
  written: string[];
36
33
  /** Vars already present with a non-empty active value; left untouched and omitted from next steps. */
37
34
  alreadySet: string[];
38
- /** Set when the secrets dir is an operator-chosen one (`FASTAGENT_SECRETS_DIR`) carrying no
39
- * `.gitignore`: a secret was just written into a directory fastagent does not own, so the caller
40
- * states the fact rather than dropping a `*`-ignoring file into someone else's path. */
35
+ /** Set when the secrets dir is an operator-chosen one (`FASTAGENT_SECRETS_DIR`) carrying no `.gitignore`. */
41
36
  unprotectedSecretsDir?: string;
42
37
  }
43
38
  /**
44
- * Append generated channel secrets to the agent's `.env` (`.secrets/.env` — never `.env.example`)
45
- * Existing non-empty values are kept — EXCEPT the names listed in
46
- * `overwrite`: those are authoritative (e.g. the credentials of an app `add feishu` JUST minted —
47
- * skipping them for a stale value would silently discard a fresh, unrecoverable secret). Manual values
48
- * (e.g. TELEGRAM_BOT_TOKEN from BotFather) are added only as commented placeholders, so the file is
49
- * ready to edit while no fake secret is committed to the user's mental model.
39
+ * Append generated channel secrets to the agent's `.env` (`.secrets/.env` — never `.env.example`) Existing non-empty
40
+ * values are kept.
50
41
  */
51
42
  export declare function appendChannelDotEnv(dir: string, kind: ChannelKind, generated: Record<string, string>, overwrite?: readonly string[], ingress?: FeishuSubscriptionMode): Promise<DotEnvWriteResult>;
52
43
  /** Whether a channel file already exists — checked before any mutation, so a no-clobber re-add is side-effect-free. */
53
44
  export declare function channelExists(dir: string, kind: ChannelKind): Promise<boolean>;
54
- /**
55
- * Scaffold `channels/<kind>.ts` into {@link dir}. Never clobbers an existing file (the glue is
56
- * authored content). The wx write is the TOCTOU safety net behind {@link channelExists}.
57
- */
45
+ /** Scaffold `channels/<kind>.ts` into {@link dir}. */
58
46
  export declare function scaffoldChannel(dir: string, kind: ChannelKind, options?: {
59
47
  ingress?: FeishuSubscriptionMode;
60
48
  }): Promise<string>;
61
- /**
62
- * The bundle's companion tools (every `.ts` beside `channel.ts` → `tools/<name>`). Unlike the channel
63
- * file they are the package's, not authored glue, so they are written on EVERY add: re-running
64
- * `add <kind>` is how an upgraded tool reaches an existing agent.
65
- */
49
+ /** The bundle's companion tools (every `.ts` beside `channel.ts` → `tools/<name>`). */
66
50
  export declare function scaffoldCompanionTools(dir: string, kind: ChannelKind): Promise<string[]>;
67
51
  /**
68
- * Verify the AGENT DIR is ready to host a channel: an ESM package.json that declares
69
- * `@fastagent-sh/fastagent` (the channel file imports it). `add` checks and guides, never bootstraps — that
70
- * is `init`'s job.
52
+ * Verify the AGENT DIR is ready to host a channel: an ESM package.json that declares `@fastagent-sh/fastagent` (the
53
+ * channel file imports it).
71
54
  */
72
55
  export declare function assertChannelReady(dir: string): Promise<void>;
@@ -1,32 +1,14 @@
1
1
  /**
2
2
  * `fastagent add <channel>`: drop a `channels/<kind>.ts` adapter-glue file (+ any companion tool, +
3
- * `.secrets/.env.example` vars) into an existing agent. `add` checks and guides; it never
4
- * bootstraps an agent (that is `init`'s job). Each channel's template files live in its own bundle
5
- * at src/channels/<kind>/scaffold/, read here at scaffold time.
3
+ * `.secrets/.env.example` vars) into an existing agent.
6
4
  */
7
5
  import { appendFile, mkdir, readFile, writeFile } from "node:fs/promises";
8
6
  import { dirname, join } from "node:path";
9
7
  import { detectRuntime } from "../runtime.js";
10
- import { SECRETS_DIRNAME, assertInsideAgentDir, ensureSecretsDir, exists } from "../paths.js";
8
+ import { SECRETS_DIRNAME, SECRET_FILE_MODE, assertInsideAgentDir, exists } from "../paths.js";
11
9
  import { baseTemplate, channelBundleFiles, channelTemplate } from "./templates.js";
12
10
  import { dotEnvPath, envExamplePath, parseEnvContent } from "../env.js";
13
11
  const CHANNEL_SCAFFOLDS = {
14
- github: {
15
- env: [
16
- {
17
- name: "GITHUB_WEBHOOK_SECRET",
18
- hint: "any random string; set the same value in the GitHub webhook",
19
- required: true,
20
- generate: true,
21
- },
22
- ],
23
- // `{channel}` / `{tools}` are path placeholders the CLI resolves to the real agent-dir-relative
24
- // location — the CLI holds no channel-private filenames.
25
- steps: [
26
- "edit {channel} — map events to intents in on()",
27
- "add the webhook in your repo (Settings → Webhooks): Payload URL = <public-url>/webhook, content type application/json",
28
- ],
29
- },
30
12
  telegram: {
31
13
  env: [
32
14
  { name: "TELEGRAM_BOT_TOKEN", hint: "from @BotFather → /newbot", required: true },
@@ -54,11 +36,8 @@ const CHANNEL_SCAFFOLDS = {
54
36
  "the agent can send messages or files by calling the scaffolded {tools}/slack-send.ts tool",
55
37
  ],
56
38
  },
57
- // Feishu is the canonical engine/cloud; Lark international reuses its protocol through a degraded
58
- // compatibility profile. Each remains its own channel KIND: route, env, state, console, onboarding.
59
- // This table is the webhook setup; continuous mode below selects only App ID/Secret. No `generate`
60
- // in either: values come FROM the platform. `add feishu` scan-creates the app; Lark lacks that
61
- // control-plane capability, so `add lark` guides console credential collection.
39
+ // Feishu is the canonical engine/cloud; Lark international reuses its protocol through a degraded compatibility
40
+ // profile.
62
41
  feishu: {
63
42
  env: [
64
43
  {
@@ -165,9 +144,8 @@ export function channelSetup(kind, ingress = "webhook", groupBehavior) {
165
144
  return { env: setup.env, steps: setup.steps };
166
145
  }
167
146
  /**
168
- * Append a channel's env vars (commented placeholders + hints) to `.env.example`, so a developer who
169
- * copies it to `.env` finds the vars already there. No-op when there is no `.env.example` or the block
170
- * is already present. Placeholders only — no real secret lands in the committable template.
147
+ * Append a channel's env vars (commented placeholders + hints) to `.env.example`, so a developer who copies it to
148
+ * `.env` finds the vars already there.
171
149
  */
172
150
  export async function appendChannelEnv(dir, kind, ingress = "webhook") {
173
151
  const file = envExamplePath(dir);
@@ -183,19 +161,14 @@ export async function appendChannelEnv(dir, kind, ingress = "webhook") {
183
161
  const marker = `# --- ${kind} channel ---`;
184
162
  if (current.includes(marker))
185
163
  return false;
186
- // Hint on its OWN line above the placeholder (like the base env.example template) — never inline
187
- // after `=`: loadEnvFile does not strip trailing comments, so an uncommented `KEY= # hint` (or a
188
- // value pasted before the `#`) would carry the hint text into the parsed value.
164
+ // Hint on its OWN line above the placeholder (like the base env.example template).
189
165
  const block = `\n${marker}\n${channelSetup(kind, ingress)
190
166
  .env.map((e) => `# ${e.hint}\n# ${e.name}=`)
191
167
  .join("\n")}\n`;
192
168
  await appendFile(file, block);
193
169
  return true;
194
170
  }
195
- /** Whether `.env` content carries a non-empty ACTIVE value for `name` — decided by THE .env parser
196
- * ({@link parseEnvContent}), not a re-implementation, so this check can never disagree with what
197
- * `loadEnvFile` will actually read (a missed match here would append a second assignment that
198
- * last-wins over the user's working secret). */
171
+ /** Whether `.env` content carries a non-empty ACTIVE value for `name`. */
199
172
  function hasActiveEnvValue(content, name) {
200
173
  return (parseEnvContent(content).get(name)?.trim() ?? "") !== "";
201
174
  }
@@ -203,34 +176,19 @@ function mentionsEnvName(content, name) {
203
176
  return content.split("\n").some((line) => new RegExp(`^\\s*#?\\s*${name}\\s*=`).test(line));
204
177
  }
205
178
  /**
206
- * Append generated channel secrets to the agent's `.env` (`.secrets/.env` — never `.env.example`)
207
- * Existing non-empty values are kept — EXCEPT the names listed in
208
- * `overwrite`: those are authoritative (e.g. the credentials of an app `add feishu` JUST minted —
209
- * skipping them for a stale value would silently discard a fresh, unrecoverable secret). Manual values
210
- * (e.g. TELEGRAM_BOT_TOKEN from BotFather) are added only as commented placeholders, so the file is
211
- * ready to edit while no fake secret is committed to the user's mental model.
179
+ * Append generated channel secrets to the agent's `.env` (`.secrets/.env` — never `.env.example`) Existing non-empty
180
+ * values are kept.
212
181
  */
213
182
  export async function appendChannelDotEnv(dir, kind, generated, overwrite = [], ingress = "webhook") {
214
183
  const file = dotEnvPath(dir);
215
184
  const secretsDir = dirname(file);
216
- await ensureSecretsDir(secretsDir);
217
- // THE one exception to "fastagent has no opinion about git": the directory it writes secrets into
218
- // carries its own `.gitignore`. `init` writes it, and so does this — the reachable case where it is
219
- // missing (a hand-made agent) is exactly the one where the next line mints an unrecoverable app
220
- // secret. `wx`, so a file the author wrote is never touched; the accepted cost is that someone who
221
- // DELETED it to track secrets deliberately gets it back once. The risk is not symmetric — that is an
222
- // annoyance; the other way is a published credential.
223
- //
224
- // Scoped to the DEFAULT `<agentDir>/.secrets`, which is fastagent's own directory. A dir named by
225
- // `FASTAGENT_SECRETS_DIR` belongs to the operator, and this template is `*` plus two negations —
226
- // dropping it there would hide that directory's OTHER contents from their `git add`, which is a
227
- // bigger harm than the one it prevents, and inflicted on a path they chose deliberately. They get the
228
- // fact instead, and own the decision.
185
+ await mkdir(secretsDir, { recursive: true });
186
+ // THE one exception to "fastagent has no opinion about git": the directory it writes secrets into carries its own
187
+ // `.gitignore`.
229
188
  const owned = secretsDir === join(dir, SECRETS_DIRNAME);
230
189
  let unprotectedSecretsDir;
231
190
  if (owned) {
232
- // Only EEXIST is tolerable (already protected, or a concurrent writer). A permission/disk failure on
233
- // the file that keeps credentials out of git must surface, not be swallowed.
191
+ // Only EEXIST is tolerable (already protected, or a concurrent writer).
234
192
  await writeFile(join(secretsDir, ".gitignore"), baseTemplate("secrets.gitignore"), { flag: "wx" }).catch((e) => {
235
193
  if (e.code !== "EEXIST")
236
194
  throw e;
@@ -260,10 +218,8 @@ export async function appendChannelDotEnv(dir, kind, generated, overwrite = [],
260
218
  continue;
261
219
  const value = generated[e.name];
262
220
  if (value !== undefined) {
263
- // An ACTIVE but EMPTY assignment already in the file (an uncommented, unfilled placeholder from
264
- // `cp .env.example .env`) must be replaced IN PLACE: a new line written anywhere else either loses
265
- // to it or wins by position under last-wins — both silently. Replace the LAST occurrence (the one
266
- // the parser would honor). Line-level match uses THE parser, never a hand regex.
221
+ // An ACTIVE but EMPTY assignment already in the file (an uncommented, unfilled placeholder from `cp
222
+ // .env.example .env`) must be replaced IN PLACE.
267
223
  let idx = -1;
268
224
  for (let i = contentLines.length - 1; i >= 0; i--) {
269
225
  if (parseEnvContent(contentLines[i]).has(e.name)) {
@@ -281,24 +237,28 @@ export async function appendChannelDotEnv(dir, kind, generated, overwrite = [],
281
237
  written.push(e.name);
282
238
  }
283
239
  else if (!mentionsEnvName(current, e.name)) {
284
- // Hint above, never inline after `=` — see appendChannelEnv (this IS the file loadEnvFile reads).
240
+ // Hint above, never inline after `=` — see appendChannelEnv (this IS the file loadDotEnv reads).
285
241
  lines.push(`# ${e.hint}`, `# ${e.name}=`);
286
242
  }
287
243
  }
244
+ // 0600 on the file this call CREATES, and nothing at all about one it did not: a `.env` the operator made with
245
+ // `cp .secrets/.env.example .secrets/.env` carries the mode that command gave it, and re-deciding it would be
246
+ // fastagent changing the permissions of a file it does not own.
247
+ const secret = { mode: SECRET_FILE_MODE };
288
248
  if (replacedInPlace) {
289
249
  current = contentLines.join("\n");
290
- await writeFile(file, current);
250
+ await writeFile(file, current, secret);
291
251
  }
292
252
  if (lines.length > 0) {
293
253
  const marker = `# --- ${kind} channel ---`;
294
254
  if (current.includes(marker)) {
295
- // A marker already present (e.g. a .env copied from .env.example) — slot the new lines under it
296
- // instead of orphaning them at the end of the file.
297
- await writeFile(file, current.replace(marker, `${marker}\n${lines.join("\n")}`));
255
+ // A marker already present (e.g. a .env copied from .env.example) — slot the new lines under it instead of
256
+ // orphaning them at the end of the file.
257
+ await writeFile(file, current.replace(marker, `${marker}\n${lines.join("\n")}`), secret);
298
258
  }
299
259
  else {
300
260
  const prefix = current === "" ? "" : current.endsWith("\n") ? "\n" : "\n\n";
301
- await appendFile(file, `${prefix}${marker}\n${lines.join("\n")}\n`);
261
+ await appendFile(file, `${prefix}${marker}\n${lines.join("\n")}\n`, secret);
302
262
  }
303
263
  }
304
264
  return { written, alreadySet, unprotectedSecretsDir };
@@ -311,10 +271,7 @@ function channelPath(dir, kind) {
311
271
  export async function channelExists(dir, kind) {
312
272
  return exists(channelPath(dir, kind));
313
273
  }
314
- /**
315
- * Scaffold `channels/<kind>.ts` into {@link dir}. Never clobbers an existing file (the glue is
316
- * authored content). The wx write is the TOCTOU safety net behind {@link channelExists}.
317
- */
274
+ /** Scaffold `channels/<kind>.ts` into {@link dir}. */
318
275
  export async function scaffoldChannel(dir, kind, options = {}) {
319
276
  const channelsDir = join(dir, "channels");
320
277
  // Don't write through a channels/ symlink that escapes the agent dir; one inside it is fine.
@@ -324,48 +281,29 @@ export async function scaffoldChannel(dir, kind, options = {}) {
324
281
  throw new Error(`${file} already exists — edit it, or remove it to re-scaffold`);
325
282
  }
326
283
  await mkdir(channelsDir, { recursive: true });
327
- let content = channelTemplate(kind, "channel.ts");
328
- if ((kind === "feishu" || kind === "lark") && options.ingress === "websocket") {
329
- const factory = `${kind}Channel`;
330
- const wsFactory = `${kind}WebSocketChannel`;
331
- let configured = content
332
- .replace(`import { ${factory} }`, `import { ${wsFactory} }`)
333
- .replace(`export default ${factory}({`, `export default ${wsFactory}({`);
334
- if (configured === content)
335
- throw new Error(`${kind} channel template has no factory anchors`);
336
- const prefix = kind === "feishu" ? "FEISHU" : "LARK";
337
- const exportAt = configured.indexOf("export default");
338
- const importEnd = configured.indexOf("\n\n");
339
- if (exportAt < 0 || importEnd < 0)
340
- throw new Error(`${kind} channel template header anchors are missing`);
341
- const brand = kind === "feishu" ? "Feishu" : "Lark";
342
- configured =
343
- `${configured.slice(0, importEnd)}\n\n` +
344
- `// ${brand} WebSocket long connection: the process connects OUT to the platform, so no public URL,\n` +
345
- `// Verification Token, Encrypt Key, or --tunnel is needed. In Events & Callbacks choose long\n` +
346
- `// connection, subscribe im.message.receive_v1, then publish the app version. Keep one process\n` +
347
- `// running in production: scale-to-zero/App Sleeping would disconnect ingress.\n` +
348
- configured.slice(exportAt);
349
- configured = configured
350
- .split("\n")
351
- .filter((line) => !line.includes(`verificationToken: process.env.${prefix}_VERIFICATION_TOKEN`) &&
352
- !line.includes(`encryptKey: process.env.${prefix}_ENCRYPT_KEY`))
353
- .join("\n");
354
- content = configured;
284
+ // The long-connection variant is its own template FILE, not a transform of the webhook one: a transform
285
+ // has to be re-taught every time the template changes shape, and a missed anchor scaffolds a channel
286
+ // configured for the wrong ingress with nothing failing.
287
+ // Which templates exist is the BUNDLE's fact, asked here rather than assumed from the kind: only
288
+ // feishu/lark ship a long-connection variant today, and a caller asking for one that is missing
289
+ // must get that sentence, not the ENOENT of a path it never named.
290
+ const template = options.ingress === "websocket" ? "channel.websocket.ts" : "channel.ts";
291
+ // CHANNEL templates only: the bundle also holds companion tools (`telegram-send.ts`), and listing
292
+ // one as an available scaffold sends whoever reads this line looking for an ingress that is a tool.
293
+ const available = channelBundleFiles(kind).filter((f) => f.startsWith("channel."));
294
+ if (!available.includes(template)) {
295
+ throw new Error(`${kind} has no ${options.ingress ?? "webhook"} scaffold (${template} is not in its bundle) — ` +
296
+ `available: ${available.join(", ")}`);
355
297
  }
356
- await writeFile(file, content, { flag: "wx" });
298
+ await writeFile(file, channelTemplate(kind, template), { flag: "wx" });
357
299
  return file;
358
300
  }
359
- /**
360
- * The bundle's companion tools (every `.ts` beside `channel.ts` → `tools/<name>`). Unlike the channel
361
- * file they are the package's, not authored glue, so they are written on EVERY add: re-running
362
- * `add <kind>` is how an upgraded tool reaches an existing agent.
363
- */
301
+ /** The bundle's companion tools (every `.ts` beside `channel.ts` → `tools/<name>`). */
364
302
  export async function scaffoldCompanionTools(dir, kind) {
365
303
  const written = [];
366
304
  for (const name of channelBundleFiles(kind)) {
367
- if (name === "channel.ts")
368
- continue;
305
+ if (name.startsWith("channel."))
306
+ continue; // channel.ts / channel.websocket.ts are the channel, not tools
369
307
  const file = join(dir, "tools", name);
370
308
  await mkdir(dirname(file), { recursive: true });
371
309
  await writeFile(file, channelTemplate(kind, name));
@@ -374,9 +312,8 @@ export async function scaffoldCompanionTools(dir, kind) {
374
312
  return written;
375
313
  }
376
314
  /**
377
- * Verify the AGENT DIR is ready to host a channel: an ESM package.json that declares
378
- * `@fastagent-sh/fastagent` (the channel file imports it). `add` checks and guides, never bootstraps — that
379
- * is `init`'s job.
315
+ * Verify the AGENT DIR is ready to host a channel: an ESM package.json that declares `@fastagent-sh/fastagent` (the
316
+ * channel file imports it).
380
317
  */
381
318
  export async function assertChannelReady(dir) {
382
319
  const pkgPath = join(dir, "package.json");
@@ -386,10 +323,9 @@ export async function assertChannelReady(dir) {
386
323
  }
387
324
  catch (e) {
388
325
  if (e.code === "ENOENT") {
389
- // `dir` is the AGENT dir, so `fastagent init` here would nest a second agent inside it — the
390
- // right remedy is the missing manifest (a --minimal init writes none), or init in the workspace.
326
+ // `dir` is the AGENT dir, so `fastagent init` here would nest a second agent inside it.
391
327
  throw new Error(`${dir}: no package.json — a channel adapter is code and needs the agent's own manifest. ` +
392
- `Add a package.json declaring @fastagent-sh/fastagent there (a --minimal init writes none), ` +
328
+ `Add a package.json declaring @fastagent-sh/fastagent there (\`init\` scaffolds one), ` +
393
329
  `or run \`fastagent init\` in the workspace for a fresh agent`);
394
330
  }
395
331
  throw e;
@@ -1,45 +1,23 @@
1
- /** The agent directory name for a raw `--agent-dir` value: the default when unset, and `./bot` read as
2
- * `bot` — `basename` already says that is what it means, so rejecting the spelling would be pedantry. */
1
+ /** The agent directory name for a raw `--agent-dir` value: the default when unset, and `./bot` read as `bot`. */
3
2
  export declare function agentDirName(raw: string | undefined): string;
4
- /** Why `name` cannot be an agent directory name, or undefined when it can. It must stay ONE segment
5
- * inside the target: anything else (a separator, `..`, an absolute path) would land the agent where the
6
- * one-level lookup cannot see it — an agent nothing would ever serve.
3
+ /**
4
+ * Why `name` cannot be an agent directory name, or undefined when it can.
7
5
  *
8
- * Returns the CONSTRAINT, not a sentence: the CLI prefixes the flag it owns and reports it as the usage
9
- * error it is (exit 2), while {@link scaffoldAgent} prefixes the option name for a programmatic caller,
10
- * who never passed a flag and should not be told to fix one. */
6
+ * `"."` is refused along with every path: the definition always lands in a SUBDIRECTORY, and the directory around it
7
+ * is the workspace. The subdirectory also carries its own `package.json`, so a `fastagent.config.ts` never loads under
8
+ * the host repo's module settings.
9
+ */
11
10
  export declare function agentDirNameError(name: string): string | undefined;
12
11
  export interface ScaffoldOptions {
13
- /** Scaffold the markdown-only unit (no package.json, no tool, no install) instead of a complete agent. */
14
- minimal?: boolean;
15
- /** The agent directory's name inside `dir` — default {@link DEFAULT_AGENT_DIRNAME}, `"."` for `dir`
16
- * itself. ONE path segment: a separator or `..` would put the agent outside the directory the author
17
- * named, where the one-level lookup could never find it. */
12
+ /** The agent directory's name inside `dir` — default {@link DEFAULT_AGENT_DIRNAME}. */
18
13
  agentDir?: string;
19
14
  }
20
15
  export interface ScaffoldResult {
21
16
  dir: string;
22
- /** Whether a complete (code-tool) agent was scaffolded (false for --minimal). */
23
- complete: boolean;
24
17
  /** The agent dir relative to `dir`: the {@link ScaffoldOptions.agentDir} that was used. */
25
18
  agentDir: string;
26
19
  /** Files written by this run (relative to `dir`). */
27
20
  created: string[];
28
- /** Files that already existed and were KEPT untouched. Only reachable with `agentDir: "."` (a
29
- * subdirectory target is proven empty first): adopting a directory means its `.gitignore`/
30
- * `package.json` are the author's. The caller surfaces them — silently skipping a file the user
31
- * expected would be worse. */
32
- kept: string[];
33
21
  }
34
- /**
35
- * Scaffold a runnable agent into `<dir>/<agentDir>/` — or into `dir` itself when `agentDir` is `"."`
36
- * (both created if missing). Default is a complete agent (persona.md + the writing-great-skills skill +
37
- * a code tool + package.json); `--minimal` drops the code tool and package.json.
38
- *
39
- * A SUBDIRECTORY target must be empty (any content there is an unfinished agent or something unrelated,
40
- * and landing persona.md beside it would be a silent mix), while `"."` is a directory being adopted —
41
- * content is expected. So `"."` KEEPS every file that already exists (reported, never overwritten, never
42
- * verified) and refuses only on a config, which means the directory is already an agent. An existing
43
- * AGENTS.md is untouched either way: that is the project's context, adopted as-is.
44
- */
22
+ /** Scaffold a runnable agent into `<dir>/<agentDir>/` (both created if missing). */
45
23
  export declare function scaffoldAgent(dir: string, options?: ScaffoldOptions): Promise<ScaffoldResult>;