@fastagent-sh/fastagent 0.28.0 → 0.30.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 (325) hide show
  1. package/README.md +10 -4
  2. package/dist/atomic-write.d.ts +4 -5
  3. package/dist/atomic-write.js +4 -5
  4. package/dist/authoring.d.ts +32 -15
  5. package/dist/authoring.js +147 -127
  6. package/dist/channels/agentcore-protocol.d.ts +8 -3
  7. package/dist/channels/agentcore-protocol.js +3 -1
  8. package/dist/channels/agentcore-service.d.ts +1 -0
  9. package/dist/channels/agentcore-service.js +14 -4
  10. package/dist/channels/agentcore.d.ts +9 -0
  11. package/dist/channels/agentcore.js +29 -4
  12. package/dist/channels/context.d.ts +2 -0
  13. package/dist/channels/context.js +5 -1
  14. package/dist/channels/feishu/feishu-api.d.ts +3 -1
  15. package/dist/channels/feishu/feishu-api.js +4 -0
  16. package/dist/channels/feishu/feishu.d.ts +0 -3
  17. package/dist/channels/feishu/feishu.js +10 -10
  18. package/dist/channels/feishu/register-app.d.ts +2 -0
  19. package/dist/channels/feishu/register-app.js +4 -6
  20. package/dist/channels/feishu/scaffold/channel.ts +32 -34
  21. package/dist/channels/feishu/scaffold/feishu-send.ts +5 -5
  22. package/dist/channels/feishu/scaffold/feishu-threads.ts +3 -3
  23. package/dist/channels/feishu/setup-mode.d.ts +2 -12
  24. package/dist/channels/feishu/setup-mode.js +2 -22
  25. package/dist/channels/feishu/setup.d.ts +12 -0
  26. package/dist/channels/feishu/setup.js +197 -0
  27. package/dist/channels/kit/abortable.d.ts +8 -0
  28. package/dist/channels/kit/abortable.js +22 -0
  29. package/dist/channels/lark/lark.d.ts +0 -3
  30. package/dist/channels/lark/lark.js +0 -5
  31. package/dist/channels/lark/scaffold/channel.ts +33 -35
  32. package/dist/channels/lark/scaffold/lark-send.ts +5 -5
  33. package/dist/channels/lark/scaffold/lark-threads.ts +3 -3
  34. package/dist/channels/lark/setup.d.ts +9 -0
  35. package/dist/channels/lark/setup.js +57 -0
  36. package/dist/channels/setup.d.ts +112 -0
  37. package/dist/channels/setup.js +31 -0
  38. package/dist/channels/slack/history.js +1 -1
  39. package/dist/channels/slack/manifest.d.ts +17 -7
  40. package/dist/channels/slack/manifest.js +12 -16
  41. package/dist/channels/slack/scaffold/channel.ts +44 -30
  42. package/dist/channels/slack/scaffold/slack-send.ts +8 -8
  43. package/dist/channels/slack/scaffold/slack-threads.ts +4 -4
  44. package/dist/channels/slack/setup.d.ts +7 -0
  45. package/dist/channels/slack/setup.js +119 -0
  46. package/dist/channels/slack/slack-api.d.ts +2 -0
  47. package/dist/channels/slack/slack-api.js +4 -0
  48. package/dist/channels/slack/slack.d.ts +21 -2
  49. package/dist/channels/slack/slack.js +398 -338
  50. package/dist/channels/slack/socket.d.ts +57 -0
  51. package/dist/channels/slack/socket.js +305 -0
  52. package/dist/channels/telegram/polling.d.ts +27 -0
  53. package/dist/channels/telegram/polling.js +105 -0
  54. package/dist/channels/telegram/scaffold/channel.ts +52 -32
  55. package/dist/channels/telegram/scaffold/telegram-send.ts +9 -9
  56. package/dist/channels/telegram/setup.d.ts +2 -0
  57. package/dist/channels/telegram/setup.js +42 -0
  58. package/dist/channels/telegram/telegram-api.d.ts +27 -1
  59. package/dist/channels/telegram/telegram-api.js +7 -6
  60. package/dist/channels/telegram/telegram.d.ts +11 -3
  61. package/dist/channels/telegram/telegram.js +184 -157
  62. package/dist/channels/wait-health.js +1 -1
  63. package/dist/channels/webhook-evidence.d.ts +11 -0
  64. package/dist/channels/webhook-evidence.js +71 -0
  65. package/dist/channels/webhooks.d.ts +37 -0
  66. package/dist/channels/webhooks.js +70 -0
  67. package/dist/child-env.d.ts +16 -0
  68. package/dist/child-env.js +30 -0
  69. package/dist/cli/box-login.js +4 -1
  70. package/dist/cli/channel-app.d.ts +3 -0
  71. package/dist/cli/channel-app.js +35 -0
  72. package/dist/cli/channel-setup.d.ts +38 -0
  73. package/dist/cli/channel-setup.js +195 -0
  74. package/dist/cli/commands/add.d.ts +6 -14
  75. package/dist/cli/commands/add.js +45 -310
  76. package/dist/cli/commands/auth.js +8 -2
  77. package/dist/cli/commands/build.d.ts +5 -0
  78. package/dist/cli/commands/build.js +16 -0
  79. package/dist/cli/commands/channels.d.ts +19 -0
  80. package/dist/cli/commands/channels.js +230 -0
  81. package/dist/cli/commands/content.d.ts +7 -3
  82. package/dist/cli/commands/content.js +16 -10
  83. package/dist/cli/commands/deploy/agentcore.js +55 -23
  84. package/dist/cli/commands/deploy/channel-apps.d.ts +17 -0
  85. package/dist/cli/commands/deploy/channel-apps.js +30 -0
  86. package/dist/cli/commands/deploy/docker.js +32 -65
  87. package/dist/cli/commands/deploy/fly.js +28 -12
  88. package/dist/cli/commands/deploy/railway.js +38 -13
  89. package/dist/cli/commands/deploy/secrets-step.d.ts +19 -0
  90. package/dist/cli/commands/deploy/secrets-step.js +66 -0
  91. package/dist/cli/commands/deploy/shared.d.ts +16 -5
  92. package/dist/cli/commands/deploy/shared.js +25 -8
  93. package/dist/cli/commands/deploy.js +53 -67
  94. package/dist/cli/commands/dev.d.ts +2 -0
  95. package/dist/cli/commands/dev.js +15 -3
  96. package/dist/cli/commands/env.js +1 -1
  97. package/dist/cli/commands/info.d.ts +2 -0
  98. package/dist/cli/commands/info.js +63 -24
  99. package/dist/cli/commands/init.js +3 -1
  100. package/dist/cli/commands/invoke.d.ts +2 -0
  101. package/dist/cli/commands/invoke.js +20 -7
  102. package/dist/cli/commands/login.d.ts +0 -7
  103. package/dist/cli/commands/login.js +22 -10
  104. package/dist/cli/commands/logout.js +1 -1
  105. package/dist/cli/commands/mcp.d.ts +12 -0
  106. package/dist/cli/commands/mcp.js +164 -0
  107. package/dist/cli/commands/models.js +3 -2
  108. package/dist/cli/commands/schedules.d.ts +4 -2
  109. package/dist/cli/commands/schedules.js +14 -10
  110. package/dist/cli/commands/secrets.d.ts +14 -0
  111. package/dist/cli/commands/secrets.js +213 -0
  112. package/dist/cli/commands/start.d.ts +14 -14
  113. package/dist/cli/commands/start.js +46 -60
  114. package/dist/cli/content-view.d.ts +4 -6
  115. package/dist/cli/content-view.js +5 -11
  116. package/dist/cli/first-run-channels.d.ts +3 -0
  117. package/dist/cli/first-run-channels.js +63 -0
  118. package/dist/cli/instance.d.ts +41 -0
  119. package/dist/cli/instance.js +155 -0
  120. package/dist/cli/invoke-stream.d.ts +5 -2
  121. package/dist/cli/invoke-stream.js +7 -2
  122. package/dist/cli/program.js +339 -108
  123. package/dist/cli/serve.d.ts +4 -5
  124. package/dist/cli/serve.js +4 -7
  125. package/dist/cli/shared.d.ts +11 -2
  126. package/dist/cli/shared.js +32 -19
  127. package/dist/cli/ticket.js +50 -6
  128. package/dist/content/declare.d.ts +31 -37
  129. package/dist/content/declare.js +59 -60
  130. package/dist/content/file.d.ts +23 -12
  131. package/dist/content/file.js +48 -35
  132. package/dist/content/git.d.ts +0 -10
  133. package/dist/content/git.js +5 -39
  134. package/dist/content/resolve.d.ts +18 -22
  135. package/dist/content/resolve.js +32 -66
  136. package/dist/content/source.d.ts +7 -8
  137. package/dist/content/source.js +10 -28
  138. package/dist/credentials-page.d.ts +26 -0
  139. package/dist/credentials-page.js +185 -0
  140. package/dist/declared-secrets.d.ts +10 -3
  141. package/dist/declared-secrets.js +8 -4
  142. package/dist/deploy/agentcore/destroy.d.ts +4 -1
  143. package/dist/deploy/agentcore/destroy.js +26 -2
  144. package/dist/deploy/agentcore/forwarder.js +1 -1
  145. package/dist/deploy/agentcore/plan.d.ts +7 -21
  146. package/dist/deploy/agentcore/plan.js +47 -49
  147. package/dist/deploy/agentcore/run.d.ts +18 -10
  148. package/dist/deploy/agentcore/run.js +93 -70
  149. package/dist/deploy/agentcore/runtime-secrets.d.ts +7 -0
  150. package/dist/deploy/agentcore/runtime-secrets.js +108 -0
  151. package/dist/deploy/agentcore/shell.js +12 -19
  152. package/dist/deploy/agentcore/sigv4.d.ts +7 -0
  153. package/dist/deploy/agentcore/sigv4.js +15 -0
  154. package/dist/deploy/agentcore/slr.d.ts +18 -0
  155. package/dist/deploy/agentcore/slr.js +27 -0
  156. package/dist/deploy/agentcore/store.d.ts +7 -0
  157. package/dist/deploy/agentcore/store.js +115 -0
  158. package/dist/deploy/api.d.ts +14 -14
  159. package/dist/deploy/api.js +26 -24
  160. package/dist/deploy/box-shell.d.ts +3 -1
  161. package/dist/deploy/build-context.d.ts +4 -4
  162. package/dist/deploy/build-context.js +11 -11
  163. package/dist/deploy/channel-ingress.d.ts +35 -9
  164. package/dist/deploy/channel-ingress.js +119 -22
  165. package/dist/deploy/container.d.ts +35 -10
  166. package/dist/deploy/container.js +80 -39
  167. package/dist/deploy/delivered-text.d.ts +13 -0
  168. package/dist/deploy/delivered-text.js +86 -0
  169. package/dist/deploy/docker/plan.d.ts +7 -8
  170. package/dist/deploy/docker/plan.js +31 -52
  171. package/dist/deploy/docker/run.d.ts +18 -43
  172. package/dist/deploy/docker/run.js +57 -79
  173. package/dist/deploy/file-store.d.ts +2 -0
  174. package/dist/deploy/file-store.js +17 -0
  175. package/dist/deploy/fly/plan.d.ts +3 -2
  176. package/dist/deploy/fly/plan.js +20 -9
  177. package/dist/deploy/fly/run.d.ts +18 -22
  178. package/dist/deploy/fly/run.js +25 -44
  179. package/dist/deploy/fly/store.d.ts +12 -0
  180. package/dist/deploy/fly/store.js +83 -0
  181. package/dist/deploy/hosts.d.ts +1 -2
  182. package/dist/deploy/hosts.js +1 -2
  183. package/dist/deploy/preflight.d.ts +18 -5
  184. package/dist/deploy/preflight.js +135 -63
  185. package/dist/deploy/railway/ignore.d.ts +8 -0
  186. package/dist/deploy/railway/ignore.js +57 -0
  187. package/dist/deploy/railway/plan.d.ts +1 -2
  188. package/dist/deploy/railway/plan.js +8 -6
  189. package/dist/deploy/railway/run.d.ts +21 -15
  190. package/dist/deploy/railway/run.js +32 -78
  191. package/dist/deploy/railway/store.d.ts +29 -0
  192. package/dist/deploy/railway/store.js +143 -0
  193. package/dist/deploy/runner.d.ts +5 -2
  194. package/dist/deploy/runner.js +4 -3
  195. package/dist/deploy/secret-store.d.ts +63 -0
  196. package/dist/deploy/secret-store.js +98 -0
  197. package/dist/deploy/secrets.d.ts +29 -46
  198. package/dist/deploy/secrets.js +49 -122
  199. package/dist/deploy/workspace.d.ts +6 -12
  200. package/dist/deploy/workspace.js +18 -94
  201. package/dist/dev-supervisor.js +6 -7
  202. package/dist/environment/declare.js +3 -2
  203. package/dist/environment/mise.d.ts +3 -1
  204. package/dist/environment/mise.js +4 -2
  205. package/dist/feishu.d.ts +1 -1
  206. package/dist/feishu.js +1 -1
  207. package/dist/harnesses/models/catalog.d.ts +1 -0
  208. package/dist/harnesses/models/catalog.js +1 -2
  209. package/dist/harnesses/models/environment.d.ts +7 -2
  210. package/dist/harnesses/models/environment.js +9 -5
  211. package/dist/harnesses/models/host.d.ts +0 -7
  212. package/dist/harnesses/models/host.js +10 -1
  213. package/dist/harnesses/models/login.d.ts +0 -8
  214. package/dist/harnesses/models/login.js +5 -4
  215. package/dist/harnesses/models/models.d.ts +7 -2
  216. package/dist/harnesses/models/models.js +28 -4
  217. package/dist/harnesses/pi-durable/agent.js +59 -38
  218. package/dist/harnesses/pi-durable/build.d.ts +12 -0
  219. package/dist/harnesses/pi-durable/build.js +270 -0
  220. package/dist/harnesses/pi-durable/built.d.ts +24 -0
  221. package/dist/harnesses/pi-durable/built.js +136 -0
  222. package/dist/harnesses/pi-durable/config.d.ts +81 -0
  223. package/dist/harnesses/pi-durable/config.js +266 -0
  224. package/dist/harnesses/pi-durable/define.d.ts +63 -13
  225. package/dist/harnesses/pi-durable/define.js +23 -2
  226. package/dist/harnesses/pi-durable/definition.d.ts +72 -0
  227. package/dist/harnesses/pi-durable/definition.js +143 -0
  228. package/dist/harnesses/pi-durable/events.d.ts +4 -3
  229. package/dist/harnesses/pi-durable/events.js +179 -136
  230. package/dist/harnesses/pi-durable/execution-env.d.ts +17 -0
  231. package/dist/harnesses/pi-durable/execution-env.js +11 -0
  232. package/dist/harnesses/pi-durable/mcp.d.ts +3 -0
  233. package/dist/harnesses/pi-durable/mcp.js +32 -0
  234. package/dist/harnesses/pi-durable/open.d.ts +36 -38
  235. package/dist/harnesses/pi-durable/open.js +97 -150
  236. package/dist/harnesses/pi-durable/reads.d.ts +0 -1
  237. package/dist/harnesses/pi-durable/reads.js +1 -3
  238. package/dist/harnesses/pi-durable/registry.d.ts +4 -6
  239. package/dist/harnesses/pi-durable/registry.js +29 -26
  240. package/dist/harnesses/pi-durable/release.d.ts +2 -2
  241. package/dist/harnesses/pi-durable/release.js +10 -7
  242. package/dist/harnesses/pi-durable/serve.d.ts +95 -15
  243. package/dist/harnesses/pi-durable/serve.js +151 -59
  244. package/dist/harnesses/pi-durable/sessions.js +15 -12
  245. package/dist/harnesses/pi-durable/text-sources.d.ts +30 -0
  246. package/dist/harnesses/pi-durable/text-sources.js +55 -0
  247. package/dist/harnesses/pi-durable/text.d.ts +16 -4
  248. package/dist/harnesses/pi-durable/text.js +115 -59
  249. package/dist/harnesses/pi-durable/wake.js +8 -8
  250. package/dist/http/client.js +9 -3
  251. package/dist/http/server.d.ts +1 -1
  252. package/dist/http/server.js +1 -1
  253. package/dist/index.d.ts +11 -4
  254. package/dist/index.js +8 -1
  255. package/dist/instance-name.d.ts +7 -0
  256. package/dist/instance-name.js +26 -0
  257. package/dist/lark.d.ts +1 -1
  258. package/dist/lark.js +1 -1
  259. package/dist/locked-file.d.ts +2 -2
  260. package/dist/locked-file.js +8 -3
  261. package/dist/mcp/auth.d.ts +22 -0
  262. package/dist/mcp/auth.js +180 -0
  263. package/dist/mcp/client.d.ts +15 -0
  264. package/dist/mcp/client.js +179 -0
  265. package/dist/mcp/config.d.ts +86 -0
  266. package/dist/mcp/config.js +225 -0
  267. package/dist/mcp/grants.d.ts +4 -0
  268. package/dist/mcp/grants.js +132 -0
  269. package/dist/mcp/host.d.ts +19 -0
  270. package/dist/mcp/host.js +128 -0
  271. package/dist/mcp/network.d.ts +12 -0
  272. package/dist/mcp/network.js +150 -0
  273. package/dist/mcp/sign-in.d.ts +2 -0
  274. package/dist/mcp/sign-in.js +204 -0
  275. package/dist/mcp-api.d.ts +107 -0
  276. package/dist/mcp-api.js +10 -0
  277. package/dist/paths.d.ts +22 -8
  278. package/dist/paths.js +44 -11
  279. package/dist/runtime-websocket.d.ts +14 -0
  280. package/dist/runtime-websocket.js +39 -0
  281. package/dist/scaffold/add-channel.d.ts +9 -16
  282. package/dist/scaffold/add-channel.js +13 -83
  283. package/dist/scaffold/templates/env.example +4 -5
  284. package/dist/scaffold/templates/fastagent.config.ts +20 -14
  285. package/dist/scaffold/templates/gitignore +9 -6
  286. package/dist/scaffold/templates.d.ts +6 -2
  287. package/dist/scaffold/templates.js +7 -2
  288. package/dist/scaffold/vendor-skill.js +4 -2
  289. package/dist/service.d.ts +44 -6
  290. package/dist/service.js +110 -10
  291. package/dist/serving-command.d.ts +10 -0
  292. package/dist/serving-command.js +1 -0
  293. package/dist/setup-page.d.ts +51 -0
  294. package/dist/setup-page.js +149 -0
  295. package/dist/slack.d.ts +1 -1
  296. package/dist/slack.js +1 -1
  297. package/dist/strip-edges.d.ts +10 -0
  298. package/dist/strip-edges.js +22 -0
  299. package/dist/telegram.d.ts +1 -1
  300. package/dist/telegram.js +1 -1
  301. package/dist/tunnel.d.ts +0 -1
  302. package/dist/tunnel.js +2 -8
  303. package/dist/value-file.d.ts +12 -6
  304. package/dist/value-file.js +36 -25
  305. package/dist/version.d.ts +7 -1
  306. package/dist/version.js +9 -3
  307. package/package.json +8 -6
  308. package/dist/channels/lark/onboard.d.ts +0 -36
  309. package/dist/channels/lark/onboard.js +0 -56
  310. package/dist/channels/slack/config-api.d.ts +0 -69
  311. package/dist/channels/slack/config-api.js +0 -162
  312. package/dist/channels/slack/onboard.d.ts +0 -40
  313. package/dist/channels/slack/onboard.js +0 -136
  314. package/dist/channels/slack/onboarding-state.d.ts +0 -31
  315. package/dist/channels/slack/onboarding-state.js +0 -64
  316. package/dist/channels/slack/register-webhook.d.ts +0 -12
  317. package/dist/channels/slack/register-webhook.js +0 -67
  318. package/dist/channels/slack/setup-server.d.ts +0 -32
  319. package/dist/channels/slack/setup-server.js +0 -201
  320. package/dist/cli/add-feishu.d.ts +0 -50
  321. package/dist/cli/add-feishu.js +0 -367
  322. package/dist/cli/add-slack.d.ts +0 -18
  323. package/dist/cli/add-slack.js +0 -216
  324. package/dist/harnesses/pi-durable/parked.d.ts +0 -18
  325. package/dist/harnesses/pi-durable/parked.js +0 -27
@@ -1,17 +1,38 @@
1
- /** What every deploy writes whoever wrote it before: build output, by its file name, never kept or reported stale. */
1
+ /**
2
+ * What a deploy builds the image from beyond the program, in the build context: the instance configuration it delivers
3
+ * and the text that configuration names, the release manifest and the package list. FastAgent's own directory, so it is
4
+ * ignored by git and absent from the author's root, and the image copies it to {@link IMAGE_CONFIG_DIR}. A directory of
5
+ * its own, never a file in the program's: what an instance configuration names beside it (its text) is
6
+ * read from its own directory, and the program's directory holds the program's.
7
+ */
8
+ export declare const DELIVERED_CONFIG_DIR = ".fastagent/deploy";
9
+ /**
10
+ * The configuration a deploy delivers, with the public URL the host gives the box as its `http.publicUrl`, where the
11
+ * operator's configuration names none (a custom domain is theirs to name): the URL the box points its channels at.
12
+ */
13
+ export declare function withPublicUrl(config: string | undefined, publicUrl: string): {
14
+ config: string;
15
+ publicUrl: string;
16
+ };
17
+ /** The `http.publicUrl` an operator's configuration names, if it does. */
18
+ export declare function ownPublicUrl(config: string | undefined): string | undefined;
19
+ /** The delivered configuration as the artifact a deploy writes, to rewrite it once the host has given the URL. */
20
+ export declare function deliveredConfigArtifact(config: string): Artifact;
21
+ /** What every deploy writes whoever wrote it before: build output, by its path, never kept or reported stale. */
2
22
  export declare const BUILD_OUTPUTS: readonly string[];
3
23
  export interface Artifact {
4
24
  path: string;
5
25
  content: string;
6
26
  }
7
- /** Where a generated image holds the definition: the build context, which is the agent directory. */
8
- export declare const IMAGE_DEFINITION_DIR = "/app/definition";
27
+ /** Where a generated image holds the agent: the build context, which is the agent directory. */
28
+ export declare const IMAGE_APP_DIR = "/app";
9
29
  /**
10
30
  * The shell command that runs `fastagent login --stdio <args>` inside a running generated image, as the server there
11
- * would see it: in the deployed agent directory, with the storage and credential paths resolved by the same defaults
31
+ * would see it: in the image's agent directory, with the storage and credential paths resolved by the same defaults
12
32
  * `prepareStartWorkspace` applies. A host's own shell does not reliably hand those over — its session may not start
13
33
  * in the server's directory, and the platform's variables are the only ones it inherits. `storage` names the root
14
- * outright for a shell that inherits none of them. The CLI is the one the server runs: the agent's own install when
34
+ * outright for a shell that inherits none of them; `program` is where the image has the agent, the generated one's
35
+ * by default. The CLI is the one the server runs: the agent's own install when
15
36
  * it has one (under Bun when the image is Bun's), else the pinned global.
16
37
  *
17
38
  * That own install may predate `login --stdio` (an agent pinned to an older release, deployed by a newer CLI). It then
@@ -19,7 +40,10 @@ export declare const IMAGE_DEFINITION_DIR = "/app/definition";
19
40
  * result line), so the command answers for it: one result line naming the mismatch, instead of a shell that ended
20
41
  * with nothing to say. No quote or backtick in it: hosts wrap this whole line in `sh -c '…'`.
21
42
  */
22
- export declare function boxLoginCommand(args: readonly string[], storage?: string): string;
43
+ export declare function boxLoginCommand(args: readonly string[], { storage, program }?: {
44
+ storage?: string;
45
+ program?: string;
46
+ }): string;
23
47
  /** Fixed first-line prefix of a fastagent-generated Dockerfile. */
24
48
  export declare const GENERATED_DOCKERFILE_MARKER = "# Generated by `fastagent deploy`";
25
49
  /** The ONE predicate for "did fastagent generate this Dockerfile?". */
@@ -28,16 +52,12 @@ export declare function isGeneratedDockerignore(content: string): boolean;
28
52
  export interface ContainerInput {
29
53
  /** Stable for one release; generated once by the deploy preflight, never on container restart. */
30
54
  releaseId: string;
31
- /** Whether the agent has a package.json (a code agent); else a pure markdown/skills agent. */
32
- hasPackageJson: boolean;
33
55
  /** The package manager the generated image targets. */
34
56
  runtime: "node" | "bun";
35
57
  /** Whether the runtime's lockfile exists (package-lock.json for npm, bun.lock/bun.lockb for bun). */
36
58
  hasLockfile: boolean;
37
59
  /** For `runtime: "bun"`, the `FROM oven/bun:<tag>` version (from packageManager `bun@x`), else `"1"`. */
38
60
  bunVersion?: string;
39
- /** This fastagent version, to PIN the global install on the markdown path (reproducible redeploys). */
40
- version: string;
41
61
  /**
42
62
  * The Debian packages the image installs: what FastAgent needs (git for content and a shipped `.git`, CA certificates
43
63
  * and curl for mise), then the agent's `aptPackages`.
@@ -52,6 +72,11 @@ export interface ContainerInput {
52
72
  * interpolate the operator's shell.
53
73
  */
54
74
  modelSpec?: string;
75
+ /**
76
+ * The instance configuration the deployment delivers (`deploy --config`), as the file's text: the image copies it to
77
+ * {@link IMAGE_CONFIG_DIR}, and `start` reads it through `FASTAGENT_CONFIG`. Without one, `{}`: the program's defaults.
78
+ */
79
+ config?: string;
55
80
  /** The agent's name, recorded in the release manifest: the agent directory's own name. */
56
81
  agent: string;
57
82
  /** Agent-dir-relative machinery paths the NAME-based excludes below would miss. */
@@ -1,16 +1,48 @@
1
1
  /** The portable container — Dockerfile + .dockerignore, host-neutral. */
2
- import { CONTENT_DIRNAME, LOCAL_CONTEXT_FILE, SECRETS_DIRNAME, STATE_DIRNAME } from "../paths.js";
2
+ import { CONTENT_DIRNAME, INSTANCE_CONFIG_FILE, INSTANCES_DIRNAME, SECRETS_DIRNAME, STATE_DIRNAME } from "../paths.js";
3
3
  import { RUNTIME_FLOOR } from "../runtime.js";
4
- import { DEPLOYED_DEFINITION_DIR, RELEASE_FILE, parseDeploymentRelease } from "./workspace.js";
4
+ import { RELEASE_FILE, parseDeploymentRelease } from "./workspace.js";
5
5
  import { MISE_ENVIRONMENT } from "../environment/mise.js";
6
6
  import { BOX_CLI_PREDATES } from "./box-shell.js";
7
+ /**
8
+ * What a deploy builds the image from beyond the program, in the build context: the instance configuration it delivers
9
+ * and the text that configuration names, the release manifest and the package list. FastAgent's own directory, so it is
10
+ * ignored by git and absent from the author's root, and the image copies it to {@link IMAGE_CONFIG_DIR}. A directory of
11
+ * its own, never a file in the program's: what an instance configuration names beside it (its text) is
12
+ * read from its own directory, and the program's directory holds the program's.
13
+ */
14
+ export const DELIVERED_CONFIG_DIR = `${INSTANCES_DIRNAME}/deploy`;
15
+ const DELIVERED_CONFIG_FILE = `${DELIVERED_CONFIG_DIR}/${INSTANCE_CONFIG_FILE}`;
16
+ const RELEASE_PATH = `${DELIVERED_CONFIG_DIR}/${RELEASE_FILE}`;
7
17
  /**
8
18
  * The packages the image installs, one per line as mise is given them: build output every deploy writes beside the
9
19
  * release manifest, so the layer that installs them is built again when they change and not when anything else does.
10
20
  */
11
21
  const PACKAGES_FILE = "fastagent.packages";
12
- /** What every deploy writes whoever wrote it before: build output, by its file name, never kept or reported stale. */
13
- export const BUILD_OUTPUTS = [RELEASE_FILE, PACKAGES_FILE];
22
+ const PACKAGES_PATH = `${DELIVERED_CONFIG_DIR}/${PACKAGES_FILE}`;
23
+ /** Where a generated image has the instance configuration, read-only: the filesystem hierarchy's for configuration. */
24
+ const IMAGE_CONFIG_DIR = "/etc/fastagent";
25
+ /**
26
+ * The configuration a deploy delivers, with the public URL the host gives the box as its `http.publicUrl`, where the
27
+ * operator's configuration names none (a custom domain is theirs to name): the URL the box points its channels at.
28
+ */
29
+ export function withPublicUrl(config, publicUrl) {
30
+ const values = (config === undefined ? {} : JSON.parse(config));
31
+ const own = ownPublicUrl(config);
32
+ if (own !== undefined)
33
+ return { config: config, publicUrl: own };
34
+ return { config: `${JSON.stringify({ ...values, http: { ...values.http, publicUrl } }, null, 2)}\n`, publicUrl };
35
+ }
36
+ /** The `http.publicUrl` an operator's configuration names, if it does. */
37
+ export function ownPublicUrl(config) {
38
+ return config === undefined ? undefined : JSON.parse(config).http?.publicUrl;
39
+ }
40
+ /** The delivered configuration as the artifact a deploy writes, to rewrite it once the host has given the URL. */
41
+ export function deliveredConfigArtifact(config) {
42
+ return { path: DELIVERED_CONFIG_FILE, content: config };
43
+ }
44
+ /** What every deploy writes whoever wrote it before: build output, by its path, never kept or reported stale. */
45
+ export const BUILD_OUTPUTS = [RELEASE_PATH, PACKAGES_PATH, DELIVERED_CONFIG_FILE];
14
46
  /**
15
47
  * The result line for a box CLI that refused this CLI's `login --stdio` as a usage error: an option it does not know
16
48
  * (`--stdio` itself, or one added since, such as `--method`). See {@link boxLoginCommand}.
@@ -21,16 +53,17 @@ const STDIO_PREDATED = JSON.stringify({
21
53
  reason: "failed",
22
54
  message: BOX_CLI_PREDATES,
23
55
  }).replaceAll('"', '\\"');
24
- /** Where a generated image keeps its storage (`FASTAGENT_STORAGE_DIR`); every host mounts its volume here. */
56
+ /** Where a generated image keeps its storage (`FASTAGENT_ROOT`); every host mounts its volume here. */
25
57
  const STORAGE_DIR = "/data";
26
- /** Where a generated image holds the definition: the build context, which is the agent directory. */
27
- export const IMAGE_DEFINITION_DIR = "/app/definition";
58
+ /** Where a generated image holds the agent: the build context, which is the agent directory. */
59
+ export const IMAGE_APP_DIR = "/app";
28
60
  /**
29
61
  * The shell command that runs `fastagent login --stdio <args>` inside a running generated image, as the server there
30
- * would see it: in the deployed agent directory, with the storage and credential paths resolved by the same defaults
62
+ * would see it: in the image's agent directory, with the storage and credential paths resolved by the same defaults
31
63
  * `prepareStartWorkspace` applies. A host's own shell does not reliably hand those over — its session may not start
32
64
  * in the server's directory, and the platform's variables are the only ones it inherits. `storage` names the root
33
- * outright for a shell that inherits none of them. The CLI is the one the server runs: the agent's own install when
65
+ * outright for a shell that inherits none of them; `program` is where the image has the agent, the generated one's
66
+ * by default. The CLI is the one the server runs: the agent's own install when
34
67
  * it has one (under Bun when the image is Bun's), else the pinned global.
35
68
  *
36
69
  * That own install may predate `login --stdio` (an agent pinned to an older release, deployed by a newer CLI). It then
@@ -38,8 +71,8 @@ export const IMAGE_DEFINITION_DIR = "/app/definition";
38
71
  * result line), so the command answers for it: one result line naming the mismatch, instead of a shell that ended
39
72
  * with nothing to say. No quote or backtick in it: hosts wrap this whole line in `sh -c '…'`.
40
73
  */
41
- export function boxLoginCommand(args, storage) {
42
- for (const value of [...args, ...(storage ? [storage] : [])]) {
74
+ export function boxLoginCommand(args, { storage, program = IMAGE_APP_DIR } = {}) {
75
+ for (const value of [...args, ...(storage ? [storage] : []), program]) {
43
76
  if (!/^[\w.@/-]+$/.test(value))
44
77
  throw new Error(`cannot pass ${JSON.stringify(value)} to a shell on the box`);
45
78
  }
@@ -47,10 +80,10 @@ export function boxLoginCommand(args, storage) {
47
80
  ...(storage
48
81
  ? [`root="${storage}"`, `export FASTAGENT_SECRETS_DIR="$root/.secrets"`]
49
82
  : [
50
- `root="\${FASTAGENT_STORAGE_DIR:-${STORAGE_DIR}}"`,
83
+ `root="\${FASTAGENT_ROOT:-${STORAGE_DIR}}"`,
51
84
  `export FASTAGENT_SECRETS_DIR="\${FASTAGENT_SECRETS_DIR:-$root/.secrets}"`,
52
85
  ]),
53
- `cd "$root/${DEPLOYED_DEFINITION_DIR}" || exit 1`,
86
+ `cd "${program}" || exit 1`,
54
87
  `f=fastagent`,
55
88
  `if [ -x node_modules/.bin/fastagent ]; then f=node_modules/.bin/fastagent; if command -v bun >/dev/null; then f="bun run fastagent"; fi; fi`,
56
89
  `$f login --stdio ${args.join(" ")}`,
@@ -85,9 +118,11 @@ function aptLayer(packages) {
85
118
  }
86
119
  /** mise's own directories for the build, thrown away with the layer: nothing of them is read at run time. */
87
120
  const BUILD_MISE_DIR = "/tmp/mise";
121
+ /** Where the build holds the package list while it installs them: not the program's directory. */
122
+ const BUILD_PACKAGES = "/tmp/fastagent.packages";
88
123
  /**
89
124
  * The layer that installs the agent's packages, after its dependencies (the agent's own mise among them) and before the
90
- * definition, so it stays cached while the definition changes. It reads them from {@link PACKAGES_FILE}, which deploy
125
+ * definition, so it stays cached while the definition changes. It reads them from {@link PACKAGES_PATH}, which deploy
91
126
  * writes, rather than listing them: the Dockerfile is kept once written, and a list here would make every package
92
127
  * change one that `--run` stops on. The packages go to mise's system directory (`--system`,
93
128
  * /usr/local/share/mise/installs), which every mise reads beside its own data directory, so a start installs nothing.
@@ -101,10 +136,10 @@ function environmentLayer(dir) {
101
136
  const steps = [
102
137
  `export ${isolation} MISE_DATA_DIR=${BUILD_MISE_DIR} MISE_CACHE_DIR=${BUILD_MISE_DIR}/cache MISE_STATE_DIR=${BUILD_MISE_DIR}/state MISE="${mise}"`,
103
138
  // One argument per line, read as it is: no quote or backslash in a package means anything to xargs.
104
- `xargs -d '\\n' -a ${PACKAGES_FILE} "$MISE" install --system`,
139
+ `xargs -d '\\n' -a ${BUILD_PACKAGES} "$MISE" install --system`,
105
140
  `rm -rf ${BUILD_MISE_DIR}`,
106
141
  ];
107
- return `COPY ${PACKAGES_FILE} ./
142
+ return `COPY ${PACKAGES_PATH} ${BUILD_PACKAGES}
108
143
  RUN ${steps.join(" \\\n && ")}
109
144
  `;
110
145
  }
@@ -113,27 +148,26 @@ RUN ${steps.join(" \\\n && ")}
113
148
  * (docker/run.ts, agentcore/run.ts): a copy cached before the floor's release would start a Node the CLI refuses.
114
149
  */
115
150
  export const NODE_IMAGE = `node:${RUNTIME_FLOOR.node.split(".")[0]}-slim`;
151
+ /** Why the image builds `dist/` itself: an upload that follows `.gitignore` (`railway up`) drops it, and `start` needs it. */
152
+ const BUILD_STEP_NOTE = "# `start` opens dist/, which an upload that follows .gitignore leaves out: build it here.";
153
+ /**
154
+ * The instance configuration's layer, after the build: the directory
155
+ * the deploy wrote, read-only where the image's configuration goes, and named to `start`.
156
+ */
157
+ const CONFIG_LAYER = `# The instance configuration \`deploy --config\` delivers ({} without one: the program's defaults).
158
+ COPY ${DELIVERED_CONFIG_DIR}/ ${IMAGE_CONFIG_DIR}/
159
+ ENV FASTAGENT_CONFIG=${IMAGE_CONFIG_DIR}/${INSTANCE_CONFIG_FILE}
160
+ `;
116
161
  function dockerfile(input) {
117
- // The build context is the agent directory, baked at IMAGE_DEFINITION_DIR; deps install (and the local bin lives)
162
+ // The build context is the agent directory, baked at IMAGE_APP_DIR; deps install (and the local bin lives)
118
163
  // there.
119
- const dir = IMAGE_DEFINITION_DIR;
164
+ const dir = IMAGE_APP_DIR;
120
165
  const layoutNote = `The agent directory is baked at ${dir}`;
121
166
  // apt layer right after FROM (cached across code changes): the agent's tools may shell out to git etc., which the
122
167
  // slim image lacks.
123
168
  const apt = aptLayer(input.apt);
124
169
  // The caches stay off the volume: they are rebuildable, and a host mount is the slow disk.
125
- const deployment = `ENV FASTAGENT_RELEASE_FILE=${dir}/${RELEASE_FILE}\nENV FASTAGENT_STORAGE_DIR=${STORAGE_DIR}\nENV npm_config_cache=/tmp/fastagent/npm\nENV BUN_INSTALL_CACHE_DIR=/tmp/fastagent/bun\n`;
126
- // No package.json → pure markdown/skills agent: install the pinned CLI GLOBALLY and run `fastagent` from PATH.
127
- if (!input.hasPackageJson) {
128
- return `${GENERATED_DOCKERFILE_MARKER}. ${layoutNote}
129
- # npm-based (a markdown/skills agent installs the pinned CLI globally; ${NODE_IMAGE} has npm).
130
- FROM ${NODE_IMAGE}
131
- ${apt}WORKDIR ${dir}
132
- ${deployment}RUN npm i -g @fastagent-sh/fastagent@${input.version}
133
- COPY . .
134
- CMD ["fastagent", "start", "${dir}"]
135
- `;
136
- }
170
+ const deployment = `ENV FASTAGENT_RELEASE_FILE=${IMAGE_CONFIG_DIR}/${RELEASE_FILE}\nENV FASTAGENT_ROOT=${STORAGE_DIR}\nENV npm_config_cache=/tmp/fastagent/npm\nENV BUN_INSTALL_CACHE_DIR=/tmp/fastagent/bun\n`;
137
171
  const environment = input.packages && input.packages.length > 0 ? environmentLayer(dir) : "";
138
172
  const isBun = input.runtime === "bun";
139
173
  const base = isBun ? `oven/bun:${input.bunVersion ?? "1"}` : NODE_IMAGE;
@@ -149,15 +183,16 @@ ${deployment}`;
149
183
  //
150
184
  // `*.tgz` RIDES ALONG with the manifest, because the install layer runs BEFORE `COPY . .` and a
151
185
  // `"dep": "file:./something.tgz"` resolves against the directory it is installing in. Without this the file is
152
- // not there yet and the build fails on a dependency the author can see sitting in the agent directory. It is
153
- // also what lets a live probe deploy the CHECKOUT (`npm pack` output) rather than the published release.
186
+ // not there yet and the build fails on a dependency the author can see sitting in the agent directory.
154
187
  if (isBun) {
155
188
  // `--frozen-lockfile` needs bun.lock and hard-fails without it.
156
189
  const install = input.hasLockfile ? "bun install --frozen-lockfile" : "bun install";
157
190
  return `${head}COPY package.json bun.lock* *.tgz ./
158
191
  RUN ${install}
159
192
  ${environment}COPY . .
160
- CMD ["bun", "run", "fastagent", "start", "${dir}"]
193
+ ${BUILD_STEP_NOTE}
194
+ RUN bun run fastagent build
195
+ ${CONFIG_LAYER}CMD ["bun", "run", "fastagent", "start", "${dir}"]
161
196
  `;
162
197
  }
163
198
  // `npm ci` requires a lockfile and hard-fails without one (a common `init --no-install` agent); fall back to `npm
@@ -166,7 +201,9 @@ CMD ["bun", "run", "fastagent", "start", "${dir}"]
166
201
  return `${head}COPY package.json package-lock.json* *.tgz ./
167
202
  RUN ${install}
168
203
  ${environment}COPY . .
169
- CMD ["./node_modules/.bin/fastagent", "start", "${dir}"]
204
+ ${BUILD_STEP_NOTE}
205
+ RUN ./node_modules/.bin/fastagent build
206
+ ${CONFIG_LAYER}CMD ["./node_modules/.bin/fastagent", "start", "${dir}"]
170
207
  `;
171
208
  }
172
209
  /** Patterns are RECURSIVE (`**​/`) on purpose. */
@@ -180,8 +217,12 @@ const DOCKERIGNORE_BASE = `${GENERATED_DOCKERIGNORE_MARKER}. Delete this line to
180
217
  **/node_modules
181
218
  **/${SECRETS_DIRNAME}/**
182
219
  **/${STATE_DIRNAME}
220
+ /${INSTANCES_DIRNAME}
221
+ # what a deploy writes for the image: the delivered configuration and text, the release manifest, the package list
222
+ !/${DELIVERED_CONFIG_DIR}
183
223
  /${CONTENT_DIRNAME}
184
- /${LOCAL_CONTEXT_FILE}
224
+ # the project's own instance configuration: the development instance's, which a deploy does not carry
225
+ /${INSTANCE_CONFIG_FILE}
185
226
  **/.cache
186
227
  **/.env
187
228
  **/.env.*
@@ -203,12 +244,12 @@ export function containerArtifacts(input) {
203
244
  };
204
245
  parseDeploymentRelease(JSON.stringify(release));
205
246
  return [
206
- { path: RELEASE_FILE, content: `${JSON.stringify(release, null, 2)}\n` },
247
+ { path: RELEASE_PATH, content: `${JSON.stringify(release, null, 2)}\n` },
248
+ // Written without a configuration too, as none: one an earlier deploy delivered would otherwise ship again.
249
+ { path: DELIVERED_CONFIG_FILE, content: input.config ?? "{}\n" },
207
250
  // Written without packages too, empty: a list an earlier deploy left would otherwise outlive the last package.
208
- { path: PACKAGES_FILE, content: (input.packages ?? []).map((spec) => `${spec}\n`).join("") },
251
+ { path: PACKAGES_PATH, content: (input.packages ?? []).map((spec) => `${spec}\n`).join("") },
209
252
  { path: "Dockerfile", content: dockerfile(input) },
210
- // Both names: BuildKit prefers the one beside the Dockerfile, the classic builder reads the context root's.
211
253
  { path: ".dockerignore", content: ignore },
212
- { path: "Dockerfile.dockerignore", content: ignore },
213
254
  ];
214
255
  }
@@ -0,0 +1,13 @@
1
+ import type { InstanceConfigSource } from "../harnesses/pi-durable/config.ts";
2
+ /**
3
+ * Why the text `source` names cannot be delivered or would not open on the host, or undefined when it can and would:
4
+ * the open's own refusals (a path of the wrong kind, a name taken twice), and a path that is not strictly inside the
5
+ * configuration's directory, written relative to it, which the image could not reproduce.
6
+ */
7
+ export declare function deliveredTextProblem(agentDir: string, source: InstanceConfigSource): string | undefined;
8
+ /**
9
+ * Replace `.fastagent/deploy/` in `agentDir` with the text `source` names, whole: what an earlier deploy delivered and
10
+ * this one does not is gone, as the release manifest and the configuration are rewritten. The configuration's own file
11
+ * is written after, with the other build outputs. Answers how many paths were copied.
12
+ */
13
+ export declare function deliverText(agentDir: string, source: InstanceConfigSource | undefined): Promise<number>;
@@ -0,0 +1,86 @@
1
+ /**
2
+ * The instance text a deploy delivers (agent-artifact.md §6.1): the files and directories the delivered configuration
3
+ * names, copied beside it into `.fastagent/deploy/` with their layout, so the paths the configuration wrote, relative to
4
+ * its file, resolve the same in the image's `/etc/fastagent/`. Only what is beside the configuration is delivered: a
5
+ * path elsewhere on this machine does not exist on the host.
6
+ */
7
+ import { existsSync, readdirSync, statSync } from "node:fs";
8
+ import { cp, mkdir, rm } from "node:fs/promises";
9
+ import { dirname, isAbsolute, join, relative } from "node:path";
10
+ import { isUnderDir } from "../paths.js";
11
+ import { openText } from "../harnesses/pi-durable/text.js";
12
+ import { declareText } from "../harnesses/pi-durable/text-sources.js";
13
+ import { DELIVERED_CONFIG_DIR } from "./container.js";
14
+ /** What the configuration names beyond the program's own text, each kind with its sources. */
15
+ function declared(agentDir, source) {
16
+ const text = declareText(agentDir, source);
17
+ return ["instructions", "skills", "prompts"].flatMap((kind) => text[kind].filter((entry) => entry.declaredBy !== undefined).map((entry) => ({ kind, source: entry })));
18
+ }
19
+ /** The first symbolic link under `root` that points at nothing, which `cp` would fail on after the person's steps. */
20
+ function danglingLink(root) {
21
+ if (!statSync(root).isDirectory())
22
+ return undefined;
23
+ for (const entry of readdirSync(root, { recursive: true, withFileTypes: true })) {
24
+ const path = join(entry.parentPath, entry.name);
25
+ if (entry.isSymbolicLink() && !existsSync(path))
26
+ return path;
27
+ }
28
+ return undefined;
29
+ }
30
+ /**
31
+ * Why the text `source` names cannot be delivered or would not open on the host, or undefined when it can and would:
32
+ * the open's own refusals (a path of the wrong kind, a name taken twice), and a path that is not strictly inside the
33
+ * configuration's directory, written relative to it, which the image could not reproduce.
34
+ */
35
+ export function deliveredTextProblem(agentDir, source) {
36
+ try {
37
+ openText(agentDir, source);
38
+ for (const { kind, source: entry } of declared(agentDir, source)) {
39
+ const at = `${source.label}: ${kind} "${entry.given}"`;
40
+ const { directory } = source;
41
+ const given = entry.given ?? "";
42
+ if (directory === undefined || isAbsolute(given) || given === "~" || given.startsWith("~/")) {
43
+ return `${at} is not relative to the configuration: a deploy delivers what is beside it, so write a relative path`;
44
+ }
45
+ if (!isUnderDir(entry.path, directory) || entry.path === directory) {
46
+ return `${at} is outside the configuration's directory (${directory}): a deploy delivers only what is inside it`;
47
+ }
48
+ if (isUnderDir(agentDir, entry.path)) {
49
+ return `${at} contains the agent directory ${agentDir}: it cannot be delivered into it`;
50
+ }
51
+ const dangling = danglingLink(entry.path);
52
+ if (dangling !== undefined)
53
+ return `${at} holds a symbolic link to nothing: ${dangling}`;
54
+ }
55
+ return undefined;
56
+ }
57
+ catch (error) {
58
+ return error instanceof Error ? error.message : String(error);
59
+ }
60
+ }
61
+ /**
62
+ * Replace `.fastagent/deploy/` in `agentDir` with the text `source` names, whole: what an earlier deploy delivered and
63
+ * this one does not is gone, as the release manifest and the configuration are rewritten. The configuration's own file
64
+ * is written after, with the other build outputs. Answers how many paths were copied.
65
+ */
66
+ export async function deliverText(agentDir, source) {
67
+ // Copying is an act, not a finding: whatever the pre-flight only warned about (a generate-only run) must not be
68
+ // carried out, or a path outside the configuration's directory would be written outside `.fastagent/deploy/`.
69
+ const problem = source === undefined ? undefined : deliveredTextProblem(agentDir, source);
70
+ if (problem !== undefined)
71
+ throw new Error(problem);
72
+ const target = join(agentDir, DELIVERED_CONFIG_DIR);
73
+ if (source?.directory !== undefined && isUnderDir(source.directory, target)) {
74
+ throw new Error(`${source.label} is inside ${target}, which every deploy replaces: keep the configuration elsewhere`);
75
+ }
76
+ await rm(target, { recursive: true, force: true });
77
+ if (source?.directory === undefined)
78
+ return 0;
79
+ const entries = declared(agentDir, source);
80
+ for (const { source: entry } of entries) {
81
+ const to = join(target, relative(source.directory, entry.path));
82
+ await mkdir(dirname(to), { recursive: true });
83
+ await cp(entry.path, to, { recursive: true, dereference: true });
84
+ }
85
+ return entries.length;
86
+ }
@@ -14,11 +14,8 @@ export interface DockerPlanInput extends ContainerInput {
14
14
  boxLogin?: string;
15
15
  /** Every declared channel and its ingress — the source of the ingress note. */
16
16
  channels: readonly DeclaredChannel[];
17
- /** Generate an optional Cloudflare Quick Tunnel service in Compose. */
18
- tunnel: boolean;
19
17
  /**
20
- * The runbook's variable list (`deploymentSecrets`): what must have a value, then everything else the value file
21
- * carries.
18
+ * The runbook's variable list (`deploymentSecrets`): the declared names, each hinted by the file that declared it.
22
19
  */
23
20
  secrets?: readonly DeploymentSecret[];
24
21
  /** The value file as the pre-flight resolved it (it follows `FASTAGENT_SECRETS_DIR`), agent-dir-relative. */
@@ -35,13 +32,15 @@ export interface DockerPlan {
35
32
  export declare const DOCKER_COMPOSE_FILE = "fastagent.compose.yml";
36
33
  export declare const DOCKER_VALUE_FILE = ".secrets/production/.env";
37
34
  export declare const MIN_DOCKER_COMPOSE_VERSION = "2.3.3";
38
- /** Pinned: generated deployment artifacts must not silently move when Cloudflare publishes `latest`. */
39
- export declare const CLOUDFLARED_IMAGE = "cloudflare/cloudflared:2026.7.1";
40
35
  /** Whether a Compose file still opts into generated-drift warnings (drop the marker after taking ownership). */
41
36
  export declare function isGeneratedCompose(content: string): boolean;
42
- /** Whether an authoritative Compose file already carries the generated tunnel service contract. */
43
- export declare function composeHasTunnelService(content: string): boolean;
44
37
  /** Compose project names are lowercase [a-z0-9_-] and must start with an alphanumeric character. */
45
38
  export declare function toDockerProjectName(directoryName: string): string;
39
+ /**
40
+ * What `--run` says of the webhook channels once Compose is up. Docker has no URL of its own: with `http.publicUrl`
41
+ * named in the delivered configuration the agent points its own channels there (its `/health` says how each went) and
42
+ * only Slack's Request URL is the operator's; without one, nothing can reach those paths yet.
43
+ */
44
+ export declare function ingressNotes(channels: readonly DeclaredChannel[], publicUrl: string | undefined, healthUrl: string | undefined): string[];
46
45
  /** Compute local-Docker artifacts + the runbook; no Docker process is touched here. */
47
46
  export declare function planDockerDeploy(input: DockerPlanInput): DockerPlan;
@@ -1,67 +1,27 @@
1
1
  import { webhookPaths } from "../channel-ingress.js";
2
+ import { manualWebhookStep, webhookKinds } from "../../channels/webhooks.js";
2
3
  import { containerArtifacts } from "../container.js";
3
4
  import { deploymentLoginCommand } from "../box-shell.js";
4
5
  import { SECRETS_DIRNAME } from "../../paths.js";
6
+ import { stripEdges, stripEnd } from "../../strip-edges.js";
5
7
  const MOUNT = "/data";
6
8
  export const DOCKER_COMPOSE_FILE = "fastagent.compose.yml";
7
9
  export const DOCKER_VALUE_FILE = `${SECRETS_DIRNAME}/production/.env`;
8
10
  const GENERATED_COMPOSE_MARKER = "# Generated by `fastagent deploy docker`";
9
11
  export const MIN_DOCKER_COMPOSE_VERSION = "2.3.3";
10
- /** Pinned: generated deployment artifacts must not silently move when Cloudflare publishes `latest`. */
11
- export const CLOUDFLARED_IMAGE = "cloudflare/cloudflared:2026.7.1";
12
12
  /** Whether a Compose file still opts into generated-drift warnings (drop the marker after taking ownership). */
13
13
  export function isGeneratedCompose(content) {
14
14
  return content.startsWith(GENERATED_COMPOSE_MARKER);
15
15
  }
16
- /** Whether an authoritative Compose file already carries the generated tunnel service contract. */
17
- export function composeHasTunnelService(content) {
18
- return /^[ \t]+tunnel:\s*(?:#.*)?$/m.test(content);
19
- }
20
16
  /** Compose project names are lowercase [a-z0-9_-] and must start with an alphanumeric character. */
21
17
  export function toDockerProjectName(directoryName) {
22
- const slug = directoryName
23
- .toLowerCase()
24
- .replace(/[^a-z0-9_-]+/g, "-")
25
- .replace(/^[-_]+|[-_]+$/g, "")
26
- .slice(0, 54)
27
- .replace(/[-_]+$/g, "");
18
+ const separator = (char) => char === "-" || char === "_";
19
+ const slug = stripEnd(stripEdges(directoryName.toLowerCase().replace(/[^a-z0-9_-]+/g, "-"), separator).slice(0, 54), separator);
28
20
  return `fastagent-${slug || "agent"}`;
29
21
  }
30
- /** `${NAME:-}` without making JavaScript treat it as interpolation. */
31
- function composeInterpolation(name) {
32
- return `\${${name}:-}`;
33
- }
34
22
  function composeYaml(input) {
35
- // Compose sits beside the Dockerfile, in the agent directory, which is the build context.
36
- const tunnelService = input.tunnel
37
- ? `
38
- # Cloudflare Quick Tunnel: ephemeral URL, generated only with \`deploy docker --tunnel\`.
39
- # No restart policy on purpose: a new process gets a new URL and needs webhook re-registration.
40
- tunnel:
41
- image: ${CLOUDFLARED_IMAGE}
42
- command:
43
- - tunnel
44
- - --no-autoupdate
45
- - --url
46
- - http://agent:${input.port}
47
- depends_on:
48
- - agent
49
- environment:
50
- # Docker Desktop may inject HTTP(S)_PROXY into containers. The origin is Docker-internal and must
51
- # bypass it; preserve any operator NO_PROXY entries after the required service/loopback names.
52
- NO_PROXY: "agent,localhost,127.0.0.1,${composeInterpolation("NO_PROXY")}"
53
- no_proxy: "agent,localhost,127.0.0.1,${composeInterpolation("no_proxy")}"
54
- # Quieter than the agent, but bounded for the same reason: nothing else on this host bounds it.
55
- logging:
56
- driver: json-file
57
- options:
58
- max-size: "10m"
59
- max-file: "3"
60
- restart: "no"
61
- `
62
- : "";
63
23
  return `${GENERATED_COMPOSE_MARKER}. Edit freely — it is kept unless you pass --force.
64
- # Durable/public ingress remains yours; --tunnel optionally adds an ephemeral Quick Tunnel service.
24
+ # Public ingress remains yours: add your own tunnel/proxy/DNS/TLS, and name its URL as http.publicUrl.
65
25
  name: ${input.projectName}
66
26
 
67
27
  services:
@@ -98,11 +58,33 @@ services:
98
58
  max-size: "10m"
99
59
  max-file: "3"
100
60
  restart: unless-stopped
101
- ${tunnelService}
61
+
102
62
  volumes:
103
63
  state:
104
64
  `;
105
65
  }
66
+ /**
67
+ * What `--run` says of the webhook channels once Compose is up. Docker has no URL of its own: with `http.publicUrl`
68
+ * named in the delivered configuration the agent points its own channels there (its `/health` says how each went) and
69
+ * only Slack's Request URL is the operator's; without one, nothing can reach those paths yet.
70
+ */
71
+ export function ingressNotes(channels, publicUrl, healthUrl) {
72
+ const paths = webhookPaths(channels);
73
+ if (paths.length === 0)
74
+ return [];
75
+ if (publicUrl === undefined) {
76
+ return [
77
+ `${paths.join(", ")} receive nothing until a public URL reaches this container — configure your own ` +
78
+ `tunnel/proxy, point those webhook path(s) (or your remapped channel routes) at it and name its URL as ` +
79
+ `http.publicUrl in the configuration --config delivers, or set the channel's ingress to "connection" ` +
80
+ `where it has one`,
81
+ ];
82
+ }
83
+ return [
84
+ `the agent points its webhook channels at ${publicUrl} on every start; ${healthUrl ?? "its /health"} says how each went`,
85
+ ...(webhookKinds(channels).includes("slack") ? [manualWebhookStep("slack", publicUrl)] : []),
86
+ ];
87
+ }
106
88
  /** Compute local-Docker artifacts + the runbook; no Docker process is touched here. */
107
89
  export function planDockerDeploy(input) {
108
90
  const composePath = DOCKER_COMPOSE_FILE;
@@ -121,17 +103,14 @@ export function planDockerDeploy(input) {
121
103
  runbook.push(`# Environment: the container reads ${input.valueFile} whole (Compose \`env_file\`) — that file declares the`, `# deployed environment (in CI, write it before running the command):`, ...secrets.map((secret) => `# ${secret.name}: ${secret.hint}`));
122
104
  }
123
105
  runbook.push(`# Run from the agent directory. The build bakes it as the definition and installs its`, `# package.json dependencies.`);
124
- runbook.push(``, `# Before building a new definition release, run \`fastagent deploy docker\` to refresh its manifest.`, `# The Compose volume at ${MOUNT} retains the deployed definition, state and credentials.`, `# --pull: the base image is a floating tag, and a copy cached here may be older than the runtime floor.`, `${compose} build --pull`, `${compose} up -d`, `curl --fail http://127.0.0.1:${input.port}/health`, ...(input.boxLogin
106
+ runbook.push(``, `# Before building a new definition release, run \`fastagent deploy docker\` to refresh its manifest.`, `# The Compose volume at ${MOUNT} is the agent's working directory, and retains its state and credentials.`, `# --pull: the base image is a floating tag, and a copy cached here may be older than the runtime floor.`, `${compose} build --pull`, `${compose} up -d`, `curl --fail http://127.0.0.1:${input.port}/health`, ...(input.boxLogin
125
107
  ? [
126
108
  `# Model auth: once it is up, the deployment logs in to ${input.boxLogin} itself:`,
127
109
  `${deploymentLoginCommand("docker", input.boxLogin)}`,
128
110
  ]
129
111
  : []), ``, `# Operate it:`, `${compose} logs -f agent`, `${compose} ps`, `${compose} down # stops containers; keeps the state volume`, `# ${compose} down -v # DESTRUCTIVE: deletes credentials and all agent state`);
130
- if (input.tunnel) {
131
- runbook.push(``, `# Quick Tunnel: \`--run\` starts the Compose tunnel service and reads its ephemeral public URL.`, `# Telegram/Feishu/Lark and locally onboarded Slack auto-register; manual Slack prints its console URL. Re-run after the`, `# tunnel container/Docker daemon restarts: a new Quick Tunnel URL must replace the old webhook URL.`, `${compose} logs -f tunnel`);
132
- }
133
- else if (paths.length > 0) {
134
- runbook.push(``, `# Public ingress is operator-owned: add your tunnel/proxy/DNS/TLS, then configure the`, `# detected default endpoint(s): ${paths.map((path) => `https://<your-domain>${path}`).join(", ")}.`, `# If your channels/*.ts glue remaps a route, use that path instead.`);
112
+ if (paths.length > 0) {
113
+ runbook.push(``, `# Public ingress is operator-owned: add your tunnel/proxy/DNS/TLS, then route the detected default`, `# endpoint(s) to the agent: ${paths.map((path) => `https://<your-domain>${path}`).join(", ")}.`, `# If your channels/*.ts glue remaps a route, use that path instead. Name that URL as http.publicUrl in the`, `# configuration \`--config\` delivers, and the agent points Telegram and Feishu/Lark at it on every start`, `# (Slack's Request URL stays yours to set).`);
135
114
  }
136
115
  runbook.push(`# Keep one agent replica: the local state volume is single-process; multiple replicas split/corrupt state.`);
137
116
  return { artifacts, composePath, runbook };