@fastagent-sh/fastagent 0.29.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 (310) hide show
  1. package/README.md +9 -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 +2 -1
  15. package/dist/channels/feishu/feishu.d.ts +0 -3
  16. package/dist/channels/feishu/feishu.js +10 -10
  17. package/dist/channels/feishu/register-app.js +3 -5
  18. package/dist/channels/feishu/scaffold/channel.ts +32 -34
  19. package/dist/channels/feishu/scaffold/feishu-send.ts +5 -5
  20. package/dist/channels/feishu/scaffold/feishu-threads.ts +3 -3
  21. package/dist/channels/feishu/setup-mode.d.ts +2 -12
  22. package/dist/channels/feishu/setup-mode.js +2 -22
  23. package/dist/channels/feishu/setup.d.ts +12 -0
  24. package/dist/channels/feishu/setup.js +197 -0
  25. package/dist/channels/kit/abortable.d.ts +8 -0
  26. package/dist/channels/kit/abortable.js +22 -0
  27. package/dist/channels/lark/lark.d.ts +0 -3
  28. package/dist/channels/lark/lark.js +0 -5
  29. package/dist/channels/lark/scaffold/channel.ts +33 -35
  30. package/dist/channels/lark/scaffold/lark-send.ts +5 -5
  31. package/dist/channels/lark/scaffold/lark-threads.ts +3 -3
  32. package/dist/channels/lark/setup.d.ts +9 -0
  33. package/dist/channels/lark/setup.js +57 -0
  34. package/dist/channels/setup.d.ts +112 -0
  35. package/dist/channels/setup.js +31 -0
  36. package/dist/channels/slack/history.js +1 -1
  37. package/dist/channels/slack/manifest.d.ts +17 -7
  38. package/dist/channels/slack/manifest.js +12 -16
  39. package/dist/channels/slack/scaffold/channel.ts +44 -30
  40. package/dist/channels/slack/scaffold/slack-send.ts +8 -8
  41. package/dist/channels/slack/scaffold/slack-threads.ts +4 -4
  42. package/dist/channels/slack/setup.d.ts +7 -0
  43. package/dist/channels/slack/setup.js +119 -0
  44. package/dist/channels/slack/slack-api.d.ts +2 -0
  45. package/dist/channels/slack/slack-api.js +4 -0
  46. package/dist/channels/slack/slack.d.ts +21 -2
  47. package/dist/channels/slack/slack.js +398 -338
  48. package/dist/channels/slack/socket.d.ts +57 -0
  49. package/dist/channels/slack/socket.js +305 -0
  50. package/dist/channels/telegram/polling.d.ts +27 -0
  51. package/dist/channels/telegram/polling.js +105 -0
  52. package/dist/channels/telegram/scaffold/channel.ts +52 -32
  53. package/dist/channels/telegram/scaffold/telegram-send.ts +9 -9
  54. package/dist/channels/telegram/setup.d.ts +2 -0
  55. package/dist/channels/telegram/setup.js +42 -0
  56. package/dist/channels/telegram/telegram-api.d.ts +27 -1
  57. package/dist/channels/telegram/telegram-api.js +7 -6
  58. package/dist/channels/telegram/telegram.d.ts +11 -3
  59. package/dist/channels/telegram/telegram.js +184 -157
  60. package/dist/channels/webhook-evidence.d.ts +11 -0
  61. package/dist/channels/webhook-evidence.js +71 -0
  62. package/dist/channels/webhooks.d.ts +37 -0
  63. package/dist/channels/webhooks.js +70 -0
  64. package/dist/child-env.d.ts +16 -0
  65. package/dist/child-env.js +30 -0
  66. package/dist/cli/box-login.js +4 -1
  67. package/dist/cli/channel-app.d.ts +3 -0
  68. package/dist/cli/channel-app.js +35 -0
  69. package/dist/cli/channel-setup.d.ts +38 -0
  70. package/dist/cli/channel-setup.js +195 -0
  71. package/dist/cli/commands/add.d.ts +6 -14
  72. package/dist/cli/commands/add.js +45 -303
  73. package/dist/cli/commands/auth.js +8 -2
  74. package/dist/cli/commands/build.d.ts +5 -0
  75. package/dist/cli/commands/build.js +16 -0
  76. package/dist/cli/commands/channels.d.ts +19 -0
  77. package/dist/cli/commands/channels.js +230 -0
  78. package/dist/cli/commands/content.d.ts +7 -3
  79. package/dist/cli/commands/content.js +16 -10
  80. package/dist/cli/commands/deploy/agentcore.js +55 -23
  81. package/dist/cli/commands/deploy/channel-apps.d.ts +17 -0
  82. package/dist/cli/commands/deploy/channel-apps.js +30 -0
  83. package/dist/cli/commands/deploy/docker.js +32 -65
  84. package/dist/cli/commands/deploy/fly.js +28 -12
  85. package/dist/cli/commands/deploy/railway.js +38 -13
  86. package/dist/cli/commands/deploy/secrets-step.d.ts +19 -0
  87. package/dist/cli/commands/deploy/secrets-step.js +66 -0
  88. package/dist/cli/commands/deploy/shared.d.ts +16 -5
  89. package/dist/cli/commands/deploy/shared.js +25 -8
  90. package/dist/cli/commands/deploy.js +53 -60
  91. package/dist/cli/commands/dev.d.ts +2 -0
  92. package/dist/cli/commands/dev.js +15 -3
  93. package/dist/cli/commands/env.js +1 -1
  94. package/dist/cli/commands/info.d.ts +2 -0
  95. package/dist/cli/commands/info.js +60 -25
  96. package/dist/cli/commands/init.js +3 -1
  97. package/dist/cli/commands/invoke.d.ts +2 -0
  98. package/dist/cli/commands/invoke.js +20 -7
  99. package/dist/cli/commands/login.d.ts +0 -7
  100. package/dist/cli/commands/login.js +22 -10
  101. package/dist/cli/commands/logout.js +1 -1
  102. package/dist/cli/commands/mcp.js +14 -6
  103. package/dist/cli/commands/models.js +3 -2
  104. package/dist/cli/commands/schedules.d.ts +4 -2
  105. package/dist/cli/commands/schedules.js +14 -10
  106. package/dist/cli/commands/secrets.d.ts +14 -0
  107. package/dist/cli/commands/secrets.js +213 -0
  108. package/dist/cli/commands/start.d.ts +14 -14
  109. package/dist/cli/commands/start.js +46 -60
  110. package/dist/cli/content-view.d.ts +4 -6
  111. package/dist/cli/content-view.js +5 -11
  112. package/dist/cli/first-run-channels.d.ts +3 -0
  113. package/dist/cli/first-run-channels.js +63 -0
  114. package/dist/cli/instance.d.ts +41 -0
  115. package/dist/cli/instance.js +155 -0
  116. package/dist/cli/invoke-stream.d.ts +5 -2
  117. package/dist/cli/invoke-stream.js +7 -2
  118. package/dist/cli/program.js +282 -110
  119. package/dist/cli/serve.d.ts +4 -5
  120. package/dist/cli/serve.js +4 -7
  121. package/dist/cli/shared.d.ts +11 -2
  122. package/dist/cli/shared.js +32 -19
  123. package/dist/cli/ticket.js +50 -6
  124. package/dist/content/declare.d.ts +31 -37
  125. package/dist/content/declare.js +59 -60
  126. package/dist/content/file.d.ts +23 -12
  127. package/dist/content/file.js +48 -35
  128. package/dist/content/git.d.ts +0 -10
  129. package/dist/content/git.js +5 -39
  130. package/dist/content/resolve.d.ts +18 -22
  131. package/dist/content/resolve.js +32 -66
  132. package/dist/content/source.d.ts +7 -8
  133. package/dist/content/source.js +10 -28
  134. package/dist/credentials-page.d.ts +26 -0
  135. package/dist/credentials-page.js +185 -0
  136. package/dist/declared-secrets.d.ts +10 -3
  137. package/dist/declared-secrets.js +8 -4
  138. package/dist/deploy/agentcore/destroy.d.ts +4 -1
  139. package/dist/deploy/agentcore/destroy.js +25 -1
  140. package/dist/deploy/agentcore/forwarder.js +1 -1
  141. package/dist/deploy/agentcore/plan.d.ts +7 -21
  142. package/dist/deploy/agentcore/plan.js +47 -49
  143. package/dist/deploy/agentcore/run.d.ts +18 -10
  144. package/dist/deploy/agentcore/run.js +93 -70
  145. package/dist/deploy/agentcore/runtime-secrets.d.ts +7 -0
  146. package/dist/deploy/agentcore/runtime-secrets.js +108 -0
  147. package/dist/deploy/agentcore/shell.js +12 -19
  148. package/dist/deploy/agentcore/sigv4.d.ts +7 -0
  149. package/dist/deploy/agentcore/sigv4.js +15 -0
  150. package/dist/deploy/agentcore/slr.d.ts +18 -0
  151. package/dist/deploy/agentcore/slr.js +27 -0
  152. package/dist/deploy/agentcore/store.d.ts +7 -0
  153. package/dist/deploy/agentcore/store.js +115 -0
  154. package/dist/deploy/api.d.ts +14 -14
  155. package/dist/deploy/api.js +26 -24
  156. package/dist/deploy/box-shell.d.ts +3 -1
  157. package/dist/deploy/build-context.d.ts +4 -4
  158. package/dist/deploy/build-context.js +11 -15
  159. package/dist/deploy/channel-ingress.d.ts +35 -9
  160. package/dist/deploy/channel-ingress.js +119 -22
  161. package/dist/deploy/container.d.ts +35 -10
  162. package/dist/deploy/container.js +79 -37
  163. package/dist/deploy/delivered-text.d.ts +13 -0
  164. package/dist/deploy/delivered-text.js +86 -0
  165. package/dist/deploy/docker/plan.d.ts +7 -8
  166. package/dist/deploy/docker/plan.js +31 -52
  167. package/dist/deploy/docker/run.d.ts +5 -43
  168. package/dist/deploy/docker/run.js +9 -84
  169. package/dist/deploy/file-store.d.ts +2 -0
  170. package/dist/deploy/file-store.js +17 -0
  171. package/dist/deploy/fly/plan.d.ts +3 -2
  172. package/dist/deploy/fly/plan.js +20 -9
  173. package/dist/deploy/fly/run.d.ts +18 -8
  174. package/dist/deploy/fly/run.js +24 -42
  175. package/dist/deploy/fly/store.d.ts +12 -0
  176. package/dist/deploy/fly/store.js +83 -0
  177. package/dist/deploy/hosts.d.ts +1 -2
  178. package/dist/deploy/hosts.js +1 -2
  179. package/dist/deploy/preflight.d.ts +18 -5
  180. package/dist/deploy/preflight.js +135 -63
  181. package/dist/deploy/railway/ignore.d.ts +8 -0
  182. package/dist/deploy/railway/ignore.js +57 -0
  183. package/dist/deploy/railway/plan.d.ts +1 -2
  184. package/dist/deploy/railway/plan.js +8 -6
  185. package/dist/deploy/railway/run.d.ts +21 -15
  186. package/dist/deploy/railway/run.js +32 -78
  187. package/dist/deploy/railway/store.d.ts +29 -0
  188. package/dist/deploy/railway/store.js +143 -0
  189. package/dist/deploy/runner.d.ts +5 -2
  190. package/dist/deploy/runner.js +4 -3
  191. package/dist/deploy/secret-store.d.ts +63 -0
  192. package/dist/deploy/secret-store.js +98 -0
  193. package/dist/deploy/secrets.d.ts +29 -46
  194. package/dist/deploy/secrets.js +49 -122
  195. package/dist/deploy/workspace.d.ts +6 -12
  196. package/dist/deploy/workspace.js +18 -94
  197. package/dist/dev-supervisor.js +6 -8
  198. package/dist/environment/declare.js +3 -2
  199. package/dist/environment/mise.d.ts +3 -1
  200. package/dist/environment/mise.js +4 -2
  201. package/dist/feishu.d.ts +1 -1
  202. package/dist/feishu.js +1 -1
  203. package/dist/harnesses/models/catalog.d.ts +1 -0
  204. package/dist/harnesses/models/catalog.js +1 -2
  205. package/dist/harnesses/models/environment.d.ts +7 -2
  206. package/dist/harnesses/models/environment.js +9 -5
  207. package/dist/harnesses/models/host.d.ts +0 -7
  208. package/dist/harnesses/models/host.js +10 -1
  209. package/dist/harnesses/models/login.d.ts +0 -8
  210. package/dist/harnesses/models/login.js +5 -4
  211. package/dist/harnesses/models/models.d.ts +7 -2
  212. package/dist/harnesses/models/models.js +28 -4
  213. package/dist/harnesses/pi-durable/agent.js +59 -38
  214. package/dist/harnesses/pi-durable/build.d.ts +12 -0
  215. package/dist/harnesses/pi-durable/build.js +270 -0
  216. package/dist/harnesses/pi-durable/built.d.ts +24 -0
  217. package/dist/harnesses/pi-durable/built.js +136 -0
  218. package/dist/harnesses/pi-durable/config.d.ts +81 -0
  219. package/dist/harnesses/pi-durable/config.js +266 -0
  220. package/dist/harnesses/pi-durable/define.d.ts +63 -13
  221. package/dist/harnesses/pi-durable/define.js +23 -2
  222. package/dist/harnesses/pi-durable/definition.d.ts +72 -0
  223. package/dist/harnesses/pi-durable/definition.js +143 -0
  224. package/dist/harnesses/pi-durable/events.d.ts +4 -3
  225. package/dist/harnesses/pi-durable/events.js +179 -136
  226. package/dist/harnesses/pi-durable/execution-env.d.ts +17 -0
  227. package/dist/harnesses/pi-durable/execution-env.js +11 -0
  228. package/dist/harnesses/pi-durable/mcp.js +1 -3
  229. package/dist/harnesses/pi-durable/open.d.ts +35 -37
  230. package/dist/harnesses/pi-durable/open.js +91 -153
  231. package/dist/harnesses/pi-durable/reads.d.ts +0 -1
  232. package/dist/harnesses/pi-durable/reads.js +1 -3
  233. package/dist/harnesses/pi-durable/registry.d.ts +4 -6
  234. package/dist/harnesses/pi-durable/registry.js +29 -26
  235. package/dist/harnesses/pi-durable/release.d.ts +2 -2
  236. package/dist/harnesses/pi-durable/release.js +10 -7
  237. package/dist/harnesses/pi-durable/serve.d.ts +91 -15
  238. package/dist/harnesses/pi-durable/serve.js +109 -47
  239. package/dist/harnesses/pi-durable/sessions.js +15 -12
  240. package/dist/harnesses/pi-durable/text-sources.d.ts +30 -0
  241. package/dist/harnesses/pi-durable/text-sources.js +55 -0
  242. package/dist/harnesses/pi-durable/text.d.ts +16 -4
  243. package/dist/harnesses/pi-durable/text.js +115 -59
  244. package/dist/harnesses/pi-durable/wake.js +8 -8
  245. package/dist/http/client.js +9 -3
  246. package/dist/http/contract.d.ts +3 -3
  247. package/dist/http/server.d.ts +1 -1
  248. package/dist/http/server.js +1 -1
  249. package/dist/index.d.ts +8 -5
  250. package/dist/index.js +4 -1
  251. package/dist/instance-name.d.ts +7 -0
  252. package/dist/instance-name.js +26 -0
  253. package/dist/lark.d.ts +1 -1
  254. package/dist/lark.js +1 -1
  255. package/dist/mcp/client.js +11 -2
  256. package/dist/mcp/config.d.ts +70 -18
  257. package/dist/mcp/config.js +136 -53
  258. package/dist/mcp/host.d.ts +5 -2
  259. package/dist/mcp/host.js +14 -6
  260. package/dist/mcp/sign-in.js +2 -2
  261. package/dist/mcp-api.d.ts +14 -0
  262. package/dist/paths.d.ts +22 -8
  263. package/dist/paths.js +44 -11
  264. package/dist/runtime-websocket.d.ts +14 -0
  265. package/dist/runtime-websocket.js +39 -0
  266. package/dist/scaffold/add-channel.d.ts +9 -16
  267. package/dist/scaffold/add-channel.js +13 -83
  268. package/dist/scaffold/templates/env.example +4 -5
  269. package/dist/scaffold/templates/fastagent.config.ts +20 -14
  270. package/dist/scaffold/templates/gitignore +9 -6
  271. package/dist/scaffold/templates.d.ts +6 -2
  272. package/dist/scaffold/templates.js +7 -2
  273. package/dist/scaffold/vendor-skill.js +4 -2
  274. package/dist/service.d.ts +44 -6
  275. package/dist/service.js +110 -10
  276. package/dist/serving-command.d.ts +10 -0
  277. package/dist/serving-command.js +1 -0
  278. package/dist/slack.d.ts +1 -1
  279. package/dist/slack.js +1 -1
  280. package/dist/strip-edges.d.ts +10 -0
  281. package/dist/strip-edges.js +22 -0
  282. package/dist/telegram.d.ts +1 -1
  283. package/dist/telegram.js +1 -1
  284. package/dist/tunnel.d.ts +0 -1
  285. package/dist/tunnel.js +2 -8
  286. package/dist/value-file.d.ts +11 -6
  287. package/dist/value-file.js +21 -9
  288. package/dist/version.d.ts +7 -1
  289. package/dist/version.js +9 -3
  290. package/package.json +6 -5
  291. package/dist/channels/lark/credentials-page.d.ts +0 -25
  292. package/dist/channels/lark/credentials-page.js +0 -206
  293. package/dist/channels/lark/onboard.d.ts +0 -52
  294. package/dist/channels/lark/onboard.js +0 -86
  295. package/dist/channels/slack/config-api.d.ts +0 -69
  296. package/dist/channels/slack/config-api.js +0 -162
  297. package/dist/channels/slack/onboard.d.ts +0 -40
  298. package/dist/channels/slack/onboard.js +0 -136
  299. package/dist/channels/slack/onboarding-state.d.ts +0 -31
  300. package/dist/channels/slack/onboarding-state.js +0 -64
  301. package/dist/channels/slack/register-webhook.d.ts +0 -12
  302. package/dist/channels/slack/register-webhook.js +0 -67
  303. package/dist/channels/slack/setup-server.d.ts +0 -32
  304. package/dist/channels/slack/setup-server.js +0 -246
  305. package/dist/cli/add-feishu.d.ts +0 -41
  306. package/dist/cli/add-feishu.js +0 -391
  307. package/dist/cli/add-slack.d.ts +0 -18
  308. package/dist/cli/add-slack.js +0 -216
  309. package/dist/harnesses/pi-durable/parked.d.ts +0 -18
  310. package/dist/harnesses/pi-durable/parked.js +0 -27
@@ -0,0 +1,230 @@
1
+ /**
2
+ * `fastagent channels setup <name>`: set up one channel's platform app for one instance (channel-setup.md §2): ask a
3
+ * person for the values only they have, check them with the platform, and keep them in the instance's value file.
4
+ * With `--record-url`: record, in the configuration a deploy delivers, the URL a person set on a Slack or Lark app's
5
+ * page, once the deployment confirms the platform reaches it there (§5).
6
+ */
7
+ import { existsSync } from "node:fs";
8
+ import { join, resolve } from "node:path";
9
+ import { recordChannelUrl } from "../channel-app.js";
10
+ import { isSetupKind, setUpChannel } from "../channel-setup.js";
11
+ import { webhookPath } from "../../channels/webhooks.js";
12
+ import { enterAgentEnv } from "../../env.js";
13
+ import { readInstanceConfigFile, resolveConfig } from "../../harnesses/pi-durable/config.js";
14
+ import { chosenIngress } from "../../harnesses/pi-durable/definition.js";
15
+ import { buildDefinitionShape, loadSourceDefinition } from "../../harnesses/pi-durable/open.js";
16
+ import { setChannelConfig } from "../../authoring.js";
17
+ import { INSTANCE_CONFIG_FILE, selectAgentEnvironment } from "../../paths.js";
18
+ import { markDevServe, unmarkDevServe } from "../../serving-command.js";
19
+ import { dotEnvPath, loadEnvValues } from "../../value-file.js";
20
+ import { agentDirOrExit, failStartup, failUsage } from "../fail.js";
21
+ import { enterEnvFile } from "../instance.js";
22
+ import { isInteractive, shellArg } from "../shared.js";
23
+ import { checkTicket, printReport, refusedCheck, runTicket, startTicket, ticketExitCode, ticketReport, } from "../ticket.js";
24
+ import { stripTrailingSlashes } from "../../strip-edges.js";
25
+ /** Without a terminal, how long a setup stays open for the person to finish their part. */
26
+ const UNATTENDED_TIMEOUT_SECONDS = 900;
27
+ export async function runChannelsSetup(name, dirArg, opts) {
28
+ if (opts.check !== undefined)
29
+ return checkSetup(name, opts.check, opts);
30
+ if (opts.recordUrl)
31
+ return recordUrl(name, dirArg, opts);
32
+ if (!isSetupKind(name)) {
33
+ failUsage(`${name}: \`channels setup\` sets up telegram, slack, feishu and lark`);
34
+ }
35
+ if (opts.ticket !== undefined) {
36
+ // The process `startTicket` started: it takes the ticket before anything slow (the definition is loaded to plan),
37
+ // then does the whole setup, its person the ticket's.
38
+ await runTicket(opts.ticket, async (step) => ({
39
+ ...(await carryOut(await planSetup(name, dirArg, opts), step.person)),
40
+ }));
41
+ process.exit(0);
42
+ }
43
+ const plan = await planSetup(name, dirArg, opts);
44
+ if (opts.input === false || opts.json || !isInteractive())
45
+ return setUpUnattended(plan, opts);
46
+ const outcome = await carryOut(plan).catch(failStartup);
47
+ console.error(" next steps:");
48
+ for (const line of outcome.next_steps)
49
+ console.error(` ${line}`);
50
+ // Platform sockets left open would hold a one-shot command open after everything durable is done.
51
+ process.exit(0);
52
+ }
53
+ async function planSetup(kind, dirArg, opts) {
54
+ if (opts.ingress !== undefined && opts.ingress !== "webhook" && opts.ingress !== "connection") {
55
+ failUsage(`--ingress must be "webhook" or "connection", got "${opts.ingress}"`);
56
+ }
57
+ const agentDir = agentDirOrExit(resolve(dirArg));
58
+ // The development instance is the default one; naming a configuration or a value file is naming another.
59
+ const dev = opts.config === undefined && opts.envFile === undefined;
60
+ selectAgentEnvironment(dev ? "dev" : "production");
61
+ if (dev)
62
+ markDevServe();
63
+ else
64
+ unmarkDevServe();
65
+ enterEnvFile(opts.envFile);
66
+ enterAgentEnv(agentDir);
67
+ const file = dotEnvPath(agentDir);
68
+ const config = opts.config !== undefined ? resolve(opts.config) : dev ? join(agentDir, INSTANCE_CONFIG_FILE) : undefined;
69
+ if (opts.ingress !== undefined && config === undefined) {
70
+ failUsage("--ingress writes into a configuration: pass --config <file>, the one `deploy --config` delivers");
71
+ }
72
+ const definition = await loadSourceDefinition(agentDir).catch(failStartup);
73
+ const instance = config !== undefined && existsSync(config) ? await readInstanceConfigFile(config).catch(failStartup) : undefined;
74
+ const effective = resolveConfig(definition, instance);
75
+ const way = opts.ingress ?? chosenIngress(effective, kind, dev);
76
+ // A channel file that names its factory receives one way whatever the configuration says: setting the app up for
77
+ // the other would change nothing but the app.
78
+ if (existsSync(join(agentDir, "src", "channels", `${kind}.ts`))) {
79
+ try {
80
+ buildDefinitionShape(definition, process.env, {
81
+ settings: definition.config.defaults?.settings ?? {},
82
+ channels: { [kind]: { ingress: way } },
83
+ });
84
+ }
85
+ catch (error) {
86
+ failStartup(new Error(`the agent does not build for ${kind} to receive by ${way} (${error.message})`));
87
+ }
88
+ }
89
+ const publicUrl = effective.http?.publicUrl;
90
+ return {
91
+ kind,
92
+ agentDir,
93
+ dev,
94
+ file,
95
+ way,
96
+ publicUrl,
97
+ writeIngress: opts.ingress === undefined ? undefined : config,
98
+ rerun: `fastagent channels setup ${kind} ${shellArg(agentDir)}` +
99
+ `${opts.ingress === undefined ? "" : ` --ingress ${opts.ingress}`}` +
100
+ `${opts.config === undefined ? "" : ` --config ${shellArg(resolve(opts.config))}`}` +
101
+ `${opts.envFile === undefined ? "" : ` --env-file ${shellArg(resolve(opts.envFile))}`}`,
102
+ };
103
+ }
104
+ /** The setup itself, once its person's part can be done: the values, the way written down, and what comes next. */
105
+ async function carryOut(plan, person) {
106
+ const outcome = await setUpChannel({
107
+ kind: plan.kind,
108
+ agentDir: plan.agentDir,
109
+ file: plan.file,
110
+ way: plan.way,
111
+ dev: plan.dev,
112
+ rerun: plan.rerun,
113
+ ...(plan.publicUrl === undefined ? {} : { publicUrl: plan.publicUrl }),
114
+ ...(person === undefined ? {} : { person }),
115
+ });
116
+ if (plan.writeIngress !== undefined) {
117
+ await setChannelConfig(plan.agentDir, plan.kind, { ingress: plan.way }, { config: plan.writeIngress });
118
+ console.error(`[fastagent] wrote channels.${plan.kind}.ingress "${plan.way}" to ${plan.writeIngress}`);
119
+ }
120
+ const serve = plan.dev
121
+ ? "fastagent dev"
122
+ : `fastagent start --env-file ${shellArg(plan.file)} # or, from .secrets/production/.env, fastagent deploy <host> --run`;
123
+ return { written: outcome.written, kept: outcome.kept, next_steps: [serve, ...outcome.after] };
124
+ }
125
+ /** The command that reads a setup's ticket, as every such ticket records it. */
126
+ const setupCheck = (kind) => `fastagent channels setup ${kind} --check`;
127
+ /**
128
+ * A setup with no one at a terminal to ask, for a coding agent: it runs in a process of its own (ticket.ts), and the
129
+ * command returns once the person has a step to take, it is done, or it is still working at it. What only the person
130
+ * has is pasted on a page of the setup's own, never asked for in the agent's conversation and never typed into a file.
131
+ */
132
+ async function setUpUnattended(plan, opts) {
133
+ const timeout = opts.timeout === undefined ? UNATTENDED_TIMEOUT_SECONDS : Number(opts.timeout);
134
+ if (!Number.isInteger(timeout) || timeout <= 0)
135
+ failUsage("--timeout must be a whole number of seconds");
136
+ const ticket = await startTicket({
137
+ // One setup per agent, channel and value file: another one's is its own business.
138
+ subject: `channels-setup:${plan.kind}:${plan.file}`,
139
+ action: `set up the ${plan.kind} app`,
140
+ check: setupCheck(plan.kind),
141
+ restart: plan.rerun,
142
+ timeoutSeconds: timeout,
143
+ waitsFirst: false,
144
+ whenRunning: "replace",
145
+ command: {
146
+ file: process.execPath,
147
+ args: [
148
+ ...process.execArgv,
149
+ process.argv[1],
150
+ "channels",
151
+ "setup",
152
+ plan.kind,
153
+ plan.agentDir,
154
+ ...(opts.ingress === undefined ? [] : ["--ingress", opts.ingress]),
155
+ ...(opts.config === undefined ? [] : ["--config", resolve(opts.config)]),
156
+ ...(opts.envFile === undefined ? [] : ["--env-file", resolve(opts.envFile)]),
157
+ ],
158
+ cwd: process.cwd(),
159
+ },
160
+ });
161
+ reportSetup(ticket, opts, "started");
162
+ process.exit(ticketExitCode(ticket));
163
+ }
164
+ /** `channels setup <name> --check <ticket>`. */
165
+ async function checkSetup(name, id, opts) {
166
+ const refused = refusedCheck(id, setupCheck(name));
167
+ if (refused !== undefined)
168
+ failUsage(refused);
169
+ // There: refusedCheck read it, and a ticket is pruned only a day after it ends.
170
+ const ticket = (await checkTicket(id));
171
+ reportSetup(ticket, opts, "checked");
172
+ process.exit(ticketExitCode(ticket));
173
+ }
174
+ /** A setup's ticket for whoever started or checked it: the shared report, and what a finished setup adds. */
175
+ function reportSetup(ticket, opts, when) {
176
+ const { json, text } = ticketReport(ticket, when, (result) => {
177
+ const written = result.written ?? [];
178
+ const kept = result.kept ?? [];
179
+ const next = result.next_steps ?? [];
180
+ return {
181
+ fields: { written, kept, next_steps: next },
182
+ text: [...(written.length > 0 ? [`wrote ${written.join(", ")}`] : []), ...next.map((line) => `next: ${line}`)],
183
+ next: "The app is set up. Its next steps are in next_steps.",
184
+ };
185
+ });
186
+ printReport(opts.json, json, text);
187
+ }
188
+ async function recordUrl(name, dirArg, opts) {
189
+ if (name !== "slack" && name !== "lark") {
190
+ failUsage(`${name}: only slack and lark have a URL a person sets (the instance points the others itself)`);
191
+ }
192
+ if (opts.config === undefined)
193
+ failUsage("--record-url writes into the configuration a deploy delivers: pass --config <file>");
194
+ const agentDir = agentDirOrExit(resolve(dirArg));
195
+ selectAgentEnvironment("production");
196
+ enterEnvFile(opts.envFile);
197
+ // The deployment's /health and the platform are reached as the instance's values and environment say, a proxy included.
198
+ enterAgentEnv(agentDir);
199
+ const config = resolve(opts.config);
200
+ const source = await readInstanceConfigFile(config).catch(failStartup);
201
+ const named = source.values.http?.publicUrl;
202
+ const configured = typeof named === "string" ? stripTrailingSlashes(named) : undefined;
203
+ // `--url` is where this machine reads the deployment's /health; the URL the box confirms is the configuration's
204
+ // own http.publicUrl when it names one, the one the platforms reach.
205
+ const reach = (opts.url === undefined ? undefined : stripTrailingSlashes(opts.url)) ?? configured;
206
+ const base = configured ?? reach;
207
+ if (reach === undefined || base === undefined) {
208
+ failUsage(`${config} names no http.publicUrl: pass --url <the deployment's public URL>`);
209
+ }
210
+ const kind = name;
211
+ const url = `${base}${webhookPath(kind)}`;
212
+ const outcome = await healthOutcome(`${reach}/health`, kind).catch(failStartup);
213
+ if (outcome !== "registered") {
214
+ failStartup(new Error(`${kind}: the deployment at ${reach} reports it ${outcome ?? "not listed"}, not reached at ${url} yet — save ` +
215
+ `${url} on the app's page (the platform verifies it with the deployment), then run this again`));
216
+ }
217
+ await recordChannelUrl(agentDir, config, kind, url, loadEnvValues(dotEnvPath(agentDir))).catch(failStartup);
218
+ printReport(opts.json, { status: "done", channel: kind, url, config }, [`recorded ${url} for ${kind} in ${config}`]);
219
+ }
220
+ /** The outcome the deployment's `/health` reports for `kind`, or undefined when it lists none. */
221
+ async function healthOutcome(healthUrl, kind) {
222
+ let body;
223
+ try {
224
+ body = (await (await fetch(healthUrl, { signal: AbortSignal.timeout(10_000) })).json());
225
+ }
226
+ catch (error) {
227
+ throw new Error(`could not read ${healthUrl}: ${String(error)}`, { cause: error });
228
+ }
229
+ return body.webhooks?.find((entry) => entry.channel === kind)?.outcome;
230
+ }
@@ -1,6 +1,10 @@
1
1
  import { type SourceOptions } from "../../content/source.ts";
2
+ import { type InstanceFlags } from "../instance.ts";
2
3
  /** A name refusal is the caller's to fix by naming another (exit 2); anything else is a startup failure (exit 1). */
3
4
  export declare function failEdit(hint: string): (error: unknown) => never;
4
- export declare function runContentList(dirArg: string, json: boolean): Promise<void>;
5
- export declare function runContentAdd(source: string, dirArg: string, opts: SourceOptions): Promise<void>;
6
- export declare function runContentRemove(name: string, dirArg: string): Promise<void>;
5
+ export declare function runContentList(dirArg: string, json: boolean, flags?: InstanceFlags): Promise<void>;
6
+ export declare function runContentAdd(source: string, dirArg: string, opts: SourceOptions & {
7
+ program?: boolean;
8
+ instance?: InstanceFlags;
9
+ }): Promise<void>;
10
+ export declare function runContentRemove(name: string, dirArg: string, flags?: InstanceFlags): Promise<void>;
@@ -1,12 +1,13 @@
1
1
  /**
2
- * `fastagent content list|add|remove`: what the agent works on, as `context.json` declares it and
3
- * `context.local.json` binds it on this machine.
2
+ * `fastagent content list|add|remove`: what the agent works on, as the program's `context.json` declares it and the
3
+ * instance's configuration adds to it.
4
4
  */
5
5
  import { resolve } from "node:path";
6
6
  import { ContentNameError, addContent, listContent, removeContent } from "../../authoring.js";
7
7
  import { readContentSource } from "../../content/source.js";
8
8
  import { contentLines } from "../content-view.js";
9
9
  import { failStartup, failUsage } from "../fail.js";
10
+ import { enterContentScope } from "../instance.js";
10
11
  /** A name refusal is the caller's to fix by naming another (exit 2); anything else is a startup failure (exit 1). */
11
12
  export function failEdit(hint) {
12
13
  return (error) => {
@@ -15,30 +16,35 @@ export function failEdit(hint) {
15
16
  failStartup(error);
16
17
  };
17
18
  }
18
- export async function runContentList(dirArg, json) {
19
- const { content, absent } = await listContent(resolve(dirArg)).catch(failStartup);
19
+ export async function runContentList(dirArg, json, flags) {
20
+ const dir = resolve(dirArg);
21
+ const { content } = await listContent(dir, enterContentScope(dir, flags)).catch(failStartup);
20
22
  if (json) {
21
- console.log(JSON.stringify({ content, absent }, null, 2));
23
+ console.log(JSON.stringify({ content }, null, 2));
22
24
  return;
23
25
  }
24
- for (const [label, value] of contentLines(content, absent))
26
+ for (const [label, value] of contentLines(content))
25
27
  console.log(`${`${label}:`.padEnd(10)} ${value}`);
26
28
  }
27
29
  export async function runContentAdd(source, dirArg, opts) {
30
+ const dir = resolve(dirArg);
31
+ const { program, instance, ...options } = opts;
28
32
  const { addition, notes } = await Promise.resolve()
29
- .then(() => readContentSource(source, process.cwd(), opts))
33
+ .then(() => readContentSource(source, process.cwd(), options))
30
34
  .catch(failStartup);
31
35
  for (const note of notes)
32
36
  console.error(`[fastagent] ${note}`);
33
- const { name, content, notes: left } = await addContent(resolve(dirArg), addition).catch(failEdit(" — pass --name"));
37
+ const scope = enterContentScope(dir, instance);
38
+ const { name, content, notes: left, } = await addContent(dir, { ...addition, ...(program ? { program } : {}) }, scope).catch(failEdit(" — pass --name"));
34
39
  const added = content.filter((entry) => entry.name === name);
35
40
  for (const [label, value] of contentLines(added))
36
41
  console.error(`[fastagent] ${label} ${value}`);
37
42
  for (const note of left)
38
43
  console.error(`[fastagent] ${note}`);
39
44
  }
40
- export async function runContentRemove(name, dirArg) {
41
- const removed = await removeContent(resolve(dirArg), name).catch(failEdit(""));
45
+ export async function runContentRemove(name, dirArg, flags) {
46
+ const dir = resolve(dirArg);
47
+ const removed = await removeContent(dir, name, enterContentScope(dir, flags)).catch(failEdit(""));
42
48
  console.error(`[fastagent] removed content ${removed.name}`);
43
49
  for (const note of removed.notes)
44
50
  console.error(`[fastagent] ${note}`);
@@ -11,10 +11,11 @@ import { awsCli, awsJson } from "../../../deploy/agentcore/aws-cli.js";
11
11
  import { agentcoreShell } from "../../../deploy/agentcore/shell.js";
12
12
  import { awsRunner, spawnRunner } from "../../../deploy/runner.js";
13
13
  import { SECRET_FILE_MODE, exists } from "../../../paths.js";
14
- import { assembleSecrets } from "../../../deploy/secrets.js";
14
+ import { deploySecrets } from "./secrets-step.js";
15
15
  import { failStartup } from "../../fail.js";
16
16
  import { shellArg } from "../../shared.js";
17
- import { boxLoginStep, registrarsFor } from "./shared.js";
17
+ import { ownPublicUrl } from "../../../deploy/container.js";
18
+ import { boxLoginStep, registrarsFor, urlRecorder } from "./shared.js";
18
19
  export const agentcoreHost = {
19
20
  isOurs: (path, content) => path.endsWith(TEMPLATE_FILE) && isGeneratedAgentcoreTemplate(content),
20
21
  async shell(agentDir) {
@@ -55,7 +56,7 @@ export const agentcoreHost = {
55
56
  console.error(`[fastagent] note: AgentCore resets the runtime's storage on every deploy AND after 14 idle days, and the ` +
56
57
  `${boxLogin} login with it: every deploy of this agent ends with a login on the runtime, and after an idle ` +
57
58
  `reset every turn fails until \`fastagent login ${boxLogin} --deployment agentcore\` (the runtime's log says ` +
58
- `so). For an agent that must keep answering unattended, set ${boxLogin}'s API key in ${valueFile}.`);
59
+ `so). For an agent that must keep answering unattended, set ${boxLogin}'s API key with \`fastagent secrets set agentcore <its variable>\`.`);
59
60
  }
60
61
  // Schedules and wake-ups need nothing from the template beyond the forwarder every stack has: the container
61
62
  // mirrors them into EventBridge schedules itself (schedule/wake-alarm.ts). A schedule that does not build is the
@@ -98,9 +99,13 @@ export const agentcoreHost = {
98
99
  rerun: ctx.rerun,
99
100
  channels,
100
101
  declaredSecrets,
102
+ modelKey: pre.modelKey,
103
+ api: ctx.api,
101
104
  values,
102
105
  valueFile,
103
106
  topology: plan.topology,
107
+ publicUrl: ownPublicUrl(container.config),
108
+ config: opts.config,
104
109
  });
105
110
  return undefined;
106
111
  }
@@ -111,24 +116,23 @@ export const agentcoreHost = {
111
116
  /** `deploy agentcore --run`: drive aws + docker to completion. */
112
117
  async function runDeployAgentcore(params) {
113
118
  const { agentDir, name, channels, topology } = params;
114
- // Decided before the first side effect: the deploy resets the runtime's storage and the login with it, so with nobody
115
- // to log it in again it would end at "not logged in" AFTER replacing a runtime that was serving, with the webhooks
116
- // a previous deploy registered still pointing at it. A coding agent's ticket has someone: its user.
117
- if (params.boxLogin && params.answerer.kind === "nobody") {
118
- failStartup(new Error(`deploy stopped: every AgentCore deploy wipes the runtime's ${params.boxLogin} login, and without a terminal ` +
119
- `nobody can log it in again — run this deploy in a terminal; from a coding agent, run ` +
120
- `\`${params.rerun} --json\`, which hands the login to its user; or set ` +
121
- `${params.boxLogin}'s API key in ${params.valueFile}`));
122
- }
123
- const { secrets, missingSecrets } = assembleSecrets({
119
+ const secretsStep = deploySecrets({
120
+ host: "agentcore",
121
+ label: "AWS Secrets Manager",
122
+ agentDir,
123
+ opts: { config: params.config },
124
+ answerer: params.answerer,
125
+ api: params.api,
124
126
  modelAuth: params.modelAuth,
125
- declared: params.declaredSecrets,
127
+ modelKey: params.modelKey,
128
+ declaredSecrets: params.declaredSecrets,
129
+ valueFile: params.valueFile,
126
130
  values: params.values,
127
131
  });
128
- // The wake-alarm shared secret (container ↔ forwarder).
129
- secrets.FASTAGENT_WAKE_SECRET = crypto.randomUUID();
130
- // The forwarder→runtime ingress secret: what makes an envelope the forwarder's rather than any IAM principal's.
131
- secrets.FASTAGENT_INGRESS_SECRET = crypto.randomUUID();
132
+ // The model's key on the target is a credential like a key in the value file: the runtime has it from its first
133
+ // invocation, so there is no login to run on the box. Only the target can say, hence after the secrets step.
134
+ let keyOnTarget = false;
135
+ const login = boxLoginStep("agentcore", params, (runtimeArn) => agentcoreShell(runtimeArn, ingressSessionId(name), awsRunner(agentDir))).boxLogin;
132
136
  // The params temp dir holds the ONE file carrying secret values (file:// parameter-overrides — never argv);
133
137
  // 0700/0600 and removed after the run, success or gate.
134
138
  const paramsDir = await mkdtemp(join(tmpdir(), "fastagent-agentcore-"));
@@ -142,12 +146,40 @@ async function runDeployAgentcore(params) {
142
146
  .replace(/[-:.TZ]/g, "")
143
147
  .slice(0, 14),
144
148
  region: process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION,
145
- secrets,
146
- missingSecrets,
147
- valueFile: params.valueFile,
149
+ provideSecrets: async (store) => {
150
+ const gate = await secretsStep(store);
151
+ if (gate !== undefined)
152
+ return gate;
153
+ const held = await store.names();
154
+ keyOnTarget = params.modelKey.variables.some((variable) => held.has(variable));
155
+ // Decided before the build and the stack: the deploy resets the runtime's storage and the login with it, so
156
+ // with nobody to log it in again it would end at "not logged in" AFTER replacing a runtime that was serving,
157
+ // with the webhooks a previous deploy registered still pointing at it. A coding agent's ticket has someone:
158
+ // its user.
159
+ if (params.boxLogin && !keyOnTarget && params.answerer.kind === "nobody") {
160
+ return (`every AgentCore deploy wipes the runtime's ${params.boxLogin} login, and without a terminal ` +
161
+ `nobody can log it in again — run this deploy in a terminal; from a coding agent, run ` +
162
+ `\`${params.rerun} --json\`, which hands the login to its user; or set ` +
163
+ `${params.boxLogin}'s API key with \`fastagent secrets set agentcore ${params.modelKey.variables[0] ?? "<its variable>"}\``);
164
+ }
165
+ return undefined;
166
+ },
167
+ // The wake-alarm shared secret (container ↔ forwarder).
168
+ wakeSecret: crypto.randomUUID(),
169
+ // The forwarder→runtime ingress secret: what makes an envelope the forwarder's rather than any IAM principal's.
170
+ ingressSecret: crypto.randomUUID(),
148
171
  channels,
149
172
  topology,
150
- ...boxLoginStep("agentcore", params, (runtimeArn) => agentcoreShell(runtimeArn, ingressSessionId(name), awsRunner(agentDir))),
173
+ ...(params.publicUrl === undefined ? {} : { publicUrl: params.publicUrl }),
174
+ ...urlRecorder(agentDir, params.config, params.values, params.answerer),
175
+ ...(login === undefined
176
+ ? {}
177
+ : {
178
+ boxLogin: {
179
+ ...login,
180
+ run: async (runtimeArn) => (keyOnTarget ? undefined : login.run(runtimeArn)),
181
+ },
182
+ }),
151
183
  }, awsRunner(agentDir), spawnRunner("docker", agentDir), (m) => console.error(`[fastagent] ${m}`), async (content) => {
152
184
  const path = join(paramsDir, "params.json");
153
185
  await writeFile(path, content, { mode: SECRET_FILE_MODE });
@@ -156,7 +188,7 @@ async function runDeployAgentcore(params) {
156
188
  const path = join(paramsDir, "forwarder.zip");
157
189
  await writeFile(path, bytes);
158
190
  return path;
159
- }, registrarsFor(agentDir, params.values, params.answerer));
191
+ }, registrarsFor(params.values, params.answerer));
160
192
  if (!outcome.ok)
161
193
  failStartup(new Error(`deploy stopped: ${outcome.gate}`));
162
194
  console.error(`[fastagent] deployed → ${outcome.runtimeArn}`);
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The production Feishu or Lark app a deploy sets up on the spot when the target's values hold none: the same
3
+ * `channels setup` a person runs by hand, aimed at the production value file (channel-setup.md §6).
4
+ */
5
+ import type { DeclaredChannel } from "../../../channels/discover.ts";
6
+ import type { SetupWay } from "../../../channels/setup.ts";
7
+ import type { Person } from "../../person.ts";
8
+ export interface ChannelAppSetup {
9
+ kind: "feishu" | "lark";
10
+ way: SetupWay;
11
+ }
12
+ /** The names of the values `app`'s setup would keep. */
13
+ export declare const channelAppNames: ({ kind, way }: ChannelAppSetup) => string[];
14
+ /** The Feishu and Lark channels whose app has no value yet in `values`. */
15
+ export declare function unpreparedChannelApps(channels: readonly DeclaredChannel[], values: ReadonlyMap<string, string>): ChannelAppSetup[];
16
+ /** Set each of `apps` up for production, its person's steps taken by `person` (absent: at this terminal). */
17
+ export declare function prepareChannelApps(agentDir: string, apps: readonly ChannelAppSetup[], rerun: string, person?: Person): Promise<void>;
@@ -0,0 +1,30 @@
1
+ import { dotEnvPath } from "../../../value-file.js";
2
+ import { setUpChannel, setupNames } from "../../channel-setup.js";
3
+ /** The names of the values `app`'s setup would keep. */
4
+ export const channelAppNames = ({ kind, way }) => setupNames(kind, way);
5
+ /** The Feishu and Lark channels whose app has no value yet in `values`. */
6
+ export function unpreparedChannelApps(channels, values) {
7
+ return ["feishu", "lark"].flatMap((kind) => {
8
+ const channel = channels.find((entry) => entry.name === kind);
9
+ if (!channel)
10
+ return [];
11
+ const way = channel.ingress === "webhook" ? "webhook" : "connection";
12
+ return setupNames(kind, way).some((name) => !values.get(name)?.trim()) ? [{ kind, way }] : [];
13
+ });
14
+ }
15
+ /** Set each of `apps` up for production, its person's steps taken by `person` (absent: at this terminal). */
16
+ export async function prepareChannelApps(agentDir, apps, rerun, person) {
17
+ const file = dotEnvPath(agentDir);
18
+ for (const { kind, way } of apps) {
19
+ console.error(`[fastagent] ${kind}: preparing the production app for ${way} in ${file}`);
20
+ const input = { kind: kind, agentDir, file, way, dev: false, rerun };
21
+ const { after } = await setUpChannel(person === undefined ? input : { ...input, person });
22
+ // What is left (the publish, a scope to approve) is not the deploy's to finish: the person is told.
23
+ for (const line of after) {
24
+ if (person === undefined)
25
+ console.error(`[fastagent] next: ${line}`);
26
+ else
27
+ person.afterwards(line);
28
+ }
29
+ }
30
+ }