@fastagent-sh/fastagent 0.24.4 → 0.26.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 (487) hide show
  1. package/README.md +54 -82
  2. package/dist/agent-api.d.ts +460 -0
  3. package/dist/agent-api.js +209 -0
  4. package/dist/answer-text.d.ts +8 -0
  5. package/dist/answer-text.js +18 -0
  6. package/dist/atomic-write.d.ts +2 -2
  7. package/dist/atomic-write.js +2 -2
  8. package/dist/authoring.d.ts +81 -0
  9. package/dist/authoring.js +245 -0
  10. package/dist/channel.d.ts +29 -15
  11. package/dist/channel.js +4 -1
  12. package/dist/channels/agentcore-protocol.d.ts +36 -50
  13. package/dist/channels/agentcore-protocol.js +3 -3
  14. package/dist/channels/agentcore-service.d.ts +8 -18
  15. package/dist/channels/agentcore-service.js +108 -127
  16. package/dist/channels/agentcore.d.ts +8 -24
  17. package/dist/channels/agentcore.js +32 -65
  18. package/dist/channels/body.d.ts +0 -16
  19. package/dist/channels/body.js +0 -25
  20. package/dist/channels/busy.js +6 -1
  21. package/dist/channels/context.d.ts +4 -0
  22. package/dist/channels/context.js +35 -0
  23. package/dist/channels/discover.d.ts +18 -20
  24. package/dist/channels/discover.js +41 -99
  25. package/dist/channels/feishu/feishu-api.d.ts +54 -4
  26. package/dist/channels/feishu/feishu-api.js +192 -149
  27. package/dist/channels/feishu/feishu.d.ts +3 -0
  28. package/dist/channels/feishu/feishu.js +117 -106
  29. package/dist/channels/feishu/history.d.ts +55 -0
  30. package/dist/channels/feishu/history.js +199 -0
  31. package/dist/channels/feishu/invoke-turn.d.ts +13 -11
  32. package/dist/channels/feishu/invoke-turn.js +9 -10
  33. package/dist/channels/feishu/model.d.ts +2 -1
  34. package/dist/channels/feishu/normalize.js +100 -0
  35. package/dist/channels/feishu/preview.d.ts +2 -2
  36. package/dist/channels/feishu/register-webhook.d.ts +1 -0
  37. package/dist/channels/feishu/register-webhook.js +3 -2
  38. package/dist/channels/feishu/scaffold/channel.ts +65 -46
  39. package/dist/channels/feishu/scaffold/feishu-send.ts +38 -33
  40. package/dist/channels/feishu/scaffold/feishu-threads.ts +25 -0
  41. package/dist/channels/feishu/setup-mode.d.ts +45 -25
  42. package/dist/channels/feishu/setup-mode.js +74 -23
  43. package/dist/channels/feishu/shared-api.d.ts +24 -6
  44. package/dist/channels/feishu/shared-api.js +30 -24
  45. package/dist/channels/kit/context-buffer.d.ts +11 -2
  46. package/dist/channels/kit/context-buffer.js +7 -4
  47. package/dist/channels/kit/delivery.d.ts +19 -3
  48. package/dist/channels/kit/delivery.js +74 -15
  49. package/dist/channels/kit/event-stream.js +2 -1
  50. package/dist/channels/kit/invoke-turn-kit.d.ts +41 -41
  51. package/dist/channels/kit/invoke-turn-kit.js +62 -71
  52. package/dist/channels/kit/place-history.d.ts +107 -0
  53. package/dist/channels/kit/place-history.js +182 -0
  54. package/dist/channels/kit/preview-kit.d.ts +9 -5
  55. package/dist/channels/kit/preview-kit.js +36 -15
  56. package/dist/channels/kit/room-threads.d.ts +46 -0
  57. package/dist/channels/kit/room-threads.js +81 -0
  58. package/dist/channels/kit/seen.js +1 -1
  59. package/dist/channels/kit/stop-command.d.ts +6 -3
  60. package/dist/channels/kit/stop-command.js +20 -18
  61. package/dist/channels/kit/thread-fork.d.ts +17 -0
  62. package/dist/channels/kit/thread-fork.js +43 -0
  63. package/dist/channels/kit/thread-participants.js +1 -1
  64. package/dist/channels/kit/transport.d.ts +5 -0
  65. package/dist/channels/kit/transport.js +5 -0
  66. package/dist/channels/kit/turn-runner.d.ts +10 -9
  67. package/dist/channels/kit/turn-runner.js +17 -19
  68. package/dist/channels/kit/turn-store.d.ts +5 -4
  69. package/dist/channels/kit/turn-store.js +5 -5
  70. package/dist/channels/lark/lark.d.ts +3 -0
  71. package/dist/channels/lark/lark.js +5 -0
  72. package/dist/channels/lark/onboard.d.ts +5 -3
  73. package/dist/channels/lark/onboard.js +14 -14
  74. package/dist/channels/lark/scaffold/channel.ts +67 -44
  75. package/dist/channels/lark/scaffold/lark-send.ts +38 -33
  76. package/dist/channels/lark/scaffold/lark-threads.ts +25 -0
  77. package/dist/channels/respond.d.ts +0 -4
  78. package/dist/channels/respond.js +1 -1
  79. package/dist/channels/router.d.ts +18 -0
  80. package/dist/channels/router.js +68 -0
  81. package/dist/channels/serve.d.ts +5 -70
  82. package/dist/channels/serve.js +105 -287
  83. package/dist/channels/slack/history.d.ts +42 -0
  84. package/dist/channels/slack/history.js +282 -0
  85. package/dist/channels/slack/invoke-turn.d.ts +12 -5
  86. package/dist/channels/slack/invoke-turn.js +7 -5
  87. package/dist/channels/slack/manifest.d.ts +1 -4
  88. package/dist/channels/slack/manifest.js +22 -10
  89. package/dist/channels/slack/model.d.ts +2 -1
  90. package/dist/channels/slack/onboard.d.ts +0 -2
  91. package/dist/channels/slack/onboard.js +2 -4
  92. package/dist/channels/slack/onboarding-state.d.ts +0 -2
  93. package/dist/channels/slack/onboarding-state.js +0 -1
  94. package/dist/channels/slack/parse.d.ts +0 -5
  95. package/dist/channels/slack/parse.js +1 -16
  96. package/dist/channels/slack/preview.d.ts +2 -3
  97. package/dist/channels/slack/preview.js +78 -52
  98. package/dist/channels/slack/register-webhook.d.ts +1 -0
  99. package/dist/channels/slack/register-webhook.js +6 -5
  100. package/dist/channels/slack/scaffold/channel.ts +23 -24
  101. package/dist/channels/slack/scaffold/slack-send.ts +50 -43
  102. package/dist/channels/slack/scaffold/slack-threads.ts +29 -0
  103. package/dist/channels/slack/setup-server.d.ts +19 -0
  104. package/dist/channels/slack/setup-server.js +113 -11
  105. package/dist/channels/slack/shared-api.d.ts +15 -4
  106. package/dist/channels/slack/shared-api.js +19 -14
  107. package/dist/channels/slack/slack-api.d.ts +61 -2
  108. package/dist/channels/slack/slack-api.js +28 -5
  109. package/dist/channels/slack/slack.js +97 -52
  110. package/dist/channels/slack/welcomed.js +1 -1
  111. package/dist/channels/telegram/context-buffer.d.ts +6 -1
  112. package/dist/channels/telegram/context-buffer.js +19 -0
  113. package/dist/channels/telegram/invoke-turn.d.ts +8 -4
  114. package/dist/channels/telegram/invoke-turn.js +7 -7
  115. package/dist/channels/telegram/parse.d.ts +7 -2
  116. package/dist/channels/telegram/parse.js +7 -0
  117. package/dist/channels/telegram/preview.d.ts +2 -2
  118. package/dist/channels/telegram/register-webhook.d.ts +1 -0
  119. package/dist/channels/telegram/register-webhook.js +3 -2
  120. package/dist/channels/telegram/scaffold/channel.ts +27 -28
  121. package/dist/channels/telegram/scaffold/telegram-send.ts +61 -108
  122. package/dist/channels/telegram/shared-api.d.ts +34 -0
  123. package/dist/channels/telegram/shared-api.js +79 -0
  124. package/dist/channels/telegram/telegram-api.d.ts +15 -5
  125. package/dist/channels/telegram/telegram-api.js +10 -6
  126. package/dist/channels/telegram/telegram.js +27 -21
  127. package/dist/channels/telegram/turn-store.js +1 -1
  128. package/dist/channels/{kit/turn-queue.d.ts → turn-queue.d.ts} +2 -2
  129. package/dist/channels/{kit/turn-queue.js → turn-queue.js} +10 -10
  130. package/dist/cli/add-feishu.d.ts +42 -12
  131. package/dist/cli/add-feishu.js +177 -124
  132. package/dist/cli/add-slack.d.ts +8 -3
  133. package/dist/cli/add-slack.js +116 -64
  134. package/dist/cli/auth-view.d.ts +10 -3
  135. package/dist/cli/auth-view.js +8 -6
  136. package/dist/cli/box-login.d.ts +18 -7
  137. package/dist/cli/box-login.js +46 -12
  138. package/dist/cli/commands/add.d.ts +15 -4
  139. package/dist/cli/commands/add.js +253 -143
  140. package/dist/cli/commands/auth.d.ts +6 -0
  141. package/dist/cli/commands/auth.js +50 -0
  142. package/dist/cli/commands/content.d.ts +6 -0
  143. package/dist/cli/commands/content.js +45 -0
  144. package/dist/cli/commands/deploy/agentcore.js +50 -77
  145. package/dist/cli/commands/deploy/docker.js +38 -39
  146. package/dist/cli/commands/deploy/fly.js +21 -18
  147. package/dist/cli/commands/deploy/host-login.d.ts +26 -0
  148. package/dist/cli/commands/deploy/host-login.js +90 -0
  149. package/dist/cli/commands/deploy/railway.js +21 -26
  150. package/dist/cli/commands/deploy/shared.d.ts +45 -17
  151. package/dist/cli/commands/deploy/shared.js +34 -15
  152. package/dist/cli/commands/deploy.d.ts +0 -4
  153. package/dist/cli/commands/deploy.js +237 -50
  154. package/dist/cli/commands/destroy.js +8 -6
  155. package/dist/cli/commands/dev.d.ts +0 -2
  156. package/dist/cli/commands/dev.js +20 -19
  157. package/dist/cli/commands/env.d.ts +12 -0
  158. package/dist/cli/commands/env.js +191 -0
  159. package/dist/cli/commands/info.js +95 -156
  160. package/dist/cli/commands/init.d.ts +2 -2
  161. package/dist/cli/commands/init.js +35 -33
  162. package/dist/cli/commands/invoke.d.ts +6 -0
  163. package/dist/cli/commands/invoke.js +132 -16
  164. package/dist/cli/commands/login.d.ts +31 -2
  165. package/dist/cli/commands/login.js +416 -65
  166. package/dist/cli/commands/logout.d.ts +4 -0
  167. package/dist/cli/commands/logout.js +53 -0
  168. package/dist/cli/commands/logs.js +8 -6
  169. package/dist/cli/commands/models.d.ts +2 -1
  170. package/dist/cli/commands/models.js +39 -37
  171. package/dist/cli/commands/schedules.d.ts +21 -0
  172. package/dist/cli/commands/schedules.js +143 -0
  173. package/dist/cli/commands/start.d.ts +11 -10
  174. package/dist/cli/commands/start.js +35 -62
  175. package/dist/cli/content-view.d.ts +9 -0
  176. package/dist/cli/content-view.js +26 -0
  177. package/dist/cli/fail.d.ts +9 -12
  178. package/dist/cli/fail.js +17 -14
  179. package/dist/cli/invoke-stream.d.ts +12 -3
  180. package/dist/cli/invoke-stream.js +85 -23
  181. package/dist/cli/kernel.d.ts +9 -2
  182. package/dist/cli/kernel.js +15 -6
  183. package/dist/cli/login-relay.d.ts +6 -4
  184. package/dist/cli/login-relay.js +19 -7
  185. package/dist/cli/models-view.d.ts +5 -1
  186. package/dist/cli/models-view.js +1 -1
  187. package/dist/cli/person.d.ts +10 -0
  188. package/dist/cli/person.js +23 -0
  189. package/dist/cli/program.js +421 -203
  190. package/dist/cli/serve.d.ts +13 -63
  191. package/dist/cli/serve.js +31 -121
  192. package/dist/cli/shared.d.ts +21 -48
  193. package/dist/cli/shared.js +70 -89
  194. package/dist/cli/ticket.d.ts +136 -0
  195. package/dist/cli/ticket.js +476 -0
  196. package/dist/client.d.ts +7 -0
  197. package/dist/client.js +2 -0
  198. package/dist/content/declare.d.ts +70 -0
  199. package/dist/content/declare.js +163 -0
  200. package/dist/content/file.d.ts +32 -0
  201. package/dist/content/file.js +119 -0
  202. package/dist/content/git.d.ts +41 -0
  203. package/dist/content/git.js +220 -0
  204. package/dist/content/mount.d.ts +2 -0
  205. package/dist/content/mount.js +15 -0
  206. package/dist/content/resolve.d.ts +52 -0
  207. package/dist/content/resolve.js +151 -0
  208. package/dist/content/source.d.ts +27 -0
  209. package/dist/content/source.js +53 -0
  210. package/dist/conversation-sessions.d.ts +26 -0
  211. package/dist/conversation-sessions.js +99 -0
  212. package/dist/declared-secrets.d.ts +19 -37
  213. package/dist/declared-secrets.js +16 -58
  214. package/dist/deploy/agentcore/destroy.d.ts +3 -3
  215. package/dist/deploy/agentcore/destroy.js +46 -28
  216. package/dist/deploy/agentcore/forwarder.js +105 -22
  217. package/dist/deploy/agentcore/logs.js +1 -1
  218. package/dist/deploy/agentcore/plan.d.ts +10 -24
  219. package/dist/deploy/agentcore/plan.js +31 -135
  220. package/dist/deploy/agentcore/run.d.ts +1 -1
  221. package/dist/deploy/agentcore/run.js +8 -5
  222. package/dist/deploy/agentcore/shell.d.ts +1 -1
  223. package/dist/deploy/agentcore/shell.js +5 -5
  224. package/dist/deploy/api.d.ts +34 -0
  225. package/dist/deploy/api.js +50 -0
  226. package/dist/deploy/box-shell.d.ts +6 -0
  227. package/dist/deploy/box-shell.js +7 -0
  228. package/dist/deploy/build-context.d.ts +7 -5
  229. package/dist/deploy/build-context.js +41 -35
  230. package/dist/deploy/channel-ingress.d.ts +6 -1
  231. package/dist/deploy/channel-ingress.js +18 -5
  232. package/dist/deploy/container.d.ts +22 -9
  233. package/dist/deploy/container.js +96 -55
  234. package/dist/deploy/docker/plan.d.ts +3 -2
  235. package/dist/deploy/docker/plan.js +13 -14
  236. package/dist/deploy/docker/run.d.ts +2 -2
  237. package/dist/deploy/docker/run.js +8 -4
  238. package/dist/deploy/fly/plan.d.ts +1 -1
  239. package/dist/deploy/fly/plan.js +12 -21
  240. package/dist/deploy/fly/run.d.ts +10 -6
  241. package/dist/deploy/fly/run.js +11 -6
  242. package/dist/deploy/hosts.d.ts +2 -1
  243. package/dist/deploy/hosts.js +2 -1
  244. package/dist/deploy/preflight.d.ts +21 -23
  245. package/dist/deploy/preflight.js +223 -202
  246. package/dist/deploy/railway/plan.d.ts +8 -6
  247. package/dist/deploy/railway/plan.js +15 -58
  248. package/dist/deploy/railway/run.d.ts +8 -3
  249. package/dist/deploy/railway/run.js +12 -8
  250. package/dist/deploy/residency.d.ts +5 -11
  251. package/dist/deploy/residency.js +0 -6
  252. package/dist/deploy/runner.d.ts +1 -1
  253. package/dist/deploy/runner.js +1 -1
  254. package/dist/deploy/secrets.d.ts +16 -2
  255. package/dist/deploy/secrets.js +33 -6
  256. package/dist/deploy/workspace.d.ts +11 -6
  257. package/dist/deploy/workspace.js +55 -38
  258. package/dist/dev-supervisor.d.ts +11 -2
  259. package/dist/dev-supervisor.js +52 -27
  260. package/dist/effect-port.d.ts +4 -4
  261. package/dist/effect-port.js +4 -4
  262. package/dist/env.d.ts +0 -13
  263. package/dist/env.js +1 -44
  264. package/dist/environment/apt.d.ts +4 -0
  265. package/dist/environment/apt.js +32 -0
  266. package/dist/environment/declare.d.ts +59 -0
  267. package/dist/environment/declare.js +210 -0
  268. package/dist/environment/mise.d.ts +51 -0
  269. package/dist/environment/mise.js +272 -0
  270. package/dist/feishu.d.ts +2 -2
  271. package/dist/feishu.js +2 -2
  272. package/dist/git.d.ts +13 -0
  273. package/dist/git.js +55 -0
  274. package/dist/harnesses/models/catalog.d.ts +20 -0
  275. package/dist/harnesses/models/catalog.js +254 -0
  276. package/dist/{engines/pi/auth.d.ts → harnesses/models/credentials.d.ts} +3 -3
  277. package/dist/{engines/pi/auth.js → harnesses/models/credentials.js} +3 -3
  278. package/dist/harnesses/models/environment.d.ts +48 -0
  279. package/dist/harnesses/models/environment.js +88 -0
  280. package/dist/harnesses/models/host.d.ts +30 -0
  281. package/dist/harnesses/models/host.js +97 -0
  282. package/dist/{engines/pi → harnesses/models}/login.d.ts +66 -70
  283. package/dist/harnesses/models/login.js +439 -0
  284. package/dist/harnesses/models/models.d.ts +84 -0
  285. package/dist/harnesses/models/models.js +197 -0
  286. package/dist/harnesses/pi-durable/agent.d.ts +42 -0
  287. package/dist/harnesses/pi-durable/agent.js +581 -0
  288. package/dist/harnesses/pi-durable/defaults.d.ts +28 -0
  289. package/dist/harnesses/pi-durable/defaults.js +26 -0
  290. package/dist/harnesses/pi-durable/define.d.ts +77 -0
  291. package/dist/harnesses/pi-durable/define.js +28 -0
  292. package/dist/harnesses/pi-durable/entries.d.ts +32 -0
  293. package/dist/harnesses/pi-durable/entries.js +126 -0
  294. package/dist/harnesses/pi-durable/events.d.ts +25 -0
  295. package/dist/harnesses/pi-durable/events.js +238 -0
  296. package/dist/harnesses/pi-durable/ids.d.ts +16 -0
  297. package/dist/harnesses/pi-durable/ids.js +55 -0
  298. package/dist/harnesses/pi-durable/messages.d.ts +41 -0
  299. package/dist/harnesses/pi-durable/messages.js +116 -0
  300. package/dist/harnesses/pi-durable/open.d.ts +95 -0
  301. package/dist/harnesses/pi-durable/open.js +330 -0
  302. package/dist/harnesses/pi-durable/reads.d.ts +30 -0
  303. package/dist/harnesses/pi-durable/reads.js +94 -0
  304. package/dist/harnesses/pi-durable/registry.d.ts +18 -0
  305. package/dist/harnesses/pi-durable/registry.js +56 -0
  306. package/dist/harnesses/pi-durable/release.d.ts +13 -0
  307. package/dist/harnesses/pi-durable/release.js +40 -0
  308. package/dist/harnesses/pi-durable/serve.d.ts +90 -0
  309. package/dist/harnesses/pi-durable/serve.js +186 -0
  310. package/dist/harnesses/pi-durable/sessions.d.ts +27 -0
  311. package/dist/harnesses/pi-durable/sessions.js +47 -0
  312. package/dist/harnesses/pi-durable/storage.d.ts +4 -0
  313. package/dist/harnesses/pi-durable/storage.js +49 -0
  314. package/dist/harnesses/pi-durable/stream.d.ts +20 -0
  315. package/dist/harnesses/pi-durable/stream.js +221 -0
  316. package/dist/harnesses/pi-durable/text.d.ts +30 -0
  317. package/dist/harnesses/pi-durable/text.js +292 -0
  318. package/dist/harnesses/pi-durable/wake.d.ts +11 -0
  319. package/dist/harnesses/pi-durable/wake.js +90 -0
  320. package/dist/http/client.d.ts +6 -0
  321. package/dist/http/client.js +256 -0
  322. package/dist/http/contract.d.ts +541 -0
  323. package/dist/http/contract.js +179 -0
  324. package/dist/http/server.d.ts +34 -0
  325. package/dist/http/server.js +194 -0
  326. package/dist/index.d.ts +19 -4
  327. package/dist/index.js +13 -5
  328. package/dist/lark.d.ts +2 -2
  329. package/dist/lark.js +2 -2
  330. package/dist/locked-file.d.ts +17 -0
  331. package/dist/{engines/pi/locked-file.js → locked-file.js} +16 -10
  332. package/dist/once.d.ts +6 -0
  333. package/dist/once.js +23 -0
  334. package/dist/paths.d.ts +36 -52
  335. package/dist/paths.js +66 -182
  336. package/dist/runtime.d.ts +25 -2
  337. package/dist/runtime.js +27 -0
  338. package/dist/scaffold/add-channel.d.ts +14 -27
  339. package/dist/scaffold/add-channel.js +82 -180
  340. package/dist/scaffold/init.d.ts +17 -18
  341. package/dist/scaffold/init.js +87 -99
  342. package/dist/scaffold/templates/SYSTEM.md +12 -0
  343. package/dist/scaffold/templates/env.example +4 -10
  344. package/dist/scaffold/templates/fastagent.config.ts +21 -33
  345. package/dist/scaffold/templates/gitignore +8 -1
  346. package/dist/scaffold/templates/skills/writing-great-skills/SKILL.md +1 -2
  347. package/dist/scaffold/templates.d.ts +9 -4
  348. package/dist/scaffold/templates.js +7 -5
  349. package/dist/scaffold/vendor-skill.d.ts +1 -4
  350. package/dist/scaffold/vendor-skill.js +5 -6
  351. package/dist/schedule/cron.d.ts +13 -0
  352. package/dist/schedule/cron.js +52 -0
  353. package/dist/schedule/eventbridge-cron.d.ts +17 -0
  354. package/dist/schedule/eventbridge-cron.js +88 -0
  355. package/dist/schedule/schedule.d.ts +23 -0
  356. package/dist/schedule/schedule.js +34 -0
  357. package/dist/schedule/scheduler.d.ts +34 -33
  358. package/dist/schedule/scheduler.js +251 -137
  359. package/dist/schedule/state.d.ts +4 -14
  360. package/dist/schedule/state.js +7 -7
  361. package/dist/schedule/wake-alarm.d.ts +22 -2
  362. package/dist/schedule/wake-alarm.js +66 -20
  363. package/dist/schedule/wakeups.d.ts +16 -15
  364. package/dist/schedule/wakeups.js +47 -45
  365. package/dist/service.d.ts +56 -169
  366. package/dist/service.js +91 -187
  367. package/dist/serving-command.d.ts +10 -0
  368. package/dist/serving-command.js +16 -0
  369. package/dist/sign-in-api.d.ts +147 -0
  370. package/dist/sign-in-api.js +9 -0
  371. package/dist/slack.d.ts +1 -1
  372. package/dist/slack.js +1 -1
  373. package/dist/{channels/kit/state.js → state-file.js} +6 -6
  374. package/dist/telegram.d.ts +1 -0
  375. package/dist/telegram.js +1 -0
  376. package/dist/tunnel.d.ts +3 -0
  377. package/dist/tunnel.js +14 -5
  378. package/dist/value-file.d.ts +40 -0
  379. package/dist/value-file.js +164 -0
  380. package/dist/version.d.ts +5 -0
  381. package/dist/version.js +14 -1
  382. package/package.json +18 -28
  383. package/dist/agent.d.ts +0 -106
  384. package/dist/agent.js +0 -21
  385. package/dist/channels/control.d.ts +0 -31
  386. package/dist/channels/control.js +0 -329
  387. package/dist/channels/define-channel.d.ts +0 -39
  388. package/dist/channels/define-channel.js +0 -9
  389. package/dist/channels/feishu/context-buffer.d.ts +0 -35
  390. package/dist/channels/feishu/context-buffer.js +0 -65
  391. package/dist/channels/feishu/scaffold/channel.websocket.ts +0 -31
  392. package/dist/channels/http.d.ts +0 -23
  393. package/dist/channels/http.js +0 -57
  394. package/dist/channels/lark/scaffold/channel.websocket.ts +0 -31
  395. package/dist/channels/slack/context-buffer.d.ts +0 -23
  396. package/dist/channels/slack/context-buffer.js +0 -37
  397. package/dist/channels/sse.d.ts +0 -4
  398. package/dist/channels/sse.js +0 -66
  399. package/dist/cli/commands/chat.d.ts +0 -3
  400. package/dist/cli/commands/chat.js +0 -14
  401. package/dist/cli/commands/routine-run.d.ts +0 -6
  402. package/dist/cli/commands/routine-run.js +0 -46
  403. package/dist/cli/commands/routine.d.ts +0 -25
  404. package/dist/cli/commands/routine.js +0 -122
  405. package/dist/cli/commands/tool.d.ts +0 -1
  406. package/dist/cli/commands/tool.js +0 -57
  407. package/dist/collect.d.ts +0 -63
  408. package/dist/collect.js +0 -90
  409. package/dist/core.d.ts +0 -13
  410. package/dist/core.js +0 -15
  411. package/dist/engines/pi/agent-models.d.ts +0 -83
  412. package/dist/engines/pi/agent-models.js +0 -120
  413. package/dist/engines/pi/agent-session-factory.d.ts +0 -121
  414. package/dist/engines/pi/agent-session-factory.js +0 -535
  415. package/dist/engines/pi/chat.d.ts +0 -3
  416. package/dist/engines/pi/chat.js +0 -8
  417. package/dist/engines/pi/config.d.ts +0 -71
  418. package/dist/engines/pi/config.js +0 -199
  419. package/dist/engines/pi/create.d.ts +0 -170
  420. package/dist/engines/pi/create.js +0 -343
  421. package/dist/engines/pi/definition.d.ts +0 -74
  422. package/dist/engines/pi/definition.js +0 -236
  423. package/dist/engines/pi/entry-images.d.ts +0 -14
  424. package/dist/engines/pi/entry-images.js +0 -23
  425. package/dist/engines/pi/invoke-session.d.ts +0 -18
  426. package/dist/engines/pi/invoke-session.js +0 -403
  427. package/dist/engines/pi/locked-file.d.ts +0 -9
  428. package/dist/engines/pi/login.js +0 -331
  429. package/dist/engines/pi/machine.d.ts +0 -53
  430. package/dist/engines/pi/machine.js +0 -137
  431. package/dist/engines/pi/models.d.ts +0 -200
  432. package/dist/engines/pi/models.js +0 -533
  433. package/dist/engines/pi/open.d.ts +0 -180
  434. package/dist/engines/pi/open.js +0 -270
  435. package/dist/engines/pi/openai-account-models.d.ts +0 -35
  436. package/dist/engines/pi/openai-account-models.js +0 -116
  437. package/dist/engines/pi/report.d.ts +0 -18
  438. package/dist/engines/pi/report.js +0 -59
  439. package/dist/engines/pi/retry-event.d.ts +0 -6
  440. package/dist/engines/pi/retry-event.js +0 -15
  441. package/dist/engines/pi/service.d.ts +0 -9
  442. package/dist/engines/pi/service.js +0 -14
  443. package/dist/engines/pi/session-builder.d.ts +0 -13
  444. package/dist/engines/pi/session-builder.js +0 -117
  445. package/dist/engines/pi/session-control.d.ts +0 -77
  446. package/dist/engines/pi/session-control.js +0 -908
  447. package/dist/engines/pi/session-effects.d.ts +0 -14
  448. package/dist/engines/pi/session-effects.js +0 -30
  449. package/dist/engines/pi/session-inheritance.d.ts +0 -21
  450. package/dist/engines/pi/session-inheritance.js +0 -204
  451. package/dist/engines/pi/session-markers.d.ts +0 -49
  452. package/dist/engines/pi/session-markers.js +0 -60
  453. package/dist/engines/pi/session-settings.d.ts +0 -67
  454. package/dist/engines/pi/session-settings.js +0 -119
  455. package/dist/engines/pi/session-store.d.ts +0 -69
  456. package/dist/engines/pi/session-store.js +0 -430
  457. package/dist/engines/pi/tool-context.d.ts +0 -45
  458. package/dist/engines/pi/tool-context.js +0 -31
  459. package/dist/engines/pi/tool.d.ts +0 -99
  460. package/dist/engines/pi/tool.js +0 -118
  461. package/dist/engines/pi/turn-kit.d.ts +0 -92
  462. package/dist/engines/pi/turn-kit.js +0 -288
  463. package/dist/engines/pi/wake-tool.d.ts +0 -9
  464. package/dist/engines/pi/wake-tool.js +0 -92
  465. package/dist/loader.d.ts +0 -41
  466. package/dist/loader.js +0 -116
  467. package/dist/node.d.ts +0 -3
  468. package/dist/node.js +0 -4
  469. package/dist/observe.d.ts +0 -6
  470. package/dist/observe.js +0 -47
  471. package/dist/pi.d.ts +0 -25
  472. package/dist/pi.js +0 -15
  473. package/dist/scaffold/templates/APPEND_SYSTEM.md +0 -12
  474. package/dist/scaffold/templates/tools/fetch-url.ts +0 -25
  475. package/dist/schedule/discover.d.ts +0 -17
  476. package/dist/schedule/discover.js +0 -61
  477. package/dist/schedule/routine.d.ts +0 -43
  478. package/dist/schedule/routine.js +0 -32
  479. package/dist/schedule/run.d.ts +0 -55
  480. package/dist/schedule/run.js +0 -146
  481. package/dist/secrets-gate.d.ts +0 -32
  482. package/dist/secrets-gate.js +0 -33
  483. package/dist/session-remote.d.ts +0 -47
  484. package/dist/session-remote.js +0 -460
  485. package/dist/session.d.ts +0 -466
  486. package/dist/session.js +0 -57
  487. /package/dist/{channels/kit/state.d.ts → state-file.d.ts} +0 -0
@@ -1,7 +1,16 @@
1
- import { type ResolvedPlacement } from "./paths.ts";
2
1
  /** chokidar `ignored` matcher for the narrow watch scope (true = ignore), rooted at the AGENT DIR. */
3
2
  export declare function devWatchIgnored(root: string, envFile: string): (path: string) => boolean;
3
+ /**
4
+ * In the dev worker: on the supervisor's `restart`, wait until no turn runs (`activeWork`, which every leased session
5
+ * counts), then `stop`. Bounded by `limitMs`, after which it stops anyway and says it cut work off: a chat that
6
+ * keeps the worker busy must not hold an edit back forever. A second `restart` while waiting is the same restart.
7
+ */
8
+ export declare function listenForRestart(stop: () => void, options?: {
9
+ busy?: () => number;
10
+ limitMs?: number;
11
+ pollMs?: number;
12
+ }): (message: unknown) => void;
4
13
  /** Spawn the dev worker and restart it on agent-dir edits; supervise its lifecycle until the process exits. */
5
- export declare function runDevSupervisor(placement: ResolvedPlacement, options?: {
14
+ export declare function runDevSupervisor(agentDir: string, options?: {
6
15
  tunnel?: boolean;
7
16
  }): Promise<void>;
@@ -5,16 +5,16 @@
5
5
  import { spawn } from "node:child_process";
6
6
  import { relative, sep } from "node:path";
7
7
  import { watch as watchTree } from "chokidar";
8
- import { AGENT_CONFIG_FILE, AGENT_MODEL_CATALOG_FILE, AGENT_MODELS_FILE, resolveStateRoot, isUnderDir, } from "./paths.js";
9
- import { dotEnvPath } from "./env.js";
8
+ import { AGENT_CONFIG_FILE, CONTEXT_FILE, LOCAL_CONTEXT_FILE, resolveStateRoot, isUnderDir } from "./paths.js";
9
+ import { dotEnvPath } from "./value-file.js";
10
10
  import { log } from "./log.js";
11
11
  import { openExternalUrl } from "./open-url.js";
12
12
  import { declaredChannels } from "./channels/discover.js";
13
+ import { activeWork } from "./channels/busy.js";
13
14
  import { announceWebhooks, startCloudflareTunnel } from "./tunnel.js";
14
- /** What the dev watcher restarts on (agent-dir-relative): the process-bound code inputs only. */
15
- /** The agent-dir directories loaded ONCE per worker: a restart is their only re-read. */
16
- const CODE_INPUT_DIRS = ["tools", "channels", "routines", "extensions"];
17
- const WATCHED_HINT = `${CODE_INPUT_DIRS.map((dir) => `${dir}/`).join(", ")}, package.json, fastagent.config.ts, models.json, models-store.json, .secrets/.env`;
15
+ /** The agent's code (its tools, extensions, channels and schedules), loaded ONCE per worker: a restart is its only re-read. */
16
+ const CODE_DIR = "src";
17
+ const WATCHED_HINT = `${CODE_DIR}/, package.json, fastagent.config.ts, context.json, context.local.json, .secrets/.env`;
18
18
  /** chokidar `ignored` matcher for the narrow watch scope (true = ignore), rooted at the AGENT DIR. */
19
19
  export function devWatchIgnored(root, envFile) {
20
20
  // The `.env` is allow-listed by its RESOLVED path, not by the `.secrets` name.
@@ -23,21 +23,14 @@ export function devWatchIgnored(root, envFile) {
23
23
  if (path === root)
24
24
  return false; // the root itself must not be pruned
25
25
  const rel = relative(root, path);
26
- // Code inputs at the agent dir root: config, package.json, and the dirs loaded once per worker (a restart is
26
+ // Code inputs at the agent dir root: config, package.json, and the code loaded once per worker (a restart is
27
27
  // their only re-read).
28
- if (rel === AGENT_CONFIG_FILE)
28
+ if (rel === AGENT_CONFIG_FILE || rel === CONTEXT_FILE || rel === LOCAL_CONTEXT_FILE)
29
29
  return false;
30
30
  if (rel === "package.json")
31
31
  return false;
32
- // models.json is read ONCE per worker (agentModels' snapshot, which every session's runtime is built from), so an
33
- // edit needs a restart like any other code input.
34
- if (rel === AGENT_MODELS_FILE)
35
- return false;
36
- // So is the model catalog: a `models --refresh` during `dev` restarts the worker onto the new models.
37
- if (rel === AGENT_MODEL_CATALOG_FILE)
38
- return false;
39
32
  const segments = rel.split(sep);
40
- if (CODE_INPUT_DIRS.includes(segments[0]))
33
+ if (segments[0] === CODE_DIR)
41
34
  return false;
42
35
  // The `.env` restarts too (credentials are process-bound).
43
36
  if (segments.length <= envRel.length && segments.every((seg, i) => seg === envRel[i]))
@@ -45,9 +38,37 @@ export function devWatchIgnored(root, envFile) {
45
38
  return true;
46
39
  };
47
40
  }
41
+ /** How long a dev worker waits for its running turns before it restarts anyway. */
42
+ const DEV_RESTART_WAIT_MS = 10 * 60_000;
43
+ /**
44
+ * In the dev worker: on the supervisor's `restart`, wait until no turn runs (`activeWork`, which every leased session
45
+ * counts), then `stop`. Bounded by `limitMs`, after which it stops anyway and says it cut work off: a chat that
46
+ * keeps the worker busy must not hold an edit back forever. A second `restart` while waiting is the same restart.
47
+ */
48
+ export function listenForRestart(stop, options = {}) {
49
+ const { busy = activeWork, limitMs = DEV_RESTART_WAIT_MS, pollMs = 100 } = options;
50
+ let restarting = false;
51
+ return (message) => {
52
+ if (message?.type !== "restart" || restarting)
53
+ return;
54
+ restarting = true;
55
+ void (async () => {
56
+ // `busy` counts pieces of work (a channel turn is counted queued and again leased), not turns, so no number.
57
+ if (busy() > 0)
58
+ log.info("[fastagent] restarting once the turns running now finish");
59
+ const deadline = Date.now() + limitMs;
60
+ while (busy() > 0 && Date.now() < deadline)
61
+ await new Promise((resolve) => setTimeout(resolve, pollMs));
62
+ if (busy() > 0) {
63
+ log.warn(`[fastagent] restarting with work still running after ${limitMs / 60_000} minutes: it is cut off`);
64
+ }
65
+ stop();
66
+ })();
67
+ };
68
+ }
48
69
  /** Spawn the dev worker and restart it on agent-dir edits; supervise its lifecycle until the process exits. */
49
- export async function runDevSupervisor(placement, options = {}) {
50
- // The placement arrives RESOLVED from the command (which already routed its refusal through failStartup).
70
+ export async function runDevSupervisor(agentDir, options = {}) {
71
+ // The agent directory arrives RESOLVED from the command (which already routed its refusal through failStartup).
51
72
  let worker;
52
73
  let reloadPending = false;
53
74
  let everServed = false; // has any worker successfully bound (sent `ready`) yet?
@@ -72,9 +93,9 @@ export async function runDevSupervisor(placement, options = {}) {
72
93
  void startCloudflareTunnel(m.port).then((t) => {
73
94
  if (t) {
74
95
  tunnel = t;
75
- void announceWebhooks(placement.agentDir, t.url, declaredChannels(m.routeChannels ?? []), {
96
+ void announceWebhooks(agentDir, t.url, declaredChannels(m.routeChannels ?? []), {
76
97
  openUrl: openExternalUrl,
77
- stateRoot: resolveStateRoot(placement.agentDir),
98
+ stateRoot: resolveStateRoot(agentDir),
78
99
  });
79
100
  }
80
101
  });
@@ -90,7 +111,7 @@ export async function runDevSupervisor(placement, options = {}) {
90
111
  }
91
112
  else if (!everServed) {
92
113
  // Failed BEFORE ever serving — a non-editable startup failure (bad flag, EADDRINUSE, broken initial
93
- // workspace) that saving cannot fix.
114
+ // definition) that saving cannot fix.
94
115
  process.exit(code ?? 1);
95
116
  }
96
117
  else {
@@ -103,26 +124,30 @@ export async function runDevSupervisor(placement, options = {}) {
103
124
  log.info(`[fastagent] change detected — restarting…`);
104
125
  if (worker) {
105
126
  reloadPending = true;
106
- worker.kill("SIGTERM"); // the exit handler respawns once the port is released
127
+ // The worker stops once its running turns finish (listenForRestart); the exit handler respawns it then. A turn
128
+ // that wrote the very file that changed is not cut off by its own edit. A worker whose channel is already
129
+ // closed is exiting: its exit handler respawns it, and `send` would throw ERR_IPC_CHANNEL_CLOSED.
130
+ if (worker.connected)
131
+ worker.send({ type: "restart" });
107
132
  }
108
133
  else {
109
134
  spawnWorker(); // worker was down (broken edit) — retry now
110
135
  }
111
136
  };
112
137
  // chokidar gives reliable cross-platform recursion + structural ignore that native fs.watch cannot.
113
- const watcher = watchTree(placement.agentDir, {
138
+ const watcher = watchTree(agentDir, {
114
139
  ignoreInitial: true, // the startup scan is not a change
115
- ignored: devWatchIgnored(placement.agentDir, dotEnvPath(placement.agentDir)),
140
+ ignored: devWatchIgnored(agentDir, dotEnvPath(agentDir)),
116
141
  });
117
142
  watcher.on("all", () => {
118
143
  clearTimeout(timer);
119
144
  timer = setTimeout(triggerReload, 200);
120
145
  });
121
146
  watcher.on("error", (error) => log.warn(`[fastagent] file watching error (${error.message}); some edits may need a manual restart`));
122
- log.info(`[fastagent] watching ${WATCHED_HINT} — code edits restart the dev worker (--no-watch to disable); AGENTS.md/SYSTEM.md/APPEND_SYSTEM.md/skills/prompts edits go live next turn without a restart`);
147
+ log.info(`[fastagent] watching ${WATCHED_HINT} — code edits restart the dev worker (--no-watch to disable); SYSTEM.md, AGENTS.md, skills/ and prompts/ go live without a restart`);
123
148
  // FASTAGENT_SECRETS_DIR can move the `.env` OUT of the agent dir entirely.
124
- if (!isUnderDir(dotEnvPath(placement.agentDir), placement.agentDir)) {
125
- log.warn(`[fastagent] .env lives outside the agent dir (FASTAGENT_SECRETS_DIR → ${dotEnvPath(placement.agentDir)}) — it is NOT watched; restart dev after editing it`);
149
+ if (!isUnderDir(dotEnvPath(agentDir), agentDir)) {
150
+ log.warn(`[fastagent] .env lives outside the agent dir (FASTAGENT_SECRETS_DIR → ${dotEnvPath(agentDir)}) — it is NOT watched; restart dev after editing it`);
126
151
  }
127
152
  const shutdown = () => {
128
153
  worker?.kill("SIGTERM");
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * The ONE crossing between this codebase's Promise-shaped ports and Effect execution.
3
3
  *
4
- * The ports are everywhere and none of them are ours to change: the SPEC fixes `Agent.invoke` as an
5
- * AsyncIterable and `SessionControl` as Promises, pi's SDK is Promise-based, every platform client
4
+ * The ports are everywhere and none of them are ours to change: the Agent API fixes its streams as
5
+ * AsyncIterables and its other operations as Promises, pi's SDK is Promise-based, every platform client
6
6
  * is `fetch`, and so is the filesystem. Two properties have to hold at each crossing:
7
7
  *
8
8
  * 1. INTERRUPTION MUST JOIN. A Promise exposes no abort hook, so cancelling the fiber that awaits
@@ -12,11 +12,11 @@
12
12
  * pending promise to settle before releasing, and abort first when the port gives us a door.
13
13
  * {@link port} is the explicit opt-out, for work a caller may walk away from — so a call site
14
14
  * holding a lease, a session or any other resource must not reach for it.
15
- * 2. THE CAUSE SURVIVES. Retry classification (`turn-kit.classifyRetryable`), the channels'
15
+ * 2. THE CAUSE SURVIVES. Retry classification (`messages.ts` `thrownFailure`), the channels'
16
16
  * platform error types and every operator-facing message read the ORIGINAL error, so the
17
17
  * wrapper carries it verbatim and {@link portError} is how a caller gets it back.
18
18
  *
19
- * The channel kit, the pi engine, the AgentCore runtime and the scheduler all cross through here, so each rule is
19
+ * The channel kit, the pi harness, the AgentCore runtime and the scheduler all cross through here, so each rule is
20
20
  * written once.
21
21
  *
22
22
  * What is NOT here is a policy: whether a failure rejects a request, is logged, or ends a turn stays
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * The ONE crossing between this codebase's Promise-shaped ports and Effect execution.
3
3
  *
4
- * The ports are everywhere and none of them are ours to change: the SPEC fixes `Agent.invoke` as an
5
- * AsyncIterable and `SessionControl` as Promises, pi's SDK is Promise-based, every platform client
4
+ * The ports are everywhere and none of them are ours to change: the Agent API fixes its streams as
5
+ * AsyncIterables and its other operations as Promises, pi's SDK is Promise-based, every platform client
6
6
  * is `fetch`, and so is the filesystem. Two properties have to hold at each crossing:
7
7
  *
8
8
  * 1. INTERRUPTION MUST JOIN. A Promise exposes no abort hook, so cancelling the fiber that awaits
@@ -12,11 +12,11 @@
12
12
  * pending promise to settle before releasing, and abort first when the port gives us a door.
13
13
  * {@link port} is the explicit opt-out, for work a caller may walk away from — so a call site
14
14
  * holding a lease, a session or any other resource must not reach for it.
15
- * 2. THE CAUSE SURVIVES. Retry classification (`turn-kit.classifyRetryable`), the channels'
15
+ * 2. THE CAUSE SURVIVES. Retry classification (`messages.ts` `thrownFailure`), the channels'
16
16
  * platform error types and every operator-facing message read the ORIGINAL error, so the
17
17
  * wrapper carries it verbatim and {@link portError} is how a caller gets it back.
18
18
  *
19
- * The channel kit, the pi engine, the AgentCore runtime and the scheduler all cross through here, so each rule is
19
+ * The channel kit, the pi harness, the AgentCore runtime and the scheduler all cross through here, so each rule is
20
20
  * written once.
21
21
  *
22
22
  * What is NOT here is a policy: whether a failure rejects a request, is logged, or ends a turn stays
package/dist/env.d.ts CHANGED
@@ -4,19 +4,6 @@
4
4
  * occurrence wins ({@link parseEnvContent}).
5
5
  */
6
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). */
8
- export declare function parseEnvContent(content: string): Map<string, string>;
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`. */
17
- export declare function dotEnvPath(agentDir: string, env?: NodeJS.ProcessEnv): string;
18
- /** The committable template: `<agentDir>/.secrets/.env.example`. */
19
- export declare function envExamplePath(agentDir: string): string;
20
7
  /**
21
8
  * ENTER an agent directory's runtime environment: its `.env` becomes `process.env`, and this process's outbound fetch
22
9
  * follows whatever proxy that (or the ambient environment) declares.
package/dist/env.js CHANGED
@@ -2,7 +2,7 @@ import { readFileSync, existsSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { log } from "./log.js";
4
4
  import { installProxyFetch } from "./proxy.js";
5
- import { SECRETS_DIRNAME, resolveSecretsDir } from "./paths.js";
5
+ import { dotEnvPath, loadEnvValues, parseEnvContent } from "./value-file.js";
6
6
  /**
7
7
  * Write parsed values into `process.env`, matching Node's `--env-file` / `process.loadEnvFile` precedence on BOTH
8
8
  * axes (verified against Node in test/env.test.ts): a real env var wins over the file, and within the file the last
@@ -13,49 +13,6 @@ export function applyEnvValues(values) {
13
13
  if (!(key in process.env))
14
14
  process.env[key] = value;
15
15
  }
16
- /** Parse .env content into key → value (the dialect above; last occurrence of a key wins). */
17
- export function parseEnvContent(content) {
18
- const parsed = new Map();
19
- for (const raw of content.split("\n")) {
20
- const line = raw.trim();
21
- if (!line || line.startsWith("#"))
22
- continue;
23
- const eq = line.indexOf("=");
24
- if (eq < 1)
25
- continue; // no `=`, or an empty key
26
- const key = line.slice(0, eq).trim();
27
- let value = line.slice(eq + 1).trim();
28
- const quote = value[0];
29
- if ((quote === '"' || quote === "'") && value.at(-1) === quote)
30
- value = value.slice(1, -1);
31
- parsed.set(key, value); // in-file: last occurrence wins (Map overwrite)
32
- }
33
- return parsed;
34
- }
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
- export function dotEnvPath(agentDir, env = process.env) {
53
- return join(resolveSecretsDir(agentDir, env), ".env");
54
- }
55
- /** The committable template: `<agentDir>/.secrets/.env.example`. */
56
- export function envExamplePath(agentDir) {
57
- return join(agentDir, SECRETS_DIRNAME, ".env.example");
58
- }
59
16
  /**
60
17
  * ENTER an agent directory's runtime environment: its `.env` becomes `process.env`, and this process's outbound fetch
61
18
  * follows whatever proxy that (or the ambient environment) declares.
@@ -0,0 +1,4 @@
1
+ /** The packages of `names` this machine does not have, or undefined when it cannot tell (not Debian, or no dpkg). */
2
+ export declare function missingAptPackages(names: readonly string[], debian?: boolean): string[] | undefined;
3
+ /** What to tell whoever runs the agent here, with the command that installs them. */
4
+ export declare function aptAdvice(missing: readonly string[]): string;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * The apt packages an environment declares, on THIS machine: the image installs them, and a machine that runs the agent
3
+ * locally supplies its own. Only Debian and Ubuntu can be asked (dpkg); elsewhere nothing is said, and FastAgent never
4
+ * runs apt itself.
5
+ */
6
+ import { spawnSync } from "node:child_process";
7
+ import { existsSync } from "node:fs";
8
+ /** The packages of `names` this machine does not have, or undefined when it cannot tell (not Debian, or no dpkg). */
9
+ export function missingAptPackages(names, debian = existsSync("/etc/debian_version")) {
10
+ if (!debian)
11
+ return undefined;
12
+ if (names.length === 0)
13
+ return [];
14
+ // dpkg-query exits 1 when a name is unknown to it; the names it does know are still printed.
15
+ // biome-ignore lint/suspicious/noTemplateCurlyInString: dpkg-query's own format, not a template
16
+ const result = spawnSync("dpkg-query", ["-W", "-f=${Package} ${db:Status-Status}\n", ...names], { encoding: "utf8" });
17
+ // A Debian-based image without dpkg cannot tell, which is what undefined says; anything else is a failure.
18
+ if (result.error?.code === "ENOENT")
19
+ return undefined;
20
+ if (result.error)
21
+ throw new Error(`dpkg-query could not run: ${result.error.message}`);
22
+ const installed = new Set(result.stdout
23
+ .split("\n")
24
+ .filter((line) => line.endsWith(" installed"))
25
+ .map((line) => line.split(" ")[0]));
26
+ return names.filter((name) => !installed.has(name));
27
+ }
28
+ /** What to tell whoever runs the agent here, with the command that installs them. */
29
+ export function aptAdvice(missing) {
30
+ return (`the image installs apt packages this machine does not have: ${missing.join(", ")} — ` +
31
+ `\`sudo apt-get install -y ${missing.join(" ")}\``);
32
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * WHAT AN AGENT'S ENVIRONMENT IS: the `environment` of `context.json`, read and refused in ONE place, and the backends a
3
+ * package may install from. FastAgent's own format, with mise underneath: `packages` (mise's tool names, each with a
4
+ * version), `aptPackages` (Debian packages the image installs) and `variables` (the environment variables its commands
5
+ * read, by name: their values come from the environment, as a tool's secrets do). Pure: installing it is mise.ts's.
6
+ * docs/design/agent-service.md §4 is the rule.
7
+ */
8
+ import { type DeclaredSecret } from "../declared-secrets.ts";
9
+ /** An agent's environment, as declared. */
10
+ export interface DeclaredEnvironment {
11
+ /** Package → version: `"latest"`, a prefix (`"2"`) or an exact version. */
12
+ packages: Record<string, string>;
13
+ /** Debian packages the image installs. */
14
+ aptPackages: string[];
15
+ /** Environment variable name → what it is for. Every one is required; its value comes from the environment. */
16
+ variables: Record<string, DeclaredVariable>;
17
+ }
18
+ /** One declared environment variable, as `context.json` holds it. */
19
+ interface DeclaredVariable {
20
+ /** What it is for, said where the variable is reported: runbooks, `info`, `fastagent env list`. */
21
+ description: string;
22
+ }
23
+ /** Read the `environment` of `context.json` (undefined is none). Every refusal names what it refuses. */
24
+ export declare function declareEnvironment(raw: unknown): DeclaredEnvironment | undefined;
25
+ /** The variables `environment` declares, as the declarations every serving path and `deploy` require. */
26
+ export declare function declaredVariables(environment: DeclaredEnvironment | undefined): DeclaredSecret[];
27
+ /** The backend a package is written with (`npm:prettier` is npm), or undefined for a registry name, which mise resolves. */
28
+ export declare function writtenBackend(name: string): string | undefined;
29
+ /**
30
+ * Why the registry name `name`, which mise resolves to `backend` (`cargo:tokei`), is refused; undefined when that
31
+ * backend is allowed.
32
+ */
33
+ export declare function resolvedBackendRefusal(name: string, backend: string): string | undefined;
34
+ /** PyPI packages whose runtime the environment does not declare, and what to declare. */
35
+ export interface MissingRuntime {
36
+ packages: string[];
37
+ missing: string[];
38
+ }
39
+ /**
40
+ * The PyPI packages, written `pypi:` or resolved to it (`backends`, by package), when the environment does not declare
41
+ * both `python` and `uv`: mise takes them from the machine and never adds them, so such a package installs on an
42
+ * author's machine that has them and fails in the image.
43
+ */
44
+ export declare function missingPythonRuntime(packages: Record<string, string>, backends: ReadonlyMap<string, string>): MissingRuntime | undefined;
45
+ /** What to tell whoever declared them, with the command that declares the rest. */
46
+ export declare function runtimeAdvice({ packages, missing }: MissingRuntime): string;
47
+ /** Whether `packages` declares `tool` under any backend or owner: `aqua:astral-sh/uv` is `uv`. */
48
+ export declare function declaresTool(packages: Record<string, string>, tool: string): boolean;
49
+ /**
50
+ * One argument of `fastagent env add`: `gh`, `gh@2`, `npm:@scope/tool@1`, or `apt:chromium` for an apt package. The
51
+ * version is what follows the last `@` after the backend, `latest` without one.
52
+ */
53
+ export declare function parsePackageArgument(arg: string): {
54
+ apt: string;
55
+ } | {
56
+ name: string;
57
+ version: string;
58
+ };
59
+ export {};
@@ -0,0 +1,210 @@
1
+ /**
2
+ * WHAT AN AGENT'S ENVIRONMENT IS: the `environment` of `context.json`, read and refused in ONE place, and the backends a
3
+ * package may install from. FastAgent's own format, with mise underneath: `packages` (mise's tool names, each with a
4
+ * version), `aptPackages` (Debian packages the image installs) and `variables` (the environment variables its commands
5
+ * read, by name: their values come from the environment, as a tool's secrets do). Pure: installing it is mise.ts's.
6
+ * docs/design/agent-service.md §4 is the rule.
7
+ */
8
+ import { isEnvName } from "../declared-secrets.js";
9
+ import { isDeployOwned } from "../deploy/secrets.js";
10
+ import { CONTEXT_FILE } from "../paths.js";
11
+ const ENVIRONMENT_KEYS = ["packages", "aptPackages", "variables"];
12
+ /** The backends a package may be written with: a release, a conda-forge build, an npm or a PyPI package. */
13
+ const WRITTEN_BACKENDS = ["aqua", "github", "gitlab", "conda", "npm", "pypi"];
14
+ /** The backends a registry name may resolve to: the written ones, more release hosts, and mise's own runtimes. */
15
+ const RESOLVED_BACKENDS = new Set([...WRITTEN_BACKENDS, "forgejo", "http", "packslip", "core"]);
16
+ /** The backends left out, each with its reason; any other is refused until it is classified. */
17
+ const LEFT_OUT = {
18
+ go: "builds from source: a whole Go toolchain in the image for one binary",
19
+ cargo: "builds from source: a whole Rust toolchain in the image for one binary",
20
+ spm: "builds from source with Swift",
21
+ gem: "compiles native extensions against Ruby",
22
+ spinel: "compiles Ruby into a binary",
23
+ asdf: "runs a plugin's shell scripts at install, and has no Windows",
24
+ vfox: "runs a plugin's code at install and whenever the environment is entered, and the plugin is not pinned",
25
+ dotnet: "needs the .NET SDK, which the environment does not declare yet",
26
+ ubi: "is deprecated in mise",
27
+ };
28
+ const ALTERNATIVE = "write the project's release with github:<owner>/<repo>, or install it in your own Dockerfile";
29
+ /** A registry name: what `mise registry` lists. */
30
+ const REGISTRY_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
31
+ /** What follows a written backend: a package or `owner/repo`, an npm scope included, without mise's `[options]`. */
32
+ const BACKEND_NAME = /^@?[A-Za-z0-9][A-Za-z0-9._/-]*$/;
33
+ /** A Debian package name (Debian Policy §5.6.1). */
34
+ const APT_NAME = /^[a-z0-9][a-z0-9+.-]+$/;
35
+ /** Read the `environment` of `context.json` (undefined is none). Every refusal names what it refuses. */
36
+ export function declareEnvironment(raw) {
37
+ if (raw === undefined)
38
+ return undefined;
39
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
40
+ throw new Error(`"environment" must be an object`);
41
+ const environment = raw;
42
+ for (const key of Object.keys(environment)) {
43
+ if (!ENVIRONMENT_KEYS.includes(key)) {
44
+ throw new Error(`environment: unknown key "${key}" (valid keys: ${ENVIRONMENT_KEYS.join(", ")})`);
45
+ }
46
+ }
47
+ return {
48
+ packages: declarePackages(environment.packages),
49
+ aptPackages: declareAptPackages(environment.aptPackages),
50
+ variables: declareVariables(environment.variables),
51
+ };
52
+ }
53
+ function declareVariables(raw) {
54
+ if (raw === undefined)
55
+ return {};
56
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
57
+ throw new Error(`environment: "variables" must be an object of variables by name`);
58
+ }
59
+ for (const [name, variable] of Object.entries(raw)) {
60
+ const at = `environment: variable ${JSON.stringify(name)}`;
61
+ if (!isEnvName(name))
62
+ throw new Error(`${at} is not an environment variable name`);
63
+ if (isDeployOwned(name)) {
64
+ throw new Error(`${at} is set by the deployment itself, so it cannot be declared: a value file's copy never travels`);
65
+ }
66
+ if (!variable || typeof variable !== "object" || Array.isArray(variable)) {
67
+ throw new Error(`${at} must be { "description": "what it is for" }: its value goes in the stage's value file ` +
68
+ `(.secrets/.env, .secrets/production/.env) or the host, never in context.json`);
69
+ }
70
+ for (const key of Object.keys(variable)) {
71
+ if (key !== "description")
72
+ throw new Error(`${at}: unknown key "${key}" (valid keys: description)`);
73
+ }
74
+ const description = variable.description;
75
+ if (typeof description !== "string" || description.trim() === "") {
76
+ throw new Error(`${at}: "description" must be a non-empty sentence saying what it is for`);
77
+ }
78
+ // Written verbatim into each host's runbook, as a comment: a line break would end the comment and start a command.
79
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: the control characters are what is refused
80
+ if (/[\u0000-\u001f\u007f]/.test(description)) {
81
+ throw new Error(`${at}: "description" must be one line of text, without control characters`);
82
+ }
83
+ }
84
+ return Object.fromEntries(Object.entries(raw).map(([name, { description }]) => [name, { description }]));
85
+ }
86
+ /** The variables `environment` declares, as the declarations every serving path and `deploy` require. */
87
+ export function declaredVariables(environment) {
88
+ return Object.entries(environment?.variables ?? {}).map(([name, { description }]) => ({
89
+ name,
90
+ source: CONTEXT_FILE,
91
+ description,
92
+ }));
93
+ }
94
+ function declarePackages(raw) {
95
+ if (raw === undefined)
96
+ return {};
97
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
98
+ throw new Error(`environment: "packages" must be an object of versions by package`);
99
+ }
100
+ for (const [name, version] of Object.entries(raw)) {
101
+ const refused = packageNameError(name);
102
+ if (refused)
103
+ throw new Error(`environment: package "${name}" ${refused}`);
104
+ if (typeof version !== "string")
105
+ throw new Error(`environment: package "${name}": the version must be a string`);
106
+ const badVersion = versionError(version);
107
+ if (badVersion)
108
+ throw new Error(`environment: package "${name}": ${badVersion}`);
109
+ }
110
+ return { ...raw };
111
+ }
112
+ function declareAptPackages(raw) {
113
+ if (raw === undefined)
114
+ return [];
115
+ if (!Array.isArray(raw))
116
+ throw new Error(`environment: "aptPackages" must be an array of Debian package names`);
117
+ const seen = new Set();
118
+ for (const name of raw) {
119
+ if (typeof name !== "string" || !APT_NAME.test(name)) {
120
+ throw new Error(`environment: apt package ${JSON.stringify(name)} is not a Debian package name`);
121
+ }
122
+ if (seen.has(name))
123
+ throw new Error(`environment: apt package "${name}" is listed twice`);
124
+ seen.add(name);
125
+ }
126
+ return [...raw];
127
+ }
128
+ /** Why `name` cannot name a package, as the rest of a sentence that starts with it; undefined when it can. */
129
+ function packageNameError(name) {
130
+ if (name.includes("["))
131
+ return "has mise's [options], which the format does not hold";
132
+ const colon = name.indexOf(":");
133
+ if (colon === -1)
134
+ return REGISTRY_NAME.test(name) ? undefined : "is not a name from mise's registry";
135
+ const backend = name.slice(0, colon);
136
+ if (backend === "http") {
137
+ return "is written with http:, whose package needs a URL a version cannot hold — a registry name that installs from a URL carries its own";
138
+ }
139
+ if (!WRITTEN_BACKENDS.includes(backend)) {
140
+ return `installs with ${backend}:, which ${refusalReason(backend)}`;
141
+ }
142
+ return BACKEND_NAME.test(name.slice(colon + 1)) ? undefined : `does not name a package after "${backend}:"`;
143
+ }
144
+ /** Why `version` is not one, or undefined. */
145
+ function versionError(version) {
146
+ if (version === "" || /\s/.test(version))
147
+ return "the version must be non-empty, without spaces";
148
+ // ref:, path:, prefix:, sub-…: build from source, read this machine, or say what a version already says.
149
+ if (version.includes(":") || version === "system") {
150
+ return `version "${version}" — a version is "latest", a prefix ("2") or an exact version ("2.1.0")`;
151
+ }
152
+ return undefined;
153
+ }
154
+ /** Why FastAgent does not install from `backend`, as a clause after "which", with what to do instead. */
155
+ function refusalReason(backend) {
156
+ const reason = LEFT_OUT[backend] ??
157
+ `FastAgent does not install from (a package is a registry name, or written with ` +
158
+ `${WRITTEN_BACKENDS.map((b) => `${b}:`).join(", ")})`;
159
+ return `${reason} — ${ALTERNATIVE}`;
160
+ }
161
+ /** The backend a package is written with (`npm:prettier` is npm), or undefined for a registry name, which mise resolves. */
162
+ export function writtenBackend(name) {
163
+ const colon = name.indexOf(":");
164
+ return colon === -1 ? undefined : name.slice(0, colon);
165
+ }
166
+ /**
167
+ * Why the registry name `name`, which mise resolves to `backend` (`cargo:tokei`), is refused; undefined when that
168
+ * backend is allowed.
169
+ */
170
+ export function resolvedBackendRefusal(name, backend) {
171
+ const kind = backend.split(":")[0] ?? backend;
172
+ if (RESOLVED_BACKENDS.has(kind))
173
+ return undefined;
174
+ return `package "${name}" resolves to ${backend}, which ${refusalReason(kind)}`;
175
+ }
176
+ /**
177
+ * The PyPI packages, written `pypi:` or resolved to it (`backends`, by package), when the environment does not declare
178
+ * both `python` and `uv`: mise takes them from the machine and never adds them, so such a package installs on an
179
+ * author's machine that has them and fails in the image.
180
+ */
181
+ export function missingPythonRuntime(packages, backends) {
182
+ const pypi = Object.keys(packages).filter((name) => backends.get(name) === "pypi");
183
+ const missing = ["python", "uv"].filter((tool) => !declaresTool(packages, tool));
184
+ return pypi.length > 0 && missing.length > 0 ? { packages: pypi, missing } : undefined;
185
+ }
186
+ /** What to tell whoever declared them, with the command that declares the rest. */
187
+ export function runtimeAdvice({ packages, missing }) {
188
+ return (`${packages.join(", ")} ${packages.length === 1 ? "installs" : "install"} with ${missing.join(" and ")}, which ` +
189
+ `the environment does not declare: mise then uses this machine's, and the image has none — run ` +
190
+ `\`fastagent env add ${missing.join(" ")}\``);
191
+ }
192
+ /** Whether `packages` declares `tool` under any backend or owner: `aqua:astral-sh/uv` is `uv`. */
193
+ export function declaresTool(packages, tool) {
194
+ return Object.keys(packages).some((name) => name
195
+ .slice(name.indexOf(":") + 1)
196
+ .split("/")
197
+ .at(-1) === tool);
198
+ }
199
+ /**
200
+ * One argument of `fastagent env add`: `gh`, `gh@2`, `npm:@scope/tool@1`, or `apt:chromium` for an apt package. The
201
+ * version is what follows the last `@` after the backend, `latest` without one.
202
+ */
203
+ export function parsePackageArgument(arg) {
204
+ if (arg.startsWith("apt:"))
205
+ return { apt: arg.slice("apt:".length) };
206
+ const at = arg.lastIndexOf("@");
207
+ if (at > arg.indexOf(":") + 1)
208
+ return { name: arg.slice(0, at), version: arg.slice(at + 1) };
209
+ return { name: arg, version: "latest" };
210
+ }