@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
@@ -1,9 +1,10 @@
1
1
  /** `fastagent deploy agentcore --run` — drive the AWS CLI + Docker to completion. */
2
2
  import type { BoxLoginStep } from "../box-shell.ts";
3
3
  import type { DeclaredChannel } from "../../channels/discover.ts";
4
- import { type Registrars } from "../channel-ingress.ts";
4
+ import { type Registrars, type UrlRecorder } from "../channel-ingress.ts";
5
5
  import type { CliRunner } from "../runner.ts";
6
6
  import { type AgentcoreTopology } from "./plan.ts";
7
+ import type { SecretStore } from "../secret-store.ts";
7
8
  export interface AgentcoreRunPlan {
8
9
  /** The base name — stack `fastagent-<name>`, ECR repo `fastagent/<name>`. */
9
10
  name: string;
@@ -18,12 +19,12 @@ export interface AgentcoreRunPlan {
18
19
  * region`.
19
20
  */
20
21
  region?: string;
21
- /** Env-var name → value: the value file's carried variables. */
22
- secrets: Record<string, string>;
23
- /** Declared names the value file supplies no value for — the run gates on these before any side effect. */
24
- missingSecrets: string[];
25
- /** That value file, agent-dir-relative, so the gate names the file this deploy actually read. */
26
- valueFile: string;
22
+ /** Makes the secret hold every value the deployment needs (secrets-step.ts), or says what is missing. */
23
+ provideSecrets: (store: SecretStore) => Promise<string | undefined>;
24
+ /** What proves an envelope came from the forwarder: minted by the deploy, a stack parameter. */
25
+ ingressSecret: string;
26
+ /** The wake-alarm shared secret between the container and the forwarder: minted by the deploy, a stack parameter. */
27
+ wakeSecret: string;
27
28
  /** Every declared channel and its ingress — the driver asks which of them have a webhook. */
28
29
  channels: readonly DeclaredChannel[];
29
30
  /**
@@ -32,10 +33,14 @@ export interface AgentcoreRunPlan {
32
33
  */
33
34
  topology: AgentcoreTopology;
34
35
  /**
35
- * Log the runtime in (`fastagent login --deployment`) once the probe verified it and before any webhook points at
36
- * it. Resolves a gate line, or undefined.
36
+ * Log the runtime in (`fastagent login --deployment`) once the probe verified it and before the deploy reads its
37
+ * webhooks. Resolves a gate line, or undefined.
37
38
  */
38
39
  boxLogin?: BoxLoginStep<[runtimeArn: string]>;
40
+ /** The `http.publicUrl` the configuration names (a custom domain); default the forwarder's URL. */
41
+ publicUrl?: string;
42
+ /** At a terminal with a `--config` file: record each URL a person set once the box confirms it. */
43
+ recordUrl?: UrlRecorder;
39
44
  }
40
45
  export type AgentcoreRunOutcome = {
41
46
  ok: true;
@@ -57,7 +62,10 @@ export type AgentcoreRunOutcome = {
57
62
  */
58
63
  export declare function pickStackOutputs(parsed: unknown): Record<string, string> | undefined;
59
64
  /** The `--parameter-overrides file://` payload: a JSON array of "Key=Value" strings. */
60
- export declare function paramsFileContent(imageUri: string, secrets: Record<string, string>, forwarder: {
65
+ export declare function paramsFileContent(imageUri: string, secretArn: string, minted: {
66
+ ingressSecret: string;
67
+ wakeSecret: string;
68
+ }, forwarder: {
61
69
  bucket: string;
62
70
  key: string;
63
71
  }): string;
@@ -1,11 +1,12 @@
1
1
  import { RESERVED_PATHS } from "../../channels/agentcore-protocol.js";
2
- import { loginGate, registerWebhooks } from "../channel-ingress.js";
2
+ import { confirmWebhooks, loginGate, } from "../channel-ingress.js";
3
3
  import { awsCli, awsJson } from "./aws-cli.js";
4
4
  import { createHash } from "node:crypto";
5
5
  import { Buffer } from "node:buffer";
6
- import { CARRIERS, CARRIER_CHUNK_SIZE, CARRIER_MAX_CHUNKS, MOUNT, agentcoreRepoName, agentcoreStackName, carrierChunk, deploymentBucketName, forwarderSource, ingressSessionId, } from "./plan.js";
6
+ import { MOUNT, agentcoreRepoName, agentcoreSecretName, agentcoreStackName, deploymentBucketName, forwarderSource, ingressSessionId, } from "./plan.js";
7
7
  import { zipSingleFile } from "./zip.js";
8
- import { encodeCarriedEnv, missingValuesGate } from "../secrets.js";
8
+ import { RUNTIME_IDENTITY_ROLE, ensureServiceLinkedRole } from "./slr.js";
9
+ import { agentcoreSecretStore } from "./store.js";
9
10
  /** AWS keys are typed, so a coding agent's user signs the CLI in at a terminal of their own. */
10
11
  const AWS_SIGN_IN = "run `aws configure` (or `aws sso login`) in a terminal of your own, where the keys are typed, then re-run";
11
12
  /**
@@ -23,23 +24,51 @@ const EMPTY_STACK_STATUSES = new Set([
23
24
  "REVIEW_IN_PROGRESS",
24
25
  "DELETE_COMPLETE",
25
26
  ]);
27
+ /**
28
+ * How often the deploy asks the runtime how its webhooks went. Each ask is a Lambda and an `InvokeAgentRuntime` that
29
+ * keeps the microVM up and is billed, where another host's `/health` costs nothing.
30
+ */
31
+ const WEBHOOKS_INTERVAL_MS = 5_000;
26
32
  /** Budget for image pull, storage initialization and channel construction. */
27
33
  const PROBE_TIMEOUT_MS = 240_000;
28
34
  const PROBE_INTERVAL_MS = 3_000;
35
+ /** One call to the forwarder's reserved probe path. */
36
+ async function askProbe(probeUrl, auth, fetchImpl) {
37
+ const res = await fetchImpl(probeUrl, {
38
+ method: "POST",
39
+ headers: { "content-type": "application/json" },
40
+ body: JSON.stringify({ auth }),
41
+ signal: AbortSignal.timeout(65_000),
42
+ });
43
+ return { status: res.status, body: await res.text() };
44
+ }
45
+ /**
46
+ * The runtime's own report of its webhook channels, through the probe: the container has no `/health` of its own to
47
+ * read, and the forwarder relays only this path. The address it is handed is the other hosts' `/health`, which this
48
+ * one has no use for.
49
+ */
50
+ function forwarderWebhooks(probeUrl, auth, fetchImpl) {
51
+ return async () => {
52
+ try {
53
+ const { status, body } = await askProbe(probeUrl, auth, fetchImpl);
54
+ const verdict = status === 200 ? JSON.parse(body) : undefined;
55
+ return verdict?.ok === true && Array.isArray(verdict.webhooks)
56
+ ? verdict.webhooks
57
+ : undefined;
58
+ }
59
+ catch {
60
+ return undefined; // polled again until the deadline
61
+ }
62
+ };
63
+ }
29
64
  /** Drive the forwarder's reserved probe path until it answers, and read the runtime's STRUCTURED verdict. */
30
65
  async function probeRuntime(probeUrl, auth, fetchImpl, timeoutMs = PROBE_TIMEOUT_MS, intervalMs = PROBE_INTERVAL_MS) {
31
66
  const deadline = Date.now() + timeoutMs;
32
67
  let last;
33
68
  for (;;) {
34
69
  try {
35
- const res = await fetchImpl(probeUrl, {
36
- method: "POST",
37
- headers: { "content-type": "application/json" },
38
- body: JSON.stringify({ auth }),
39
- signal: AbortSignal.timeout(65_000),
40
- });
41
- const bodyText = await res.text();
42
- if (res.status === 200) {
70
+ const { status, body: bodyText } = await askProbe(probeUrl, auth, fetchImpl);
71
+ if (status === 200) {
43
72
  let verdict;
44
73
  try {
45
74
  verdict = JSON.parse(bodyText);
@@ -55,7 +84,7 @@ async function probeRuntime(probeUrl, auth, fetchImpl, timeoutMs = PROBE_TIMEOUT
55
84
  }
56
85
  }
57
86
  const firstLine = bodyText.trim().split("\n")[0] ?? "";
58
- last = `${res.status}${firstLine ? ` ${firstLine}` : ""}`;
87
+ last = `${status}${firstLine ? ` ${firstLine}` : ""}`;
59
88
  }
60
89
  catch {
61
90
  // not routable yet (Function URL DNS, cold start) — keep polling until the deadline
@@ -93,32 +122,16 @@ export function pickStackOutputs(parsed) {
93
122
  }
94
123
  return out;
95
124
  }
96
- /** The secrets `--run` mints for this host, each with the template parameter of its own. */
97
- const MINTED_PARAMS = {
98
- FASTAGENT_INGRESS_SECRET: "FastagentIngressSecret",
99
- FASTAGENT_WAKE_SECRET: "FastagentWakeSecret",
100
- };
101
- /** What each carrier holds for this deploy: every variable but the minted ones, as one encoded object. */
102
- function carrierValues(secrets) {
103
- const carried = Object.fromEntries(Object.entries(secrets).filter(([name]) => !(name in MINTED_PARAMS)));
104
- return { FASTAGENT_ENV: encodeCarriedEnv(carried) };
105
- }
106
125
  /** The `--parameter-overrides file://` payload: a JSON array of "Key=Value" strings. */
107
- export function paramsFileContent(imageUri, secrets, forwarder) {
108
- const params = [`ImageUri=${imageUri}`, `ForwarderBucket=${forwarder.bucket}`, `ForwarderS3Key=${forwarder.key}`];
109
- for (const [name, param] of Object.entries(MINTED_PARAMS)) {
110
- if (secrets[name] !== undefined)
111
- params.push(`${param}=${secrets[name]}`);
112
- }
113
- // Every chunk is written, empty ones included: an omitted parameter keeps its PREVIOUS value on a stack update.
114
- const values = carrierValues(secrets);
115
- for (const carrier of CARRIERS) {
116
- const value = values[carrier.env];
117
- for (let i = 0; i < CARRIER_MAX_CHUNKS; i++) {
118
- params.push(`${carrierChunk(carrier, i).param}=${value.slice(i * CARRIER_CHUNK_SIZE, (i + 1) * CARRIER_CHUNK_SIZE)}`);
119
- }
120
- }
121
- return `${JSON.stringify(params)}\n`;
126
+ export function paramsFileContent(imageUri, secretArn, minted, forwarder) {
127
+ return `${JSON.stringify([
128
+ `ImageUri=${imageUri}`,
129
+ `SecretArn=${secretArn}`,
130
+ `ForwarderBucket=${forwarder.bucket}`,
131
+ `ForwarderS3Key=${forwarder.key}`,
132
+ `FastagentIngressSecret=${minted.ingressSecret}`,
133
+ `FastagentWakeSecret=${minted.wakeSecret}`,
134
+ ])}\n`;
122
135
  }
123
136
  /** Run the deploy through `aws` + `docker`. */
124
137
  export async function deployAgentcoreRun(plan, aws, docker, log, writeSecretFile, writeForwarderZip, registrars,
@@ -163,21 +176,6 @@ probe = {}) {
163
176
  if ((await docker(["buildx", "version"], { capture: true })).code !== 0) {
164
177
  return gate("docker buildx not available — the image must be linux/arm64 (cross-built); install buildx, then re-run");
165
178
  }
166
- // 3. Gate missing required secret VALUES before any side effect (no half-created infra).
167
- const missingValues = missingValuesGate(plan.missingSecrets, plan.valueFile);
168
- if (missingValues)
169
- return gate(missingValues);
170
- // 3b.
171
- const capacity = CARRIER_CHUNK_SIZE * CARRIER_MAX_CHUNKS;
172
- const carried = carrierValues(plan.secrets);
173
- if (carried.FASTAGENT_ENV.length > capacity) {
174
- return gate(`the variables in ${plan.valueFile} are too large to carry (${carried.FASTAGENT_ENV.length} chars encoded > ` +
175
- `${capacity}) — move what the deployment does not need out of that file`);
176
- }
177
- // Name what travels from the value file onto the runtime.
178
- const secretNames = Object.keys(plan.secrets);
179
- if (secretNames.length > 0)
180
- log(`carrying ${secretNames.length} secret(s): ${secretNames.join(", ")}`);
181
179
  // 3c. Say what this deploy is ABOUT TO TOUCH while it can still be stopped for free: until here the account and
182
180
  // region only surfaced several hundred log lines in, inside the ECR image URI.
183
181
  const source = plan.region ? "the environment" : "aws configure";
@@ -203,7 +201,12 @@ probe = {}) {
203
201
  `cloudformation deploy fails minutes from now, after the image build: ` +
204
202
  `https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/agentcore-regions.html`);
205
203
  }
206
- // 3d. Read the stack here, for the two questions that both depend on it: does this deploy destroy the agent's
204
+ // 3d. The role the runtime needs in this account, made if a first deploy finds it missing: a stop here costs nothing,
205
+ // and the same fault found by CloudFormation costs the build.
206
+ const roleGate = await ensureServiceLinkedRole(cli, RUNTIME_IDENTITY_ROLE, log);
207
+ if (roleGate !== undefined)
208
+ return gate(roleGate);
209
+ // 3e. Read the stack here, for the two questions that both depend on it: does this deploy destroy the agent's
207
210
  // memory (below), and is there a failed first create to clear (step 7)? The destructive one is only worth saying
208
211
  // while the multi-minute build has not run yet, and step 7 reuses this answer unless it was still in flight.
209
212
  // An unanswered question must not read as "a first deploy, nothing to lose": `sts get-caller-identity` succeeding
@@ -236,6 +239,18 @@ probe = {}) {
236
239
  `sessions, channel state and pending wake-ups start blank. ` +
237
240
  `Cross-deploy memory needs a real volume: \`deploy fly\` or \`deploy railway\`.`);
238
241
  }
242
+ // 3f. The deployment's values, after the only questions that precede a write and before the build: the secret the stack reads, made to hold every name the definition
243
+ // requires (a coding agent's run, with nobody to ask, stops here naming the commands).
244
+ const secretStore = agentcoreSecretStore(aws, agentcoreSecretName(plan.name), log);
245
+ try {
246
+ await secretStore.ensure();
247
+ }
248
+ catch (error) {
249
+ return gate(error instanceof Error ? error.message : String(error));
250
+ }
251
+ const secretsGate = await plan.provideSecrets(secretStore);
252
+ if (secretsGate !== undefined)
253
+ return gate(secretsGate);
239
254
  // 4.
240
255
  const registry = `${account}.dkr.ecr.${region}.amazonaws.com`;
241
256
  const image = `${registry}/${repo}:${plan.tag}`;
@@ -306,7 +321,7 @@ probe = {}) {
306
321
  if ((await docker(buildArgs)).code !== 0) {
307
322
  return gate("`docker buildx build` failed — see the output above; fix and re-run");
308
323
  }
309
- // 7. The 3d answer stands unless it was still settling then — a second read is the narrow exception, not the rule.
324
+ // 7. The 3e answer stands unless it was still settling then — a second read is the narrow exception, not the rule.
310
325
  const beforeDeploy = settling ? await readStackStatus() : stackStatus;
311
326
  if ("ok" in beforeDeploy && beforeDeploy.ok === "ROLLBACK_COMPLETE") {
312
327
  log(`stack ${stack} is ROLLBACK_COMPLETE (a failed first create) — deleting it before re-creating…`);
@@ -318,7 +333,14 @@ probe = {}) {
318
333
  }
319
334
  }
320
335
  log(`deploying stack ${stack}…`);
321
- const paramsPath = await writeSecretFile(paramsFileContent(image, plan.secrets, forwarderParams));
336
+ let secretArn;
337
+ try {
338
+ secretArn = await secretStore.arn();
339
+ }
340
+ catch (error) {
341
+ return gate(error instanceof Error ? error.message : String(error));
342
+ }
343
+ const paramsPath = await writeSecretFile(paramsFileContent(image, secretArn, plan, forwarderParams));
322
344
  const deployed = await aws([
323
345
  "cloudformation",
324
346
  "deploy",
@@ -386,7 +408,7 @@ probe = {}) {
386
408
  // Verify storage initialization and channel construction before registering webhooks.
387
409
  if (url) {
388
410
  log("probing the deployed runtime (storage initialization + channel construction)…");
389
- const verdict = await probeRuntime(`${url}${RESERVED_PATHS.probe}`, plan.secrets.FASTAGENT_INGRESS_SECRET ?? "", probe.fetchImpl ?? fetch, probe.timeoutMs, probe.intervalMs);
411
+ const verdict = await probeRuntime(`${url}${RESERVED_PATHS.probe}`, plan.ingressSecret, probe.fetchImpl ?? fetch, probe.timeoutMs, probe.intervalMs);
390
412
  if (!verdict.ok)
391
413
  return gate(verdict.gate);
392
414
  log("runtime verified (storage ready, channels constructed)");
@@ -397,22 +419,23 @@ probe = {}) {
397
419
  notLoggedIn,
398
420
  channels: plan.channels,
399
421
  log,
400
- // The forwarder URL outlives a redeploy, so the webhooks an earlier deploy registered already point at it.
401
- ...(redeploy
402
- ? { afterLogin: "they work again: the forwarder URL is unchanged, so nothing needs pointing" }
403
- : { baseUrl: url, afterLogin: "point the channels by hand (a redeploy would wipe that login again)" }),
422
+ // The runtime points its own channels at the forwarder URL as the probe wakes it, login or not.
423
+ afterLogin: "they work again: the runtime has pointed its own channels, so nothing needs pointing",
404
424
  }));
405
425
  }
406
- // 9.
407
- const registrationGateMsg = url
408
- ? await registerWebhooks({
409
- baseUrl: url,
410
- channels: plan.channels,
411
- registrars,
412
- log,
413
- retryHint: "re-run to retry registration (steps already done are skipped)",
414
- })
415
- : undefined;
426
+ // 9. The runtime points its own channels as the probe wakes it; this reads what it made of them.
427
+ const registrationGateMsg = await confirmWebhooks({
428
+ baseUrl: url,
429
+ publicUrl: plan.publicUrl ?? url,
430
+ channels: plan.channels,
431
+ registrars,
432
+ log,
433
+ ...(plan.recordUrl === undefined ? {} : { record: plan.recordUrl }),
434
+ retryHint: "re-run to retry registration (steps already done are skipped)",
435
+ probe: forwarderWebhooks(`${url}${RESERVED_PATHS.probe}`, plan.ingressSecret, probe.fetchImpl ?? fetch),
436
+ intervalMs: probe.intervalMs ?? WEBHOOKS_INTERVAL_MS,
437
+ ...(probe.timeoutMs === undefined ? {} : { timeoutMs: probe.timeoutMs }),
438
+ });
416
439
  if (registrationGateMsg)
417
440
  return gate(registrationGateMsg);
418
441
  return { ok: true, runtimeArn, url };
@@ -0,0 +1,7 @@
1
+ /** The secret's JSON object, every value a string. */
2
+ export declare function readSecretValues(secretArn: string): Promise<Record<string, string>>;
3
+ /**
4
+ * Put the secret's values into `env`, unless the platform set the name itself (the platform's variables win, as they do
5
+ * over a value file anywhere). Without `FASTAGENT_SECRET_ARN` there is nothing to read.
6
+ */
7
+ export declare function applyRuntimeSecrets(env?: NodeJS.ProcessEnv): Promise<void>;
@@ -0,0 +1,108 @@
1
+ /**
2
+ * What the AgentCore container does with its deployment's values: read the Secrets Manager secret `FASTAGENT_SECRET_ARN`
3
+ * names and put each name it holds into the environment (agent-artifact.md §6.2).
4
+ *
5
+ * The runtime has no way to reference a secret from an environment variable, so the container reads it itself, on its
6
+ * first invocation. Its credentials are the execution role's, from the microVM's instance-metadata service (IMDSv2); the
7
+ * request is SigV4-signed with the signer `shell.ts` already uses, so no AWS SDK is installed in the image.
8
+ */
9
+ import { SignatureV4 } from "@smithy/signature-v4";
10
+ import { SECRET_ARN_ENV } from "./plan.js";
11
+ import { Sha256 } from "./sigv4.js";
12
+ const IMDS = "http://169.254.169.254/latest";
13
+ const TIMEOUT_MS = 5_000;
14
+ async function imdsText(url, init) {
15
+ const response = await fetch(url, { ...init, signal: AbortSignal.timeout(TIMEOUT_MS) });
16
+ if (!response.ok)
17
+ throw new Error(`${init.method ?? "GET"} ${url} answered ${response.status}`);
18
+ return response.text();
19
+ }
20
+ /** The execution role's credentials, as the metadata service hands them to the microVM. */
21
+ async function roleCredentials() {
22
+ const token = await imdsText(`${IMDS}/api/token`, {
23
+ method: "PUT",
24
+ headers: { "x-aws-ec2-metadata-token-ttl-seconds": "60" },
25
+ });
26
+ const headers = { "x-aws-ec2-metadata-token": token };
27
+ const role = (await imdsText(`${IMDS}/meta-data/iam/security-credentials/`, { headers })).trim();
28
+ const parsed = JSON.parse(await imdsText(`${IMDS}/meta-data/iam/security-credentials/${role}`, { headers }));
29
+ const { AccessKeyId, SecretAccessKey, Token } = (parsed ?? {});
30
+ if (typeof AccessKeyId !== "string" || typeof SecretAccessKey !== "string" || typeof Token !== "string") {
31
+ throw new Error(`the metadata service's credentials for ${role} are not an access key, a secret and a token`);
32
+ }
33
+ return { accessKeyId: AccessKeyId, secretAccessKey: SecretAccessKey, sessionToken: Token };
34
+ }
35
+ /** `arn:<partition>:secretsmanager:<region>:<account>:secret:<name>-<suffix>` → its region. */
36
+ function regionOf(secretArn) {
37
+ const [, , service, region] = secretArn.split(":");
38
+ if (service !== "secretsmanager" || !region)
39
+ throw new Error(`${SECRET_ARN_ENV} is not a Secrets Manager ARN`);
40
+ return region;
41
+ }
42
+ /** The secret's JSON object, every value a string. */
43
+ export async function readSecretValues(secretArn) {
44
+ const region = regionOf(secretArn);
45
+ const hostname = `secretsmanager.${region}.amazonaws.com`;
46
+ const body = JSON.stringify({ SecretId: secretArn });
47
+ const signed = await new SignatureV4({
48
+ service: "secretsmanager",
49
+ region,
50
+ credentials: await roleCredentials(),
51
+ sha256: Sha256,
52
+ }).sign({
53
+ method: "POST",
54
+ protocol: "https:",
55
+ hostname,
56
+ path: "/",
57
+ body,
58
+ headers: {
59
+ host: hostname,
60
+ "content-type": "application/x-amz-json-1.1",
61
+ "x-amz-target": "secretsmanager.GetSecretValue",
62
+ },
63
+ });
64
+ const response = await fetch(`https://${hostname}/`, {
65
+ method: "POST",
66
+ headers: signed.headers,
67
+ body,
68
+ signal: AbortSignal.timeout(TIMEOUT_MS),
69
+ });
70
+ const answer = await response.text();
71
+ // Never the parser's message: it quotes the text around the error, and this document holds every value.
72
+ if (!response.ok)
73
+ throw new Error(`GetSecretValue answered ${response.status}: ${answer.slice(0, 200)}`);
74
+ let values;
75
+ try {
76
+ values = JSON.parse(JSON.parse(answer).SecretString ?? "");
77
+ }
78
+ catch {
79
+ throw new Error(`the secret ${secretArn} is not a JSON object of values`);
80
+ }
81
+ if (values === null || typeof values !== "object" || Array.isArray(values)) {
82
+ throw new Error(`the secret ${secretArn} is not a JSON object of values`);
83
+ }
84
+ for (const [name, value] of Object.entries(values)) {
85
+ if (typeof value !== "string")
86
+ throw new Error(`the secret ${secretArn}: ${name} is not a string`);
87
+ }
88
+ return values;
89
+ }
90
+ /**
91
+ * Put the secret's values into `env`, unless the platform set the name itself (the platform's variables win, as they do
92
+ * over a value file anywhere). Without `FASTAGENT_SECRET_ARN` there is nothing to read.
93
+ */
94
+ export async function applyRuntimeSecrets(env = process.env) {
95
+ const arn = env[SECRET_ARN_ENV];
96
+ if (!arn)
97
+ return;
98
+ let values;
99
+ try {
100
+ values = await readSecretValues(arn);
101
+ }
102
+ catch (error) {
103
+ throw new Error(`could not read the deployment's values from ${arn}: ${error instanceof Error ? error.message : String(error)}`);
104
+ }
105
+ for (const [name, value] of Object.entries(values))
106
+ if (!(name in env))
107
+ env[name] = value;
108
+ }
@@ -13,14 +13,15 @@
13
13
  * terminal, so the command switches it to raw, no echo (answers are not echoed back, and a long line is not cut by
14
14
  * the terminal's line editor) before handing it to `exec`.
15
15
  */
16
- import { createHash, createHmac, randomUUID } from "node:crypto";
16
+ import { randomUUID } from "node:crypto";
17
17
  import { mkdtemp, readFile, rm } from "node:fs/promises";
18
18
  import { tmpdir } from "node:os";
19
19
  import { join } from "node:path";
20
20
  import { PassThrough, Writable } from "node:stream";
21
21
  import { SignatureV4 } from "@smithy/signature-v4";
22
- import { WebSocket, ping } from "undici";
22
+ import { RuntimeWebSocket, pinger } from "../../runtime-websocket.js";
23
23
  import { awsCli, awsJson } from "./aws-cli.js";
24
+ import { Sha256 } from "./sigv4.js";
24
25
  import { MOUNT, PROBE_PAYLOAD } from "./plan.js";
25
26
  const STDIN = 0x00;
26
27
  const STDOUT = 0x01;
@@ -29,6 +30,7 @@ const STATUS = 0x03;
29
30
  const HEARTBEAT = 0x05;
30
31
  /** The service closes a connection whose frame is larger (code 1009). */
31
32
  const MAX_PAYLOAD = 64 * 1024 - 1;
33
+ const WS_OPEN = 1;
32
34
  /** The SDK's interval; a connection with no ping for ~60s is dropped by the service. */
33
35
  const KEEPALIVE_MS = 30_000;
34
36
  /** The longest a presigned shell URL may live (the SDK refuses more). */
@@ -49,20 +51,6 @@ async function awsCliCredentials(aws) {
49
51
  return read.ok;
50
52
  throw new Error(`could not read AWS credentials from the AWS CLI (${"absent" in read ? "none configured" : read.unreadable})`);
51
53
  }
52
- /** SHA-256 / HMAC-SHA-256 over node:crypto, in the shape SigV4's signer takes. */
53
- class Sha256 {
54
- hash;
55
- constructor(secret) {
56
- const key = secret instanceof ArrayBuffer ? Buffer.from(secret) : secret;
57
- this.hash = key === undefined ? createHash("sha256") : createHmac("sha256", key);
58
- }
59
- update(data) {
60
- this.hash.update(data instanceof ArrayBuffer ? Buffer.from(data) : data);
61
- }
62
- async digest() {
63
- return new Uint8Array(this.hash.digest());
64
- }
65
- }
66
54
  /** The runtime's region, from its ARN (`arn:<partition>:bedrock-agentcore:<region>:<account>:runtime/<id>`). */
67
55
  function regionOf(runtimeArn) {
68
56
  const region = runtimeArn.split(":")[3];
@@ -105,7 +93,12 @@ export function shellExit(status) {
105
93
  /** Open `command` in a new shell on `url`: stdin/stdout as streams, the other channels folded in. */
106
94
  export function openShellChannel(url, command) {
107
95
  const output = new PassThrough();
108
- const ws = new WebSocket(url);
96
+ const ws = new RuntimeWebSocket(url);
97
+ const ping = pinger(ws);
98
+ if (ping === undefined) {
99
+ ws.close();
100
+ throw new Error("this runtime cannot send WebSocket pings, and the AgentCore service drops a shell connection that has none for about a minute");
101
+ }
109
102
  ws.binaryType = "arraybuffer";
110
103
  const frame = (channel, payload = Buffer.alloc(0)) => ws.send(Buffer.concat([Buffer.from([channel]), payload]));
111
104
  const stdin = (bytes) => {
@@ -122,7 +115,7 @@ export function openShellChannel(url, command) {
122
115
  ws.addEventListener("open", () => {
123
116
  stdin(Buffer.from(`stty raw -echo 2>/dev/null; exec sh -c '${command}'\n`));
124
117
  keepalive = setInterval(() => {
125
- ping(ws);
118
+ ping();
126
119
  frame(HEARTBEAT);
127
120
  }, KEEPALIVE_MS);
128
121
  });
@@ -157,7 +150,7 @@ export function openShellChannel(url, command) {
157
150
  const input = new Writable({
158
151
  write(chunk, _encoding, done) {
159
152
  void opened.then((open) => {
160
- if (open && ws.readyState === WebSocket.OPEN)
153
+ if (open && ws.readyState === WS_OPEN)
161
154
  stdin(chunk);
162
155
  done();
163
156
  });
@@ -0,0 +1,7 @@
1
+ /** SHA-256 / HMAC-SHA-256 over node:crypto, in the shape SigV4's signer takes. */
2
+ export declare class Sha256 {
3
+ private readonly hash;
4
+ constructor(secret?: string | ArrayBuffer | ArrayBufferView);
5
+ update(data: string | ArrayBuffer | ArrayBufferView): void;
6
+ digest(): Promise<Uint8Array>;
7
+ }
@@ -0,0 +1,15 @@
1
+ import { createHash, createHmac } from "node:crypto";
2
+ /** SHA-256 / HMAC-SHA-256 over node:crypto, in the shape SigV4's signer takes. */
3
+ export class Sha256 {
4
+ hash;
5
+ constructor(secret) {
6
+ const key = secret instanceof ArrayBuffer ? Buffer.from(secret) : secret;
7
+ this.hash = key === undefined ? createHash("sha256") : createHmac("sha256", key);
8
+ }
9
+ update(data) {
10
+ this.hash.update(data instanceof ArrayBuffer ? Buffer.from(data) : data);
11
+ }
12
+ async digest() {
13
+ return new Uint8Array(this.hash.digest());
14
+ }
15
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The service-linked role AgentCore's runtime needs in the account, checked before a deploy builds anything. Without it
3
+ * CloudFormation fails at the runtime, minutes after the image push, with a bare `AccessDenied` that names nothing.
4
+ *
5
+ * Read first, create only when it is absent: IAM authorizes `CreateServiceLinkedRole` before it says a role already
6
+ * exists, so creating unconditionally would refuse a deployer who lacks that permission in every account where the role
7
+ * is long since there. A read that cannot be answered warns and goes on, as the region probe does.
8
+ */
9
+ import type { AwsCli } from "./aws-cli.ts";
10
+ export interface ServiceLinkedRole {
11
+ /** The service principal the role is created for. */
12
+ service: string;
13
+ /** The role's name, which is what `iam get-role` takes. */
14
+ name: string;
15
+ }
16
+ export declare const RUNTIME_IDENTITY_ROLE: ServiceLinkedRole;
17
+ /** A gate line when the role is absent and cannot be made, else undefined. */
18
+ export declare function ensureServiceLinkedRole(cli: AwsCli, role: ServiceLinkedRole, log: (message: string) => void): Promise<string | undefined>;
@@ -0,0 +1,27 @@
1
+ export const RUNTIME_IDENTITY_ROLE = {
2
+ service: "runtime-identity.bedrock-agentcore.amazonaws.com",
3
+ name: "AWSServiceRoleForBedrockAgentCoreRuntimeIdentity",
4
+ };
5
+ /** A gate line when the role is absent and cannot be made, else undefined. */
6
+ export async function ensureServiceLinkedRole(cli, role, log) {
7
+ const found = await cli.present(["iam", "get-role", "--role-name", role.name]);
8
+ if ("ok" in found)
9
+ return undefined;
10
+ if ("unreadable" in found) {
11
+ log(`warn: could not check for the service-linked role ${role.name} (${found.unreadable.split("\n")[0]}) — if the ` +
12
+ `account has never used AgentCore and this principal may not create it, cloudformation deploy fails at the ` +
13
+ `runtime, after the image build`);
14
+ return undefined;
15
+ }
16
+ log(`creating the service-linked role ${role.name}…`);
17
+ const created = await cli.write(["iam", "create-service-linked-role", "--aws-service-name", role.service]);
18
+ if ("done" in created)
19
+ return undefined;
20
+ // Made by another deploy between the read and the write: it is there, which is all that is wanted.
21
+ if ("refused" in created && /has been taken/i.test(created.refused))
22
+ return undefined;
23
+ const why = "refused" in created ? created.refused : "it was not found";
24
+ return (`the service-linked role ${role.name} that AgentCore's runtime needs does not exist in this account, and it could ` +
25
+ `not be created (${why}) — have someone with iam:CreateServiceLinkedRole run ` +
26
+ `\`aws iam create-service-linked-role --aws-service-name ${role.service}\`, then re-run`);
27
+ }
@@ -0,0 +1,7 @@
1
+ import type { CliRunner } from "../runner.ts";
2
+ import type { SecretStore } from "../secret-store.ts";
3
+ export interface AgentcoreSecretStore extends SecretStore {
4
+ /** The secret's ARN, which the stack takes as its `SecretArn` parameter. */
5
+ arn(): Promise<string>;
6
+ }
7
+ export declare function agentcoreSecretStore(aws: CliRunner, secretName: string, log: (message: string) => void): AgentcoreSecretStore;