@fastagent-sh/fastagent 0.21.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (388) hide show
  1. package/README.md +9 -12
  2. package/dist/agent.d.ts +24 -37
  3. package/dist/agent.js +7 -15
  4. package/dist/atomic-write.d.ts +13 -17
  5. package/dist/atomic-write.js +18 -29
  6. package/dist/bind.d.ts +12 -22
  7. package/dist/bind.js +18 -27
  8. package/dist/channel.d.ts +11 -33
  9. package/dist/channels/agentcore-limits.d.ts +2 -13
  10. package/dist/channels/agentcore-limits.js +2 -13
  11. package/dist/channels/agentcore-protocol.d.ts +67 -58
  12. package/dist/channels/agentcore-protocol.js +23 -15
  13. package/dist/channels/agentcore-service.d.ts +19 -33
  14. package/dist/channels/agentcore-service.js +176 -59
  15. package/dist/channels/agentcore.d.ts +39 -33
  16. package/dist/channels/agentcore.js +90 -224
  17. package/dist/channels/body.d.ts +15 -2
  18. package/dist/channels/body.js +24 -2
  19. package/dist/channels/busy.d.ts +1 -21
  20. package/dist/channels/busy.js +2 -39
  21. package/dist/channels/control.d.ts +17 -62
  22. package/dist/channels/control.js +74 -182
  23. package/dist/channels/define-channel.d.ts +39 -0
  24. package/dist/channels/define-channel.js +9 -0
  25. package/dist/channels/discover.d.ts +10 -31
  26. package/dist/channels/discover.js +37 -31
  27. package/dist/channels/feishu/bootstrap-token.d.ts +1 -8
  28. package/dist/channels/feishu/bootstrap-token.js +7 -23
  29. package/dist/channels/feishu/card.d.ts +14 -28
  30. package/dist/channels/feishu/card.js +16 -30
  31. package/dist/channels/feishu/cloud.d.ts +1 -5
  32. package/dist/channels/feishu/context-buffer.d.ts +4 -23
  33. package/dist/channels/feishu/context-buffer.js +8 -31
  34. package/dist/channels/feishu/crypto.d.ts +10 -5
  35. package/dist/channels/feishu/crypto.js +12 -19
  36. package/dist/channels/feishu/feishu-api.d.ts +2 -1
  37. package/dist/channels/feishu/feishu-api.js +5 -4
  38. package/dist/channels/feishu/feishu.d.ts +4 -10
  39. package/dist/channels/feishu/feishu.js +113 -227
  40. package/dist/channels/feishu/invoke-turn.d.ts +5 -4
  41. package/dist/channels/feishu/invoke-turn.js +36 -54
  42. package/dist/channels/feishu/model.d.ts +4 -21
  43. package/dist/channels/feishu/model.js +1 -8
  44. package/dist/channels/feishu/normalize.d.ts +2 -10
  45. package/dist/channels/feishu/normalize.js +7 -42
  46. package/dist/channels/feishu/parse.d.ts +9 -29
  47. package/dist/channels/feishu/parse.js +8 -26
  48. package/dist/channels/feishu/preview.d.ts +12 -14
  49. package/dist/channels/feishu/preview.js +108 -207
  50. package/dist/channels/feishu/register-app.d.ts +8 -23
  51. package/dist/channels/feishu/register-app.js +7 -20
  52. package/dist/channels/feishu/register-webhook.d.ts +2 -9
  53. package/dist/channels/feishu/register-webhook.js +9 -33
  54. package/dist/channels/feishu/scaffold/channel.ts +27 -19
  55. package/dist/channels/feishu/scaffold/channel.websocket.ts +31 -0
  56. package/dist/channels/feishu/setup-mode.d.ts +11 -36
  57. package/dist/channels/feishu/setup-mode.js +7 -26
  58. package/dist/channels/feishu/ws-ingress.d.ts +1 -2
  59. package/dist/channels/feishu/ws-ingress.js +6 -8
  60. package/dist/channels/http.d.ts +15 -15
  61. package/dist/channels/http.js +19 -9
  62. package/dist/channels/kit/attachment-path.d.ts +4 -5
  63. package/dist/channels/kit/attachment-path.js +10 -34
  64. package/dist/channels/kit/context-buffer.d.ts +14 -21
  65. package/dist/channels/kit/context-buffer.js +25 -38
  66. package/dist/channels/kit/delivery.d.ts +35 -0
  67. package/dist/channels/kit/delivery.js +129 -0
  68. package/dist/channels/kit/event-stream.d.ts +5 -0
  69. package/dist/channels/kit/event-stream.js +29 -0
  70. package/dist/channels/kit/invoke-turn-kit.d.ts +53 -26
  71. package/dist/channels/kit/invoke-turn-kit.js +89 -32
  72. package/dist/channels/kit/preview-kit.d.ts +18 -72
  73. package/dist/channels/kit/preview-kit.js +22 -88
  74. package/dist/channels/kit/signature.d.ts +3 -9
  75. package/dist/channels/kit/signature.js +3 -9
  76. package/dist/channels/kit/state.d.ts +1 -2
  77. package/dist/channels/kit/state.js +5 -14
  78. package/dist/channels/kit/stop-command.d.ts +1 -2
  79. package/dist/channels/kit/stop-command.js +6 -9
  80. package/dist/channels/kit/tasks.d.ts +1 -1
  81. package/dist/channels/kit/tasks.js +8 -21
  82. package/dist/channels/kit/text.d.ts +2 -16
  83. package/dist/channels/kit/text.js +23 -31
  84. package/dist/channels/kit/thread-participants.d.ts +4 -17
  85. package/dist/channels/kit/thread-participants.js +8 -55
  86. package/dist/channels/kit/transport.d.ts +26 -0
  87. package/dist/channels/kit/transport.js +20 -0
  88. package/dist/channels/kit/turn-queue.d.ts +7 -11
  89. package/dist/channels/kit/turn-queue.js +21 -39
  90. package/dist/channels/kit/turn-runner.d.ts +32 -18
  91. package/dist/channels/kit/turn-runner.js +98 -53
  92. package/dist/channels/kit/turn-store.d.ts +32 -32
  93. package/dist/channels/kit/turn-store.js +45 -88
  94. package/dist/channels/lark/lark.d.ts +1 -5
  95. package/dist/channels/lark/onboard.d.ts +3 -12
  96. package/dist/channels/lark/onboard.js +3 -13
  97. package/dist/channels/lark/scaffold/channel.ts +27 -19
  98. package/dist/channels/lark/scaffold/channel.websocket.ts +31 -0
  99. package/dist/channels/registration.d.ts +9 -35
  100. package/dist/channels/registration.js +6 -37
  101. package/dist/channels/secret.js +2 -5
  102. package/dist/channels/serve.d.ts +47 -44
  103. package/dist/channels/serve.js +165 -101
  104. package/dist/channels/slack/config-api.d.ts +4 -27
  105. package/dist/channels/slack/config-api.js +5 -34
  106. package/dist/channels/slack/context-buffer.d.ts +2 -3
  107. package/dist/channels/slack/context-buffer.js +5 -5
  108. package/dist/channels/slack/invoke-turn.d.ts +3 -1
  109. package/dist/channels/slack/invoke-turn.js +25 -34
  110. package/dist/channels/slack/manifest.js +1 -3
  111. package/dist/channels/slack/model.d.ts +1 -2
  112. package/dist/channels/slack/model.js +1 -1
  113. package/dist/channels/slack/onboard.d.ts +4 -3
  114. package/dist/channels/slack/onboard.js +8 -11
  115. package/dist/channels/slack/onboarding-state.d.ts +3 -5
  116. package/dist/channels/slack/onboarding-state.js +9 -6
  117. package/dist/channels/slack/parse.d.ts +4 -13
  118. package/dist/channels/slack/parse.js +7 -28
  119. package/dist/channels/slack/preview.d.ts +12 -2
  120. package/dist/channels/slack/preview.js +292 -269
  121. package/dist/channels/slack/reaction.d.ts +11 -3
  122. package/dist/channels/slack/reaction.js +19 -8
  123. package/dist/channels/slack/register-webhook.js +4 -7
  124. package/dist/channels/slack/scaffold/channel.ts +20 -13
  125. package/dist/channels/slack/scaffold/slack-send.ts +2 -2
  126. package/dist/channels/slack/setup-server.d.ts +1 -5
  127. package/dist/channels/slack/setup-server.js +1 -5
  128. package/dist/channels/slack/shared-api.d.ts +1 -4
  129. package/dist/channels/slack/shared-api.js +2 -13
  130. package/dist/channels/slack/slack-api.d.ts +2 -1
  131. package/dist/channels/slack/slack-api.js +6 -4
  132. package/dist/channels/slack/slack.d.ts +11 -14
  133. package/dist/channels/slack/slack.js +121 -122
  134. package/dist/channels/sse.d.ts +1 -1
  135. package/dist/channels/sse.js +1 -1
  136. package/dist/channels/telegram/context-buffer.d.ts +7 -22
  137. package/dist/channels/telegram/context-buffer.js +6 -23
  138. package/dist/channels/telegram/invoke-turn.d.ts +7 -7
  139. package/dist/channels/telegram/invoke-turn.js +24 -43
  140. package/dist/channels/telegram/parse.d.ts +24 -51
  141. package/dist/channels/telegram/parse.js +32 -84
  142. package/dist/channels/telegram/preview.d.ts +10 -1
  143. package/dist/channels/telegram/preview.js +79 -100
  144. package/dist/channels/telegram/register-webhook.js +5 -23
  145. package/dist/channels/telegram/scaffold/channel.ts +25 -18
  146. package/dist/channels/telegram/scaffold/telegram-send.ts +6 -4
  147. package/dist/channels/telegram/telegram-api.d.ts +4 -3
  148. package/dist/channels/telegram/telegram-api.js +9 -7
  149. package/dist/channels/telegram/telegram.d.ts +4 -18
  150. package/dist/channels/telegram/telegram.js +38 -94
  151. package/dist/channels/telegram/turn-store.d.ts +11 -14
  152. package/dist/channels/telegram/turn-store.js +8 -6
  153. package/dist/channels/wait-health.d.ts +4 -6
  154. package/dist/channels/wait-health.js +10 -14
  155. package/dist/cli/add-feishu.d.ts +2 -12
  156. package/dist/cli/add-feishu.js +24 -71
  157. package/dist/cli/add-slack.d.ts +5 -3
  158. package/dist/cli/add-slack.js +53 -76
  159. package/dist/cli/auth-view.d.ts +2 -16
  160. package/dist/cli/auth-view.js +2 -16
  161. package/dist/cli/commands/add.js +25 -43
  162. package/dist/cli/commands/chat.d.ts +0 -1
  163. package/dist/cli/commands/chat.js +5 -6
  164. package/dist/cli/commands/deploy/agentcore.js +60 -65
  165. package/dist/cli/commands/deploy/docker.js +50 -23
  166. package/dist/cli/commands/deploy/fly.js +23 -61
  167. package/dist/cli/commands/deploy/railway.js +23 -18
  168. package/dist/cli/commands/deploy/shared.d.ts +38 -57
  169. package/dist/cli/commands/deploy/shared.js +40 -59
  170. package/dist/cli/commands/deploy.d.ts +10 -27
  171. package/dist/cli/commands/deploy.js +38 -60
  172. package/dist/cli/commands/destroy.d.ts +4 -0
  173. package/dist/cli/commands/destroy.js +56 -0
  174. package/dist/cli/commands/dev.d.ts +2 -1
  175. package/dist/cli/commands/dev.js +20 -23
  176. package/dist/cli/commands/info.d.ts +0 -2
  177. package/dist/cli/commands/info.js +83 -49
  178. package/dist/cli/commands/init.d.ts +1 -2
  179. package/dist/cli/commands/init.js +16 -55
  180. package/dist/cli/commands/invoke.d.ts +0 -1
  181. package/dist/cli/commands/invoke.js +5 -7
  182. package/dist/cli/commands/login.d.ts +2 -1
  183. package/dist/cli/commands/login.js +33 -38
  184. package/dist/cli/commands/logs.js +3 -4
  185. package/dist/cli/commands/models.js +1 -5
  186. package/dist/cli/commands/routine-run.d.ts +6 -0
  187. package/dist/cli/commands/routine-run.js +47 -0
  188. package/dist/cli/commands/routine.d.ts +25 -0
  189. package/dist/cli/commands/routine.js +122 -0
  190. package/dist/cli/commands/start.d.ts +31 -3
  191. package/dist/cli/commands/start.js +131 -81
  192. package/dist/cli/commands/tool.js +26 -12
  193. package/dist/cli/fail.d.ts +13 -23
  194. package/dist/cli/fail.js +20 -23
  195. package/dist/cli/invoke-stream.d.ts +1 -8
  196. package/dist/cli/kernel.d.ts +6 -38
  197. package/dist/cli/kernel.js +14 -77
  198. package/dist/cli/models-view.d.ts +1 -9
  199. package/dist/cli/models-view.js +11 -22
  200. package/dist/cli/program.d.ts +5 -6
  201. package/dist/cli/program.js +120 -157
  202. package/dist/cli/serve.d.ts +57 -41
  203. package/dist/cli/serve.js +94 -121
  204. package/dist/cli/shared.d.ts +15 -61
  205. package/dist/cli/shared.js +55 -181
  206. package/dist/cli.js +1 -8
  207. package/dist/collect.d.ts +6 -0
  208. package/dist/collect.js +6 -0
  209. package/dist/core.d.ts +4 -1
  210. package/dist/core.js +9 -7
  211. package/dist/declared-secrets.d.ts +55 -0
  212. package/dist/declared-secrets.js +88 -0
  213. package/dist/deploy/agentcore/aws-cli.d.ts +91 -0
  214. package/dist/deploy/agentcore/aws-cli.js +64 -0
  215. package/dist/deploy/agentcore/destroy.d.ts +51 -0
  216. package/dist/deploy/agentcore/destroy.js +273 -0
  217. package/dist/deploy/agentcore/forwarder.js +28 -79
  218. package/dist/deploy/agentcore/logs.d.ts +2 -18
  219. package/dist/deploy/agentcore/logs.js +27 -51
  220. package/dist/deploy/agentcore/plan.d.ts +85 -105
  221. package/dist/deploy/agentcore/plan.js +152 -314
  222. package/dist/deploy/agentcore/run.d.ts +28 -28
  223. package/dist/deploy/agentcore/run.js +227 -306
  224. package/dist/deploy/agentcore/zip.d.ts +2 -15
  225. package/dist/deploy/agentcore/zip.js +3 -17
  226. package/dist/deploy/build-context.d.ts +21 -0
  227. package/dist/deploy/build-context.js +137 -0
  228. package/dist/deploy/channel-ingress.d.ts +26 -39
  229. package/dist/deploy/channel-ingress.js +57 -28
  230. package/dist/deploy/container.d.ts +20 -34
  231. package/dist/deploy/container.js +49 -100
  232. package/dist/deploy/docker/plan.d.ts +12 -11
  233. package/dist/deploy/docker/plan.js +43 -24
  234. package/dist/deploy/docker/run.d.ts +22 -25
  235. package/dist/deploy/docker/run.js +39 -37
  236. package/dist/deploy/fly/plan.d.ts +15 -44
  237. package/dist/deploy/fly/plan.js +41 -73
  238. package/dist/deploy/fly/run.d.ts +16 -51
  239. package/dist/deploy/fly/run.js +42 -95
  240. package/dist/deploy/hosts.d.ts +24 -3
  241. package/dist/deploy/hosts.js +24 -3
  242. package/dist/deploy/preflight.d.ts +47 -41
  243. package/dist/deploy/preflight.js +282 -383
  244. package/dist/deploy/railway/plan.d.ts +16 -42
  245. package/dist/deploy/railway/plan.js +42 -60
  246. package/dist/deploy/railway/run.d.ts +30 -68
  247. package/dist/deploy/railway/run.js +86 -100
  248. package/dist/deploy/registration-gate.d.ts +6 -13
  249. package/dist/deploy/residency.d.ts +37 -0
  250. package/dist/deploy/residency.js +24 -0
  251. package/dist/deploy/runner.d.ts +10 -9
  252. package/dist/deploy/runner.js +7 -18
  253. package/dist/deploy/secrets.d.ts +40 -41
  254. package/dist/deploy/secrets.js +117 -103
  255. package/dist/deploy/workspace.d.ts +50 -0
  256. package/dist/deploy/workspace.js +197 -0
  257. package/dist/dev-supervisor.d.ts +1 -9
  258. package/dist/dev-supervisor.js +21 -64
  259. package/dist/effect-port.d.ts +50 -0
  260. package/dist/effect-port.js +77 -0
  261. package/dist/engines/pi/agent-session-factory.d.ts +50 -69
  262. package/dist/engines/pi/agent-session-factory.js +249 -146
  263. package/dist/engines/pi/auth.d.ts +15 -11
  264. package/dist/engines/pi/auth.js +96 -99
  265. package/dist/engines/pi/chat.d.ts +1 -5
  266. package/dist/engines/pi/chat.js +2 -14
  267. package/dist/engines/pi/config.d.ts +53 -51
  268. package/dist/engines/pi/config.js +83 -92
  269. package/dist/engines/pi/create.d.ts +60 -75
  270. package/dist/engines/pi/create.js +108 -141
  271. package/dist/engines/pi/definition.d.ts +14 -58
  272. package/dist/engines/pi/definition.js +21 -85
  273. package/dist/engines/pi/invoke-session.d.ts +5 -25
  274. package/dist/engines/pi/invoke-session.js +209 -244
  275. package/dist/engines/pi/login.d.ts +95 -35
  276. package/dist/engines/pi/login.js +259 -107
  277. package/dist/engines/pi/machine.d.ts +31 -0
  278. package/dist/engines/pi/machine.js +106 -0
  279. package/dist/engines/pi/models.d.ts +70 -89
  280. package/dist/engines/pi/models.js +188 -81
  281. package/dist/engines/pi/open.d.ts +65 -53
  282. package/dist/engines/pi/open.js +129 -69
  283. package/dist/engines/pi/report.d.ts +3 -12
  284. package/dist/engines/pi/report.js +2 -16
  285. package/dist/engines/pi/search-tools.d.ts +1 -9
  286. package/dist/engines/pi/search-tools.js +26 -57
  287. package/dist/engines/pi/service.d.ts +2 -16
  288. package/dist/engines/pi/service.js +2 -16
  289. package/dist/engines/pi/session-builder.d.ts +5 -9
  290. package/dist/engines/pi/session-builder.js +44 -82
  291. package/dist/engines/pi/session-control.js +286 -269
  292. package/dist/engines/pi/session-effects.d.ts +14 -0
  293. package/dist/engines/pi/session-effects.js +30 -0
  294. package/dist/engines/pi/session-inheritance.d.ts +9 -18
  295. package/dist/engines/pi/session-inheritance.js +103 -160
  296. package/dist/engines/pi/session-markers.d.ts +31 -30
  297. package/dist/engines/pi/session-markers.js +26 -25
  298. package/dist/engines/pi/session-settings.d.ts +6 -22
  299. package/dist/engines/pi/session-settings.js +10 -21
  300. package/dist/engines/pi/session-store.d.ts +26 -92
  301. package/dist/engines/pi/session-store.js +96 -310
  302. package/dist/engines/pi/tool-context.d.ts +17 -45
  303. package/dist/engines/pi/tool-context.js +7 -26
  304. package/dist/engines/pi/tool.d.ts +45 -36
  305. package/dist/engines/pi/tool.js +43 -57
  306. package/dist/engines/pi/turn-kit.d.ts +14 -52
  307. package/dist/engines/pi/turn-kit.js +18 -67
  308. package/dist/engines/pi/wake-tool.d.ts +2 -10
  309. package/dist/engines/pi/wake-tool.js +7 -27
  310. package/dist/env.d.ts +27 -28
  311. package/dist/env.js +45 -50
  312. package/dist/index.js +0 -2
  313. package/dist/lark.d.ts +1 -2
  314. package/dist/lark.js +1 -2
  315. package/dist/loader.d.ts +14 -55
  316. package/dist/loader.js +24 -43
  317. package/dist/log.d.ts +2 -12
  318. package/dist/log.js +3 -24
  319. package/dist/node.d.ts +2 -15
  320. package/dist/node.js +2 -18
  321. package/dist/observe.d.ts +2 -8
  322. package/dist/open-url.d.ts +1 -1
  323. package/dist/open-url.js +1 -1
  324. package/dist/paths.d.ts +70 -134
  325. package/dist/paths.js +165 -232
  326. package/dist/pi.d.ts +4 -6
  327. package/dist/pi.js +3 -3
  328. package/dist/proxy.d.ts +16 -6
  329. package/dist/proxy.js +52 -16
  330. package/dist/runtime.d.ts +18 -12
  331. package/dist/runtime.js +18 -11
  332. package/dist/scaffold/add-channel.d.ts +14 -31
  333. package/dist/scaffold/add-channel.js +48 -112
  334. package/dist/scaffold/init.d.ts +9 -31
  335. package/dist/scaffold/init.js +45 -138
  336. package/dist/scaffold/templates/env.example +6 -4
  337. package/dist/scaffold/templates/fastagent.config.ts +36 -0
  338. package/dist/scaffold/templates.d.ts +3 -3
  339. package/dist/scaffold/templates.js +5 -8
  340. package/dist/scaffold/vendor-skill.d.ts +2 -5
  341. package/dist/scaffold/vendor-skill.js +19 -29
  342. package/dist/schedule/cron.d.ts +9 -5
  343. package/dist/schedule/cron.js +9 -11
  344. package/dist/schedule/discover.d.ts +12 -12
  345. package/dist/schedule/discover.js +43 -34
  346. package/dist/schedule/routine.d.ts +43 -0
  347. package/dist/schedule/routine.js +32 -0
  348. package/dist/schedule/run.d.ts +55 -0
  349. package/dist/schedule/run.js +146 -0
  350. package/dist/schedule/scheduler.d.ts +44 -55
  351. package/dist/schedule/scheduler.js +291 -265
  352. package/dist/schedule/state.d.ts +105 -10
  353. package/dist/schedule/state.js +293 -19
  354. package/dist/schedule/wake-alarm.d.ts +10 -23
  355. package/dist/schedule/wake-alarm.js +91 -121
  356. package/dist/schedule/wakeups.d.ts +23 -39
  357. package/dist/schedule/wakeups.js +33 -75
  358. package/dist/secrets-gate.d.ts +32 -0
  359. package/dist/secrets-gate.js +33 -0
  360. package/dist/service.d.ts +137 -98
  361. package/dist/service.js +250 -252
  362. package/dist/session-remote.d.ts +33 -44
  363. package/dist/session-remote.js +203 -192
  364. package/dist/session.d.ts +153 -228
  365. package/dist/session.js +33 -55
  366. package/dist/tunnel.d.ts +8 -36
  367. package/dist/tunnel.js +24 -65
  368. package/dist/version.d.ts +2 -2
  369. package/dist/version.js +2 -2
  370. package/package.json +10 -16
  371. package/dist/channels/agentcore-state.d.ts +0 -77
  372. package/dist/channels/agentcore-state.js +0 -276
  373. package/dist/channels/github/github.d.ts +0 -31
  374. package/dist/channels/github/github.js +0 -89
  375. package/dist/channels/github/scaffold/channel.ts +0 -25
  376. package/dist/cli/commands/attach.d.ts +0 -108
  377. package/dist/cli/commands/attach.js +0 -637
  378. package/dist/cli/commands/fire.d.ts +0 -7
  379. package/dist/cli/commands/fire.js +0 -38
  380. package/dist/cli/commands/schedule.d.ts +0 -12
  381. package/dist/cli/commands/schedule.js +0 -87
  382. package/dist/github.d.ts +0 -3
  383. package/dist/github.js +0 -2
  384. package/dist/scaffold/templates/fastagent.config.mjs +0 -23
  385. package/dist/schedule/audit.d.ts +0 -18
  386. package/dist/schedule/audit.js +0 -57
  387. package/dist/schedule/schedule.d.ts +0 -35
  388. package/dist/schedule/schedule.js +0 -23
@@ -59,13 +59,7 @@ function errorSignal(error) {
59
59
  const causeCode = e.cause && typeof e.cause === "object" ? e.cause.code : undefined;
60
60
  return { status, code: e.code ?? causeCode };
61
61
  }
62
- /**
63
- * Pull the structured error `code` pi records on a failed message's diagnostics. `diagnostics`
64
- * accumulates across attempts (`appendAssistantMessageDiagnostic`), so the terminal cause is the LAST
65
- * code-bearing entry — `findLast`, not `find`: an earlier attempt's transient 503 must not classify a
66
- * terminal 400/auth failure as retryable. (Reverse scan rather than `findLast` — the tsconfig lib is
67
- * ES2022.)
68
- */
62
+ /** Pull the structured error `code` pi records on a failed message's diagnostics. */
69
63
  function messageSignal(message) {
70
64
  const diagnostics = message.diagnostics ?? [];
71
65
  for (let i = diagnostics.length - 1; i >= 0; i--) {
@@ -76,14 +70,14 @@ function messageSignal(message) {
76
70
  return {};
77
71
  }
78
72
  /**
79
- * Terminal mapping, decided by the resolved message's stopReason: pi's prompt() resolves a message
80
- * with stopReason "error"/"aborted" rather than throwing, so relying on catch alone would miss this
81
- * entire failure class (violating SPEC MUST 1).
73
+ * Terminal mapping, decided by the resolved message's stopReason: pi's `prompt()` RESOLVES a message with stopReason
74
+ * "error"/"aborted" rather than throwing, so relying on catch alone would miss that whole failure class and violate
75
+ * SPEC MUST 1.
82
76
  */
83
77
  export function toTerminal(message) {
84
78
  if (message.stopReason === "aborted") {
85
- // A deliberate stop (a control-plane or consumer abort), not an error — see {@link ABORTED_CODE}
86
- // for the consumer contract (design §6).
79
+ // A deliberate stop (a control-plane or consumer abort), not an error — see {@link ABORTED_CODE} for the consumer
80
+ // contract (design §6).
87
81
  const details = message.errorMessage ?? "run aborted";
88
82
  return { type: "failed", details, retryable: false, code: ABORTED_CODE };
89
83
  }
@@ -98,30 +92,28 @@ export function errorToTerminal(error) {
98
92
  return { type: "failed", details, retryable: classifyRetryable(details, errorSignal(error)) };
99
93
  }
100
94
  /**
101
- * Map prompt images to pi ImageContent, resizing each to model-friendly dimensions/size with pi's
102
- * Photon resizer (reused from pi-coding-agent, lazy-imported so the common no-image headless path never
103
- * loads the TUI module graph). A null resize (unresizable / Photon unavailable) keeps the original
104
- * bytes — the provider then applies its own limit.
95
+ * Map prompt images to pi ImageContent. `prompt()` normalizes its images itself, with the bound model's resize
96
+ * profile (`inputLimits.images.resize`) and format conversion, so a `"prompt"` delivery hands them over as-is.
97
+ * `steer`/`followUp` do not, so a `"queued"` delivery resizes here with pi's default profile: the model is not known
98
+ * yet when a queued message is prepared. pi's resizer is lazy-imported so the no-image path never loads its module
99
+ * graph.
105
100
  */
106
- export async function toPiPromptOptions(prompt) {
101
+ export async function toPiPromptOptions(prompt, delivery) {
107
102
  if (!prompt.images || prompt.images.length === 0)
108
103
  return undefined;
104
+ if (delivery === "prompt") {
105
+ return { images: prompt.images.map((img) => ({ type: "image", data: img.data, mimeType: img.mimeType })) };
106
+ }
109
107
  const { resizeImage } = await import("@earendil-works/pi-coding-agent");
110
108
  const images = await Promise.all(prompt.images.map(async (img) => {
111
- const resized = await resizeImage(Buffer.from(img.data, "base64"), img.mimeType, {
112
- maxWidth: 1568,
113
- maxHeight: 1568,
114
- maxBytes: 5 * 1024 * 1024,
115
- }).catch(() => null);
109
+ const resized = await resizeImage(Buffer.from(img.data, "base64"), img.mimeType).catch(() => null);
116
110
  return resized
117
111
  ? { type: "image", data: resized.data, mimeType: resized.mimeType }
118
112
  : { type: "image", data: img.data, mimeType: img.mimeType };
119
113
  }));
120
114
  return { images };
121
115
  }
122
- /** The SPEC projection of the rich stream. Events with no `AgentEvent` counterpart (progress,
123
- * message boundaries, run boundaries) project to null — the invoke terminal is produced from the
124
- * resolved message ({@link toTerminal}), not from `run_settled`. */
116
+ /** The SPEC projection of the rich stream. */
125
117
  export function projectAgentEvent(se) {
126
118
  switch (se.type) {
127
119
  case "message_delta": {
@@ -137,8 +129,7 @@ export function projectAgentEvent(se) {
137
129
  return { type: "tool_ended", id: d.id, isError: d.isError, content: d.content };
138
130
  }
139
131
  case "retry_scheduled": {
140
- // `operation` (compaction | branch_summary) stays session-plane vocabulary — a turn renderer
141
- // only needs "transient failure, retrying"; the engine detail lives in the control plane.
132
+ // `operation` (compaction | branch_summary) stays session-plane vocabulary.
142
133
  const d = se.data;
143
134
  return { type: "retrying", attempt: d.attempt, maxAttempts: d.maxAttempts, delayMs: d.delayMs, reason: d.error };
144
135
  }
@@ -146,43 +137,3 @@ export function projectAgentEvent(se) {
146
137
  return null;
147
138
  }
148
139
  }
149
- // ── EventQueue: push→pull plumbing for a two-port engine ────────────────────
150
- //
151
- // Single-consumer async queue; single-threaded JS means no await interleaves between push and
152
- // drain, so no locking. Engines that are natively async-iterable would not need it.
153
- export class EventQueue {
154
- buffer = [];
155
- wake;
156
- push(item) {
157
- this.buffer.push(item);
158
- const wake = this.wake;
159
- this.wake = undefined;
160
- wake?.();
161
- }
162
- /**
163
- * Yield pushed events in order until `done` settles AND the buffer is drained. The terminal is
164
- * produced separately (toTerminal); rejections of `done` are swallowed here (the caller awaits
165
- * `run` itself) to avoid unhandled rejections.
166
- */
167
- async *drainUntil(done) {
168
- let settled = false;
169
- const onSettle = () => {
170
- settled = true;
171
- const wake = this.wake;
172
- this.wake = undefined;
173
- wake?.();
174
- };
175
- const finished = done.then(onSettle, onSettle);
176
- while (true) {
177
- while (this.buffer.length > 0) {
178
- yield this.buffer.shift();
179
- }
180
- if (settled)
181
- break;
182
- await new Promise((resolve) => {
183
- this.wake = resolve;
184
- });
185
- }
186
- await finished;
187
- }
188
- }
@@ -1,17 +1,9 @@
1
1
  import { type MountedTool } from "./tool.ts";
2
2
  /**
3
- * Parse a delay to milliseconds: a number is SECONDS; a string MUST carry a unit — `"<n><s|m|h|d>"`
4
- * ("30m", "2h", "1d"). Undefined for anything else, INCLUDING a bare numeric string like "120": one
5
- * encoding, one scale. Deliberate — an LLM freely emits a number OR a numeral-string, so letting a
6
- * unitless string alias to a different unit than a number (120s vs 120min, 60x apart) is a silent
7
- * footgun the guardrail (min 60s) would mask. A rejected value comes back to the model to fix.
3
+ * Parse a delay to milliseconds: a number is SECONDS; a string MUST carry a unit — `"<n><s|m|h|d>"` ("30m", "2h",
4
+ * "1d").
8
5
  */
9
6
  export declare function parseDelayMs(input: string | number): number | undefined;
10
- /**
11
- * Append the built-in `wake` tool to `tools` — but only when `enabled` (the serving path, where the
12
- * scheduler poller honors a wake-up) and only when the workspace hasn't defined its own `wake` (that
13
- * wins, like any tool collision). The single place the mount decision + collision rule run.
14
- */
15
7
  export declare function withWakeTool(tools: MountedTool[], stateRoot: string, enabled: boolean): MountedTool[];
16
8
  /** Build the `wake` tool bound to `stateRoot` (where wake-ups persist). */
17
9
  export declare function makeWakeTool(stateRoot: string, now?: () => Date): MountedTool;
@@ -1,25 +1,10 @@
1
- /**
2
- * The built-in `wake` tool: the agent's self-scheduling surface. Calling it records a one-shot wake-up
3
- * (wakeups.ts); the scheduler fires it back into the SAME session, so the agent resumes THIS
4
- * conversation after a delay ("check the deploy in 10 minutes"). The session comes from the turn
5
- * context (ToolContext.sessionManager, set around the turn); the state root is closed over at build
6
- * time (where it is known — the workspace opener), never read from the turn.
7
- *
8
- * Mounted by the opener ONLY when `config.selfSchedule` is on AND on the serving path (`dev`/`start`, where
9
- * the scheduler poller honors a wake-up) — never on the one-shot `invoke`/`fire` (they exit and never poll).
10
- * To opt OUT: leave `selfSchedule` off (the default), or override the built-in by defining your own
11
- * `tools/wake.ts` (a same-name collision wins — see {@link withWakeTool}). There is no name-based tool
12
- * exclusion config today.
13
- */
1
+ /** The built-in `wake` tool: the agent's self-scheduling surface. */
14
2
  import { z } from "zod";
15
3
  import { addWakeup, removeWakeup } from "../../schedule/wakeups.js";
16
4
  import { defineTool } from "./tool.js";
17
5
  /**
18
- * Parse a delay to milliseconds: a number is SECONDS; a string MUST carry a unit — `"<n><s|m|h|d>"`
19
- * ("30m", "2h", "1d"). Undefined for anything else, INCLUDING a bare numeric string like "120": one
20
- * encoding, one scale. Deliberate — an LLM freely emits a number OR a numeral-string, so letting a
21
- * unitless string alias to a different unit than a number (120s vs 120min, 60x apart) is a silent
22
- * footgun the guardrail (min 60s) would mask. A rejected value comes back to the model to fix.
6
+ * Parse a delay to milliseconds: a number is SECONDS; a string MUST carry a unit — `"<n><s|m|h|d>"` ("30m", "2h",
7
+ * "1d").
23
8
  */
24
9
  export function parseDelayMs(input) {
25
10
  if (typeof input === "number")
@@ -30,17 +15,10 @@ export function parseDelayMs(input) {
30
15
  const mult = { s: 1_000, m: 60_000, h: 3_600_000, d: 86_400_000 }[m[2].toLowerCase()];
31
16
  return Number(m[1]) * mult;
32
17
  }
33
- /**
34
- * Append the built-in `wake` tool to `tools` — but only when `enabled` (the serving path, where the
35
- * scheduler poller honors a wake-up) and only when the workspace hasn't defined its own `wake` (that
36
- * wins, like any tool collision). The single place the mount decision + collision rule run.
37
- */
38
18
  export function withWakeTool(tools, stateRoot, enabled) {
39
19
  if (!enabled)
40
20
  return tools;
41
21
  // wake/unwake are a PAIR over one store: if the workspace defines EITHER name, mount NEITHER built-in.
42
- // Mixing halves would mislead — an author's wake doesn't write our wakeups store, so our unwake could
43
- // never cancel what it returns ("not yours" forever); the author owns the whole concept or none of it.
44
22
  if (tools.some((t) => t.name === "wake" || t.name === "unwake"))
45
23
  return tools;
46
24
  return [...tools, makeWakeTool(stateRoot), makeUnwakeTool(stateRoot)];
@@ -92,8 +70,10 @@ export function makeWakeTool(stateRoot, now = () => new Date()) {
92
70
  },
93
71
  });
94
72
  }
95
- /** Build the `unwake` tool: cancel one of THIS conversation's pending wake-ups by id (a wake/recurring
96
- * that is no longer needed). Session-scoped — a conversation can never cancel another's. */
73
+ /**
74
+ * Build the `unwake` tool: cancel one of THIS conversation's pending wake-ups by id (a wake/recurring that is no
75
+ * longer needed).
76
+ */
97
77
  function makeUnwakeTool(stateRoot) {
98
78
  return defineTool({
99
79
  name: "unwake",
package/dist/env.d.ts CHANGED
@@ -1,36 +1,35 @@
1
1
  /**
2
- * Load a `.env` file into `process.env`, matching Node's `--env-file` / `process.loadEnvFile` precedence
3
- * on BOTH axes (verified against Node): a real env var wins over the file (an already-set key is kept),
4
- * and within the file a repeated key takes the LAST occurrence. Portable across Node and Bun: Bun has no
5
- * `process.loadEnvFile`, so we parse the file ourselves rather than depend on a Node-only entry point.
6
- *
7
- * Two phases keep the two rules distinct: parse into a map (later line overrides earlier = last-wins),
8
- * then apply only the keys `process.env` doesn't already have (env-wins). Minimal parser — `KEY=VALUE`
9
- * per line, `#` comments and blank lines skipped, surrounding matched single/double quotes stripped:
10
- * enough for the flat secret files fastagent reads (tokens, keys), not a full dotenv dialect (no
11
- * multiline, no `export`, no interpolation). A missing file throws ENOENT for the caller to treat as
12
- * "no .env"; any other read error propagates.
2
+ * Write parsed values into `process.env`, matching Node's `--env-file` / `process.loadEnvFile` precedence on BOTH
3
+ * axes (verified against Node in test/env.test.ts): a real env var wins over the file, and within the file the last
4
+ * occurrence wins ({@link parseEnvContent}).
13
5
  */
14
- export declare function loadEnvFile(file: string): void;
15
- /** Parse .env content into key → value (the dialect above; last occurrence of a key wins). THE parser —
16
- * anything else reading/deciding on .env content (e.g. `add`'s secret pre-fill) must use this, never a
17
- * private re-implementation: two parsers of one dialect diverge silently. */
6
+ export declare function applyEnvValues(values: ReadonlyMap<string, string>): void;
7
+ /** Parse .env content into key → value (the dialect above; last occurrence of a key wins). */
18
8
  export declare function parseEnvContent(content: string): Map<string, string>;
19
- /** The agent's `.env` file: `<resolved secrets dir>/.env` — default `<agentDir>/.secrets/.env`,
20
- * moved together with auth.json by `FASTAGENT_SECRETS_DIR` ({@link resolveSecretsDir} in the neutral
21
- * paths.ts). THE path every reader/writer of the agent's .env must use, so "where do secrets
22
- * live" cannot diverge across commands. The file's OWN location resolves from the REAL environment:
23
- * commands locate + load `.env` first, so a `FASTAGENT_SECRETS_DIR` set INSIDE it still relocates
24
- * auth.json but cannot move the file it is read from. */
9
+ /**
10
+ * READ a value file without entering it: the values as data, never `process.env`. This is how code reasons about an
11
+ * environment OTHER than its own — `deploy` resolves configuration in the environment being deployed, which this
12
+ * file declares, and merging it into the running process would put the two environments in one bag. A missing file
13
+ * is no values, which is normal.
14
+ */
15
+ export declare function loadEnvValues(file: string): Map<string, string>;
16
+ /** The agent's `.env` file: `<resolved secrets dir>/.env`. */
25
17
  export declare function dotEnvPath(agentDir: string, env?: NodeJS.ProcessEnv): string;
26
- /** The committable template: `<agentDir>/.secrets/.env.example` — deliberately NOT moved by
27
- * `FASTAGENT_SECRETS_DIR`: it is authored agent surface that travels with the directory (the
28
- * scaffolded `.secrets/.gitignore` un-ignores exactly it), while the real values follow the override. */
18
+ /** The committable template: `<agentDir>/.secrets/.env.example`. */
29
19
  export declare function envExamplePath(agentDir: string): string;
30
20
  /**
31
- * Load the agent's `.env` ({@link dotEnvPath}) into `process.env` ({@link loadEnvFile}), treating a
32
- * MISSING file as normal (no .env) — the agent-facing entry every command + the tunnel use. `agentDir`
33
- * is the AGENT DIR (resolvePlacement().agentDir). Only ENOENT is swallowed; any other read error (a
34
- * corrupt/unreadable file) propagates, so a real problem surfaces instead of silently skipping.
21
+ * ENTER an agent directory's runtime environment: its `.env` becomes `process.env`, and this process's outbound fetch
22
+ * follows whatever proxy that (or the ambient environment) declares.
23
+ *
24
+ * ONE function because the two steps are one fact in a fixed order — the proxy can be declared in the `.env`, so it
25
+ * has to be read first, and `installProxyFetch` reads the environment at construction. A command that did only half
26
+ * would send an authored tool, a channel's app-creation flow, or a skill download DIRECT on a machine that has no
27
+ * direct route. Whether a given request then uses the proxy is the dispatcher's decision, not the
28
+ * caller's — see {@link installProxyFetch} on loopback.
29
+ */
30
+ export declare function enterAgentEnv(agentDir: string): void;
31
+ /**
32
+ * Load the agent's `.env` ({@link dotEnvPath}) into `process.env` ({@link applyEnvValues}), treating a MISSING file as
33
+ * normal (no .env). Commands want {@link enterAgentEnv}, which is this plus the proxy that `.env` may declare.
35
34
  */
36
35
  export declare function loadDotEnv(agentDir: string): void;
package/dist/env.js CHANGED
@@ -1,30 +1,19 @@
1
1
  import { readFileSync, existsSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { log } from "./log.js";
4
+ import { installProxyFetch } from "./proxy.js";
4
5
  import { SECRETS_DIRNAME, resolveSecretsDir } from "./paths.js";
5
6
  /**
6
- * Load a `.env` file into `process.env`, matching Node's `--env-file` / `process.loadEnvFile` precedence
7
- * on BOTH axes (verified against Node): a real env var wins over the file (an already-set key is kept),
8
- * and within the file a repeated key takes the LAST occurrence. Portable across Node and Bun: Bun has no
9
- * `process.loadEnvFile`, so we parse the file ourselves rather than depend on a Node-only entry point.
10
- *
11
- * Two phases keep the two rules distinct: parse into a map (later line overrides earlier = last-wins),
12
- * then apply only the keys `process.env` doesn't already have (env-wins). Minimal parser — `KEY=VALUE`
13
- * per line, `#` comments and blank lines skipped, surrounding matched single/double quotes stripped:
14
- * enough for the flat secret files fastagent reads (tokens, keys), not a full dotenv dialect (no
15
- * multiline, no `export`, no interpolation). A missing file throws ENOENT for the caller to treat as
16
- * "no .env"; any other read error propagates.
7
+ * Write parsed values into `process.env`, matching Node's `--env-file` / `process.loadEnvFile` precedence on BOTH
8
+ * axes (verified against Node in test/env.test.ts): a real env var wins over the file, and within the file the last
9
+ * occurrence wins ({@link parseEnvContent}).
17
10
  */
18
- export function loadEnvFile(file) {
19
- const parsed = parseEnvContent(readFileSync(file, "utf8"));
20
- for (const [key, value] of parsed) {
11
+ export function applyEnvValues(values) {
12
+ for (const [key, value] of values)
21
13
  if (!(key in process.env))
22
- process.env[key] = value; // env-vs-file: a real env var wins
23
- }
14
+ process.env[key] = value;
24
15
  }
25
- /** Parse .env content into key → value (the dialect above; last occurrence of a key wins). THE parser —
26
- * anything else reading/deciding on .env content (e.g. `add`'s secret pre-fill) must use this, never a
27
- * private re-implementation: two parsers of one dialect diverge silently. */
16
+ /** Parse .env content into key → value (the dialect above; last occurrence of a key wins). */
28
17
  export function parseEnvContent(content) {
29
18
  const parsed = new Map();
30
19
  for (const raw of content.split("\n")) {
@@ -43,50 +32,56 @@ export function parseEnvContent(content) {
43
32
  }
44
33
  return parsed;
45
34
  }
46
- /** The agent's `.env` file: `<resolved secrets dir>/.env` — default `<agentDir>/.secrets/.env`,
47
- * moved together with auth.json by `FASTAGENT_SECRETS_DIR` ({@link resolveSecretsDir} in the neutral
48
- * paths.ts). THE path every reader/writer of the agent's .env must use, so "where do secrets
49
- * live" cannot diverge across commands. The file's OWN location resolves from the REAL environment:
50
- * commands locate + load `.env` first, so a `FASTAGENT_SECRETS_DIR` set INSIDE it still relocates
51
- * auth.json but cannot move the file it is read from. */
35
+ /**
36
+ * READ a value file without entering it: the values as data, never `process.env`. This is how code reasons about an
37
+ * environment OTHER than its own — `deploy` resolves configuration in the environment being deployed, which this
38
+ * file declares, and merging it into the running process would put the two environments in one bag. A missing file
39
+ * is no values, which is normal.
40
+ */
41
+ export function loadEnvValues(file) {
42
+ try {
43
+ return parseEnvContent(readFileSync(file, "utf8"));
44
+ }
45
+ catch (error) {
46
+ if (error.code === "ENOENT")
47
+ return new Map();
48
+ throw error;
49
+ }
50
+ }
51
+ /** The agent's `.env` file: `<resolved secrets dir>/.env`. */
52
52
  export function dotEnvPath(agentDir, env = process.env) {
53
53
  return join(resolveSecretsDir(agentDir, env), ".env");
54
54
  }
55
- /** The committable template: `<agentDir>/.secrets/.env.example` — deliberately NOT moved by
56
- * `FASTAGENT_SECRETS_DIR`: it is authored agent surface that travels with the directory (the
57
- * scaffolded `.secrets/.gitignore` un-ignores exactly it), while the real values follow the override. */
55
+ /** The committable template: `<agentDir>/.secrets/.env.example`. */
58
56
  export function envExamplePath(agentDir) {
59
57
  return join(agentDir, SECRETS_DIRNAME, ".env.example");
60
58
  }
61
59
  /**
62
- * Load the agent's `.env` ({@link dotEnvPath}) into `process.env` ({@link loadEnvFile}), treating a
63
- * MISSING file as normal (no .env) — the agent-facing entry every command + the tunnel use. `agentDir`
64
- * is the AGENT DIR (resolvePlacement().agentDir). Only ENOENT is swallowed; any other read error (a
65
- * corrupt/unreadable file) propagates, so a real problem surfaces instead of silently skipping.
60
+ * ENTER an agent directory's runtime environment: its `.env` becomes `process.env`, and this process's outbound fetch
61
+ * follows whatever proxy that (or the ambient environment) declares.
62
+ *
63
+ * ONE function because the two steps are one fact in a fixed order — the proxy can be declared in the `.env`, so it
64
+ * has to be read first, and `installProxyFetch` reads the environment at construction. A command that did only half
65
+ * would send an authored tool, a channel's app-creation flow, or a skill download DIRECT on a machine that has no
66
+ * direct route. Whether a given request then uses the proxy is the dispatcher's decision, not the
67
+ * caller's — see {@link installProxyFetch} on loopback.
68
+ */
69
+ export function enterAgentEnv(agentDir) {
70
+ loadDotEnv(agentDir);
71
+ installProxyFetch();
72
+ }
73
+ /**
74
+ * Load the agent's `.env` ({@link dotEnvPath}) into `process.env` ({@link applyEnvValues}), treating a MISSING file as
75
+ * normal (no .env). Commands want {@link enterAgentEnv}, which is this plus the proxy that `.env` may declare.
66
76
  */
67
77
  export function loadDotEnv(agentDir) {
68
78
  const path = dotEnvPath(agentDir);
69
- try {
70
- loadEnvFile(path);
71
- }
72
- catch (error) {
73
- if (error.code !== "ENOENT")
74
- throw error;
75
- }
76
- // A `.env` at the agent's root is the file habit puts there, and nothing reads it. Left silent, the
77
- // symptom is a setting that appears configured and is not.
78
- //
79
- // Scoped to keys FASTAGENT itself reads, because that is the only case we can be sure about: an agent
80
- // directory can be the author's repository too (the shape `--agent-dir .` exists for), where a root
81
- // `.env` is their APPLICATION's — and "move the values" would break it. Nothing here can tell those
82
- // apart, so the broader guess (warn whenever the agent has no env of its own) was a warning on every
83
- // boot, with destructive advice, aimed at the population that never had the problem. Channel
84
- // credentials are deliberately out of scope: a channel that cannot find its token reports that itself,
85
- // and it is the one that knows the name.
79
+ applyEnvValues(loadEnvValues(path)); // ONE definition of "a missing value file is normal" (loadEnvValues)
80
+ // A `.env` at the agent's root is the file habit puts there, and nothing reads it.
86
81
  const stray = join(agentDir, ".env");
87
82
  if (stray === path || !existsSync(stray))
88
83
  return;
89
- const misplaced = [...parseEnvContent(readFileSync(stray, "utf8")).keys()].filter((k) => k.startsWith("FASTAGENT_"));
84
+ const misplaced = [...parseEnvContent(readFileSync(stray, "utf8")).keys()];
90
85
  if (misplaced.length > 0) {
91
86
  log.warn(`[fastagent] ${stray} is NOT read — it sets ${misplaced.join(", ")}, and this agent's env lives at ` +
92
87
  `${path}; move those values there`);
package/dist/index.js CHANGED
@@ -1,6 +1,4 @@
1
1
  // Convenience entry: the engine-neutral core plus the pi reference implementation.
2
- // Contract/channel-only consumers should prefer `@fastagent-sh/fastagent/core`; pi-specific consumers
3
- // may use `@fastagent-sh/fastagent/pi`. The root remains the supported all-in-one surface.
4
2
  export * from "./core.js";
5
3
  export * from "./node.js";
6
4
  export * from "./session.js";
package/dist/lark.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- /** `@fastagent-sh/fastagent/lark` — the Lark-international compatibility surface over the canonical
2
- * Feishu engine. Feishu tenants use `@fastagent-sh/fastagent/feishu`. */
1
+ /** `@fastagent-sh/fastagent/lark` — the Lark-international compatibility surface over the canonical Feishu engine. */
3
2
  export { larkChannel, larkWebSocketChannel, defaultLarkRoute, larkEnvelope, type LarkChannelOptions, type LarkWebSocketChannelOptions, type LarkMessageEvent, type LarkMessage, type LarkRoute, type LarkFailure, } from "./channels/lark/lark.ts";
4
3
  export { larkTransport, type FeishuTransport as LarkTransport } from "./channels/feishu/shared-api.ts";
package/dist/lark.js CHANGED
@@ -1,4 +1,3 @@
1
- /** `@fastagent-sh/fastagent/lark` — the Lark-international compatibility surface over the canonical
2
- * Feishu engine. Feishu tenants use `@fastagent-sh/fastagent/feishu`. */
1
+ /** `@fastagent-sh/fastagent/lark` — the Lark-international compatibility surface over the canonical Feishu engine. */
3
2
  export { larkChannel, larkWebSocketChannel, defaultLarkRoute, larkEnvelope, } from "./channels/lark/lark.js";
4
3
  export { larkTransport } from "./channels/feishu/shared-api.js";
package/dist/loader.d.ts CHANGED
@@ -1,48 +1,3 @@
1
- /** One module file a directory declares. `name` is what the domain knows it by; nothing is imported
2
- * to produce this. */
3
- export interface InventoryEntry {
4
- /** Basename without extension — the authoritative name for tools/channels/schedules. */
5
- name: string;
6
- /** "tools/foo.ts"-style label for errors and collisions. */
7
- label: string;
8
- file: string;
9
- }
10
- /**
11
- * WHAT A CODE-INPUT DIRECTORY DECLARES — the single answer to "which files here are modules",
12
- * without importing any of them.
13
- *
14
- * It exists because four consumers need that answer and only one of them may import: the loader
15
- * below, `fastagent info`'s channel listing, `--tunnel`'s webhook registration, and the deploy
16
- * pre-flight's schedule probe. When they each read the directory themselves they disagreed — on
17
- * what counts as a module file, on how to strip the extension, on which errno means "no such
18
- * directory" — and a fix had to be applied in four places, which is why the fourth kept being
19
- * missed. There is one reading now; what to DO with a failure stays with the caller, because that
20
- * genuinely differs (the loader throws, the tunnel cannot).
21
- *
22
- * A missing directory is an empty inventory. Everything else throws, ENOTDIR included: `channels`
23
- * and `schedules` are named directories, so the path existing as a FILE is a mistake in the agent
24
- * rather than an agent without one (`service.test.ts` pins that for `schedules`). `paths.ts` folds
25
- * ENOTDIR into its empty scan for the opposite reason — it asks which children HAPPEN to be agent
26
- * dirs, where a file simply is not one. `not_found` is a non-Node runtime's ENOENT.
27
- *
28
- * SYMLINKS ARE SKIPPED, and that is a boundary rather than an oversight: `assertInsideAgentDir`
29
- * guards the code-input DIRECTORY against escaping the definition, and nothing guards the entries
30
- * inside it, so following a link would import from anywhere on the box past the very check meant to
31
- * prevent it. Do not "fix" this by following them — report them, which this does.
32
- *
33
- * The skip is WARNED HERE, not handed back, because "this file is not loadable" holds for all four
34
- * consumers and only one of them imports — a listing that reports the name it cannot load reads as
35
- * "I never created it". What to do about a load FAILURE does differ per caller, so that travels as
36
- * data. One warning per READ, so a command that both lists and loads the same directory (`deploy
37
- * agentcore` does) says it twice — both readings are true, and remembering what was already said
38
- * would mean a restart stops mentioning a skip that is still there.
39
- *
40
- * A skip is therefore a warning ONLY, unlike a {@link ModuleLoadFailure}: it is absent from `info
41
- * --json` and `deploy` does not gate on it. That is the deliberate cost of one report for four
42
- * consumers, three of which list names and have nowhere to put data. To give a machine consumer the
43
- * skips, return them beside the entries — do not reconstruct them from stderr.
44
- */
45
- export declare function moduleInventory(subDir: string): Promise<InventoryEntry[]>;
46
1
  export interface DiscoveredModule {
47
2
  /** Basename without extension — the authoritative name for tools/channels. */
48
3
  name: string;
@@ -53,9 +8,7 @@ export interface DiscoveredModule {
53
8
  default?: unknown;
54
9
  };
55
10
  }
56
- /** An agent module that failed to load, surfaced as data so its caller can report the exact file.
57
- * `loadModuleDir` fills it for import failures; domain loaders add validation failures. The caller owns
58
- * policy: tools/schedules may skip one bad file, while serving treats a broken declared channel as fatal. */
11
+ /** An agent module that failed to load, surfaced as data so its caller can report the exact file. */
59
12
  export interface ModuleLoadFailure {
60
13
  /** "tools/foo.ts"-style label. */
61
14
  label: string;
@@ -63,20 +16,26 @@ export interface ModuleLoadFailure {
63
16
  /** The failure message (an import error carries {@link moduleLoadHint}). */
64
17
  message: string;
65
18
  }
66
- /** A module the loader skipped, said once, the same way for tools, channels and schedules. */
19
+ /** A module the loader skipped, said once, the same way for tools, channels and routines. */
67
20
  export declare function reportModuleLoadFailures(failures: readonly ModuleLoadFailure[]): void;
68
21
  /**
69
- * Import every module the directory declares ({@link moduleInventory}). A file that fails to IMPORT
70
- * is collected into `failures` (with {@link moduleLoadHint}) rather than thrown, so the caller can
71
- * report every bad file and apply domain policy; `loadTools`/`loadChannels` add validation failures
72
- * the same way. Entries the inventory SKIPPED are already reported by it, for every consumer.
22
+ * THE REFUSAL every path that is about to RUN the agent shares: an enabled file under `tools/`, `channels/` or
23
+ * `routines/` is a declaration, so one that cannot load means the agent is missing something its author said it
24
+ * has. Starting anyway announces a ready service over an absent capability — a schedule that never fires, a tool the
25
+ * model simply never gets — with nothing but one warning to find it by. Every failure is reported before this
26
+ * throws, so a boot fixes all of them at once rather than one per restart.
27
+ *
28
+ * Inspecting a definition (`info`, `fastagent tool`) is NOT this path: it must survive a broken file in order to
29
+ * report it.
73
30
  */
31
+ export declare function refuseBrokenDeclarations(failures: readonly ModuleLoadFailure[]): void;
32
+ /** Import every module the directory declares ({@link moduleInventory}). */
74
33
  export declare function loadModuleDir(subDir: string): Promise<{
75
34
  modules: DiscoveredModule[];
76
35
  failures: ModuleLoadFailure[];
77
36
  }>;
78
37
  /**
79
- * A hint for the two common dynamic-import failures — an uninstalled dependency or a non-ESM
80
- * package — and empty otherwise, so an unrelated error is reported on its own.
38
+ * A hint for the two common dynamic-import failures — an uninstalled dependency or a non-ESM package — and empty
39
+ * otherwise, so an unrelated error is reported on its own.
81
40
  */
82
41
  export declare function moduleLoadHint(error: NodeJS.ErrnoException): string;
package/dist/loader.js CHANGED
@@ -20,41 +20,10 @@ function moduleName(fileName) {
20
20
  return basename(fileName, extname(fileName));
21
21
  }
22
22
  /**
23
- * WHAT A CODE-INPUT DIRECTORY DECLARES — the single answer to "which files here are modules",
24
- * without importing any of them.
25
- *
26
- * It exists because four consumers need that answer and only one of them may import: the loader
27
- * below, `fastagent info`'s channel listing, `--tunnel`'s webhook registration, and the deploy
28
- * pre-flight's schedule probe. When they each read the directory themselves they disagreed — on
29
- * what counts as a module file, on how to strip the extension, on which errno means "no such
30
- * directory" — and a fix had to be applied in four places, which is why the fourth kept being
31
- * missed. There is one reading now; what to DO with a failure stays with the caller, because that
32
- * genuinely differs (the loader throws, the tunnel cannot).
33
- *
34
- * A missing directory is an empty inventory. Everything else throws, ENOTDIR included: `channels`
35
- * and `schedules` are named directories, so the path existing as a FILE is a mistake in the agent
36
- * rather than an agent without one (`service.test.ts` pins that for `schedules`). `paths.ts` folds
37
- * ENOTDIR into its empty scan for the opposite reason — it asks which children HAPPEN to be agent
38
- * dirs, where a file simply is not one. `not_found` is a non-Node runtime's ENOENT.
39
- *
40
- * SYMLINKS ARE SKIPPED, and that is a boundary rather than an oversight: `assertInsideAgentDir`
41
- * guards the code-input DIRECTORY against escaping the definition, and nothing guards the entries
42
- * inside it, so following a link would import from anywhere on the box past the very check meant to
43
- * prevent it. Do not "fix" this by following them — report them, which this does.
44
- *
45
- * The skip is WARNED HERE, not handed back, because "this file is not loadable" holds for all four
46
- * consumers and only one of them imports — a listing that reports the name it cannot load reads as
47
- * "I never created it". What to do about a load FAILURE does differ per caller, so that travels as
48
- * data. One warning per READ, so a command that both lists and loads the same directory (`deploy
49
- * agentcore` does) says it twice — both readings are true, and remembering what was already said
50
- * would mean a restart stops mentioning a skip that is still there.
51
- *
52
- * A skip is therefore a warning ONLY, unlike a {@link ModuleLoadFailure}: it is absent from `info
53
- * --json` and `deploy` does not gate on it. That is the deliberate cost of one report for four
54
- * consumers, three of which list names and have nowhere to put data. To give a machine consumer the
55
- * skips, return them beside the entries — do not reconstruct them from stderr.
23
+ * WHAT A CODE-INPUT DIRECTORY DECLARES — the single answer to "which files here are modules", without importing any of
24
+ * them. Internal: the loading functions below are its only consumers.
56
25
  */
57
- export async function moduleInventory(subDir) {
26
+ async function moduleInventory(subDir) {
58
27
  let dirents;
59
28
  try {
60
29
  dirents = await readdir(subDir, { withFileTypes: true });
@@ -68,8 +37,6 @@ export async function moduleInventory(subDir) {
68
37
  const sub = basename(subDir);
69
38
  const entries = [];
70
39
  // Sorted by the NAME a consumer sees, so none of them re-sorts and none can disagree about order.
71
- // Filename breaks a tie: `foo.js` and `foo.ts` both read as `foo`, and the domain loaders document
72
- // that the FIRST wins — deciding that here keeps it from depending on readdir's order.
73
40
  const byName = (a, b) => moduleName(a.name).localeCompare(moduleName(b.name)) || a.name.localeCompare(b.name);
74
41
  for (const dirent of dirents.sort(byName)) {
75
42
  if (!isModuleFile(dirent.name))
@@ -90,17 +57,31 @@ export async function moduleInventory(subDir) {
90
57
  }
91
58
  return entries;
92
59
  }
93
- /** A module the loader skipped, said once, the same way for tools, channels and schedules. */
60
+ /** A module the loader skipped, said once, the same way for tools, channels and routines. */
94
61
  export function reportModuleLoadFailures(failures) {
95
62
  for (const f of failures)
96
63
  log.warn(`[fastagent] ${f.label} failed to load, skipping it — ${f.message}`);
97
64
  }
98
65
  /**
99
- * Import every module the directory declares ({@link moduleInventory}). A file that fails to IMPORT
100
- * is collected into `failures` (with {@link moduleLoadHint}) rather than thrown, so the caller can
101
- * report every bad file and apply domain policy; `loadTools`/`loadChannels` add validation failures
102
- * the same way. Entries the inventory SKIPPED are already reported by it, for every consumer.
66
+ * THE REFUSAL every path that is about to RUN the agent shares: an enabled file under `tools/`, `channels/` or
67
+ * `routines/` is a declaration, so one that cannot load means the agent is missing something its author said it
68
+ * has. Starting anyway announces a ready service over an absent capability — a schedule that never fires, a tool the
69
+ * model simply never gets — with nothing but one warning to find it by. Every failure is reported before this
70
+ * throws, so a boot fixes all of them at once rather than one per restart.
71
+ *
72
+ * Inspecting a definition (`info`, `fastagent tool`) is NOT this path: it must survive a broken file in order to
73
+ * report it.
103
74
  */
75
+ export function refuseBrokenDeclarations(failures) {
76
+ if (failures.length === 0)
77
+ return;
78
+ reportModuleLoadFailures(failures);
79
+ // The reasons are repeated from the warnings above because this message is all an embedder catching the rejection
80
+ // has, and all a deployment running at FASTAGENT_LOG_LEVEL=error sees.
81
+ throw new Error(`failed to load: ${failures.map((f) => `${f.label} (${f.message})`).join("; ")} — fix it, or rename an ` +
82
+ `intentionally disabled file to *.disabled`);
83
+ }
84
+ /** Import every module the directory declares ({@link moduleInventory}). */
104
85
  export async function loadModuleDir(subDir) {
105
86
  const entries = await moduleInventory(subDir);
106
87
  const modules = [];
@@ -121,8 +102,8 @@ export async function loadModuleDir(subDir) {
121
102
  return { modules, failures };
122
103
  }
123
104
  /**
124
- * A hint for the two common dynamic-import failures — an uninstalled dependency or a non-ESM
125
- * package — and empty otherwise, so an unrelated error is reported on its own.
105
+ * A hint for the two common dynamic-import failures — an uninstalled dependency or a non-ESM package — and empty
106
+ * otherwise, so an unrelated error is reported on its own.
126
107
  */
127
108
  export function moduleLoadHint(error) {
128
109
  if (error.code === "ERR_MODULE_NOT_FOUND" || /Cannot find (package|module)/.test(error.message)) {