@fastagent-sh/fastagent 0.29.0 → 0.30.1

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 (312) 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/kernel.js +3 -0
  119. package/dist/cli/program.js +282 -110
  120. package/dist/cli/serve.d.ts +4 -5
  121. package/dist/cli/serve.js +4 -7
  122. package/dist/cli/shared.d.ts +11 -2
  123. package/dist/cli/shared.js +32 -19
  124. package/dist/cli/ticket.d.ts +7 -0
  125. package/dist/cli/ticket.js +106 -30
  126. package/dist/content/declare.d.ts +31 -37
  127. package/dist/content/declare.js +59 -60
  128. package/dist/content/file.d.ts +23 -12
  129. package/dist/content/file.js +48 -35
  130. package/dist/content/git.d.ts +0 -10
  131. package/dist/content/git.js +5 -39
  132. package/dist/content/resolve.d.ts +18 -22
  133. package/dist/content/resolve.js +32 -66
  134. package/dist/content/source.d.ts +7 -8
  135. package/dist/content/source.js +10 -28
  136. package/dist/credentials-page.d.ts +26 -0
  137. package/dist/credentials-page.js +185 -0
  138. package/dist/declared-secrets.d.ts +10 -3
  139. package/dist/declared-secrets.js +8 -4
  140. package/dist/deploy/agentcore/destroy.d.ts +4 -1
  141. package/dist/deploy/agentcore/destroy.js +25 -1
  142. package/dist/deploy/agentcore/forwarder.js +1 -1
  143. package/dist/deploy/agentcore/plan.d.ts +7 -21
  144. package/dist/deploy/agentcore/plan.js +47 -49
  145. package/dist/deploy/agentcore/run.d.ts +18 -10
  146. package/dist/deploy/agentcore/run.js +93 -70
  147. package/dist/deploy/agentcore/runtime-secrets.d.ts +7 -0
  148. package/dist/deploy/agentcore/runtime-secrets.js +108 -0
  149. package/dist/deploy/agentcore/shell.js +12 -19
  150. package/dist/deploy/agentcore/sigv4.d.ts +7 -0
  151. package/dist/deploy/agentcore/sigv4.js +15 -0
  152. package/dist/deploy/agentcore/slr.d.ts +18 -0
  153. package/dist/deploy/agentcore/slr.js +27 -0
  154. package/dist/deploy/agentcore/store.d.ts +7 -0
  155. package/dist/deploy/agentcore/store.js +115 -0
  156. package/dist/deploy/api.d.ts +14 -14
  157. package/dist/deploy/api.js +26 -24
  158. package/dist/deploy/box-shell.d.ts +3 -1
  159. package/dist/deploy/build-context.d.ts +4 -4
  160. package/dist/deploy/build-context.js +11 -15
  161. package/dist/deploy/channel-ingress.d.ts +35 -9
  162. package/dist/deploy/channel-ingress.js +119 -22
  163. package/dist/deploy/container.d.ts +35 -10
  164. package/dist/deploy/container.js +79 -37
  165. package/dist/deploy/delivered-text.d.ts +13 -0
  166. package/dist/deploy/delivered-text.js +86 -0
  167. package/dist/deploy/docker/plan.d.ts +7 -8
  168. package/dist/deploy/docker/plan.js +31 -52
  169. package/dist/deploy/docker/run.d.ts +5 -43
  170. package/dist/deploy/docker/run.js +9 -84
  171. package/dist/deploy/file-store.d.ts +2 -0
  172. package/dist/deploy/file-store.js +17 -0
  173. package/dist/deploy/fly/plan.d.ts +3 -2
  174. package/dist/deploy/fly/plan.js +20 -9
  175. package/dist/deploy/fly/run.d.ts +18 -8
  176. package/dist/deploy/fly/run.js +24 -42
  177. package/dist/deploy/fly/store.d.ts +12 -0
  178. package/dist/deploy/fly/store.js +83 -0
  179. package/dist/deploy/hosts.d.ts +1 -2
  180. package/dist/deploy/hosts.js +1 -2
  181. package/dist/deploy/preflight.d.ts +18 -5
  182. package/dist/deploy/preflight.js +135 -63
  183. package/dist/deploy/railway/ignore.d.ts +8 -0
  184. package/dist/deploy/railway/ignore.js +57 -0
  185. package/dist/deploy/railway/plan.d.ts +1 -2
  186. package/dist/deploy/railway/plan.js +8 -6
  187. package/dist/deploy/railway/run.d.ts +21 -15
  188. package/dist/deploy/railway/run.js +32 -78
  189. package/dist/deploy/railway/store.d.ts +29 -0
  190. package/dist/deploy/railway/store.js +143 -0
  191. package/dist/deploy/runner.d.ts +5 -2
  192. package/dist/deploy/runner.js +4 -3
  193. package/dist/deploy/secret-store.d.ts +63 -0
  194. package/dist/deploy/secret-store.js +98 -0
  195. package/dist/deploy/secrets.d.ts +29 -46
  196. package/dist/deploy/secrets.js +49 -122
  197. package/dist/deploy/workspace.d.ts +6 -12
  198. package/dist/deploy/workspace.js +18 -94
  199. package/dist/dev-supervisor.js +6 -8
  200. package/dist/environment/declare.js +3 -2
  201. package/dist/environment/mise.d.ts +3 -1
  202. package/dist/environment/mise.js +4 -2
  203. package/dist/feishu.d.ts +1 -1
  204. package/dist/feishu.js +1 -1
  205. package/dist/harnesses/models/catalog.d.ts +1 -0
  206. package/dist/harnesses/models/catalog.js +1 -2
  207. package/dist/harnesses/models/environment.d.ts +7 -2
  208. package/dist/harnesses/models/environment.js +9 -5
  209. package/dist/harnesses/models/host.d.ts +0 -7
  210. package/dist/harnesses/models/host.js +10 -1
  211. package/dist/harnesses/models/login.d.ts +0 -8
  212. package/dist/harnesses/models/login.js +5 -4
  213. package/dist/harnesses/models/models.d.ts +7 -2
  214. package/dist/harnesses/models/models.js +28 -4
  215. package/dist/harnesses/pi-durable/agent.js +59 -38
  216. package/dist/harnesses/pi-durable/build.d.ts +12 -0
  217. package/dist/harnesses/pi-durable/build.js +270 -0
  218. package/dist/harnesses/pi-durable/built.d.ts +24 -0
  219. package/dist/harnesses/pi-durable/built.js +136 -0
  220. package/dist/harnesses/pi-durable/config.d.ts +81 -0
  221. package/dist/harnesses/pi-durable/config.js +266 -0
  222. package/dist/harnesses/pi-durable/define.d.ts +63 -13
  223. package/dist/harnesses/pi-durable/define.js +23 -2
  224. package/dist/harnesses/pi-durable/definition.d.ts +72 -0
  225. package/dist/harnesses/pi-durable/definition.js +143 -0
  226. package/dist/harnesses/pi-durable/events.d.ts +4 -3
  227. package/dist/harnesses/pi-durable/events.js +179 -136
  228. package/dist/harnesses/pi-durable/execution-env.d.ts +17 -0
  229. package/dist/harnesses/pi-durable/execution-env.js +11 -0
  230. package/dist/harnesses/pi-durable/mcp.js +1 -3
  231. package/dist/harnesses/pi-durable/open.d.ts +35 -37
  232. package/dist/harnesses/pi-durable/open.js +91 -153
  233. package/dist/harnesses/pi-durable/reads.d.ts +0 -1
  234. package/dist/harnesses/pi-durable/reads.js +1 -3
  235. package/dist/harnesses/pi-durable/registry.d.ts +4 -6
  236. package/dist/harnesses/pi-durable/registry.js +29 -26
  237. package/dist/harnesses/pi-durable/release.d.ts +2 -2
  238. package/dist/harnesses/pi-durable/release.js +10 -7
  239. package/dist/harnesses/pi-durable/serve.d.ts +91 -15
  240. package/dist/harnesses/pi-durable/serve.js +109 -47
  241. package/dist/harnesses/pi-durable/sessions.js +15 -12
  242. package/dist/harnesses/pi-durable/text-sources.d.ts +30 -0
  243. package/dist/harnesses/pi-durable/text-sources.js +55 -0
  244. package/dist/harnesses/pi-durable/text.d.ts +16 -4
  245. package/dist/harnesses/pi-durable/text.js +115 -59
  246. package/dist/harnesses/pi-durable/wake.js +8 -8
  247. package/dist/http/client.js +9 -3
  248. package/dist/http/contract.d.ts +3 -3
  249. package/dist/http/server.d.ts +1 -1
  250. package/dist/http/server.js +1 -1
  251. package/dist/index.d.ts +8 -5
  252. package/dist/index.js +4 -1
  253. package/dist/instance-name.d.ts +7 -0
  254. package/dist/instance-name.js +26 -0
  255. package/dist/lark.d.ts +1 -1
  256. package/dist/lark.js +1 -1
  257. package/dist/mcp/client.js +11 -2
  258. package/dist/mcp/config.d.ts +70 -18
  259. package/dist/mcp/config.js +136 -53
  260. package/dist/mcp/host.d.ts +5 -2
  261. package/dist/mcp/host.js +14 -6
  262. package/dist/mcp/sign-in.js +2 -2
  263. package/dist/mcp-api.d.ts +14 -0
  264. package/dist/paths.d.ts +22 -8
  265. package/dist/paths.js +44 -11
  266. package/dist/runtime-websocket.d.ts +14 -0
  267. package/dist/runtime-websocket.js +39 -0
  268. package/dist/scaffold/add-channel.d.ts +9 -16
  269. package/dist/scaffold/add-channel.js +13 -83
  270. package/dist/scaffold/templates/env.example +4 -5
  271. package/dist/scaffold/templates/fastagent.config.ts +20 -14
  272. package/dist/scaffold/templates/gitignore +9 -6
  273. package/dist/scaffold/templates.d.ts +6 -2
  274. package/dist/scaffold/templates.js +7 -2
  275. package/dist/scaffold/vendor-skill.js +4 -2
  276. package/dist/service.d.ts +44 -6
  277. package/dist/service.js +110 -10
  278. package/dist/serving-command.d.ts +10 -0
  279. package/dist/serving-command.js +1 -0
  280. package/dist/slack.d.ts +1 -1
  281. package/dist/slack.js +1 -1
  282. package/dist/strip-edges.d.ts +10 -0
  283. package/dist/strip-edges.js +22 -0
  284. package/dist/telegram.d.ts +1 -1
  285. package/dist/telegram.js +1 -1
  286. package/dist/tunnel.d.ts +0 -1
  287. package/dist/tunnel.js +2 -8
  288. package/dist/value-file.d.ts +11 -6
  289. package/dist/value-file.js +21 -9
  290. package/dist/version.d.ts +7 -1
  291. package/dist/version.js +9 -3
  292. package/package.json +6 -5
  293. package/dist/channels/lark/credentials-page.d.ts +0 -25
  294. package/dist/channels/lark/credentials-page.js +0 -206
  295. package/dist/channels/lark/onboard.d.ts +0 -52
  296. package/dist/channels/lark/onboard.js +0 -86
  297. package/dist/channels/slack/config-api.d.ts +0 -69
  298. package/dist/channels/slack/config-api.js +0 -162
  299. package/dist/channels/slack/onboard.d.ts +0 -40
  300. package/dist/channels/slack/onboard.js +0 -136
  301. package/dist/channels/slack/onboarding-state.d.ts +0 -31
  302. package/dist/channels/slack/onboarding-state.js +0 -64
  303. package/dist/channels/slack/register-webhook.d.ts +0 -12
  304. package/dist/channels/slack/register-webhook.js +0 -67
  305. package/dist/channels/slack/setup-server.d.ts +0 -32
  306. package/dist/channels/slack/setup-server.js +0 -246
  307. package/dist/cli/add-feishu.d.ts +0 -41
  308. package/dist/cli/add-feishu.js +0 -391
  309. package/dist/cli/add-slack.d.ts +0 -18
  310. package/dist/cli/add-slack.js +0 -216
  311. package/dist/harnesses/pi-durable/parked.d.ts +0 -18
  312. package/dist/harnesses/pi-durable/parked.js +0 -27
@@ -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;
@@ -0,0 +1,115 @@
1
+ /**
2
+ * AgentCore's values as a deployment's store: one Secrets Manager secret per deployment, a JSON object of NAME → value
3
+ * that the container reads on its first invocation (runtime-secrets.ts). It lives outside the stack, as the ECR
4
+ * repository and the artifact bucket do: its values must exist before the first deploy, and a stack update must not be
5
+ * what rewrites them.
6
+ *
7
+ * Reading goes around `awsCli().read`, whose unreadable answer quotes stdout: here stdout is every value.
8
+ */
9
+ import { mkdtemp, rm, writeFile } from "node:fs/promises";
10
+ import { tmpdir } from "node:os";
11
+ import { join } from "node:path";
12
+ import { SECRET_FILE_MODE } from "../../paths.js";
13
+ import { awsCli, awsJson } from "./aws-cli.js";
14
+ const describe = (secretName, ...query) => [
15
+ "secretsmanager",
16
+ "describe-secret",
17
+ "--secret-id",
18
+ secretName,
19
+ ...query,
20
+ ];
21
+ /** The values the secret holds now. A secret that is not a JSON object of strings is refused, naming nothing in it. */
22
+ function parseValues(secretString) {
23
+ let parsed;
24
+ try {
25
+ parsed = JSON.parse(secretString);
26
+ }
27
+ catch {
28
+ throw new Error("its value is not JSON");
29
+ }
30
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
31
+ throw new Error("its value is not a JSON object of NAME → value");
32
+ }
33
+ for (const [name, value] of Object.entries(parsed)) {
34
+ if (typeof value !== "string")
35
+ throw new Error(`its value for ${name} is not a string`);
36
+ }
37
+ return parsed;
38
+ }
39
+ export function agentcoreSecretStore(aws, secretName, log) {
40
+ const cli = awsCli(aws);
41
+ const current = async () => {
42
+ const { code, stdout, stderr } = await aws(["secretsmanager", "get-secret-value", "--secret-id", secretName, "--query", "SecretString", "--output", "text"], { capture: true, captureStderr: true });
43
+ if (code !== 0) {
44
+ throw new Error(`\`aws secretsmanager get-secret-value\` failed: ${(stderr ?? "").trim().slice(0, 300)}`);
45
+ }
46
+ try {
47
+ return parseValues(stdout.trim());
48
+ }
49
+ catch (error) {
50
+ throw new Error(`the secret ${secretName}: ${error instanceof Error ? error.message : String(error)}`);
51
+ }
52
+ };
53
+ const store = {
54
+ async exists() {
55
+ const read = await cli.read(describe(secretName, "--output", "json"), awsJson((parsed) => parsed ?? undefined));
56
+ if ("unreadable" in read)
57
+ throw new Error(`could not read secret ${secretName}: ${read.unreadable}`);
58
+ if ("absent" in read)
59
+ return false;
60
+ if (read.ok.DeletedDate !== undefined) {
61
+ throw new Error(`secret ${secretName} is scheduled for deletion: \`aws secretsmanager restore-secret --secret-id ${secretName}\` ` +
62
+ `brings it back, or wait for the deletion and re-run`);
63
+ }
64
+ return true;
65
+ },
66
+ async ensure() {
67
+ if (await store.exists())
68
+ return;
69
+ log(`creating secret ${secretName}…`);
70
+ const created = await cli.write([
71
+ "secretsmanager",
72
+ "create-secret",
73
+ "--name",
74
+ secretName,
75
+ "--description",
76
+ "FastAgent deployment values",
77
+ "--secret-string",
78
+ "{}",
79
+ ]);
80
+ if (!("done" in created)) {
81
+ throw new Error(`\`aws secretsmanager create-secret\` failed: ${"refused" in created ? created.refused : ""}`);
82
+ }
83
+ },
84
+ async names() {
85
+ return new Set(Object.keys(await current()));
86
+ },
87
+ async write(values) {
88
+ const names = Object.keys(values);
89
+ log(`setting ${names.length} value(s) in ${secretName}: ${names.join(", ")}`);
90
+ // Read, merge, write: one operator at a time, as every host's store assumes.
91
+ const merged = { ...(await current()), ...values };
92
+ // Over a file only this process can read, never argv.
93
+ const dir = await mkdtemp(join(tmpdir(), "fastagent-secret-"));
94
+ try {
95
+ const file = join(dir, "secret.json");
96
+ await writeFile(file, JSON.stringify(merged), { mode: SECRET_FILE_MODE });
97
+ const { code, stderr } = await aws(["secretsmanager", "put-secret-value", "--secret-id", secretName, "--secret-string", `file://${file}`], { capture: true, captureStderr: true });
98
+ if (code !== 0) {
99
+ throw new Error(`\`aws secretsmanager put-secret-value\` failed: ${(stderr ?? "").trim().slice(0, 300)}`);
100
+ }
101
+ }
102
+ finally {
103
+ await rm(dir, { recursive: true, force: true });
104
+ }
105
+ },
106
+ async arn() {
107
+ const read = await cli.read(describe(secretName, "--query", "ARN", "--output", "text"), (stdout) => stdout.trim() || undefined);
108
+ if (!("ok" in read)) {
109
+ throw new Error(`could not read the ARN of secret ${secretName}: ${"unreadable" in read ? read.unreadable : "absent"}`);
110
+ }
111
+ return read.ok;
112
+ },
113
+ };
114
+ return store;
115
+ }
@@ -1,34 +1,34 @@
1
- import type { AgentConfig } from "../harnesses/pi-durable/define.ts";
2
- /** Whether the deployment serves the API (and whether this deploy gives it its key), and why not when it does not. */
1
+ import type { EffectiveConfig } from "../harnesses/pi-durable/config.ts";
2
+ /** Whether the deployment serves the API, and why not when it does not. */
3
3
  export type DeploymentApi = {
4
4
  served: true;
5
- generatesKey: boolean;
6
5
  } | {
7
6
  served: false;
8
7
  why: string;
9
8
  };
10
9
  /**
11
- * Decide the API for one deploy, writing nothing: a deploy the pre-flight refuses leaves the value file as it was.
12
- * Turned off, it is off on the deployment too, a key the values or the host still hold included: the serve reads the
13
- * same setting.
10
+ * Decide the API for one deploy, writing nothing. Turned off, it is off on the deployment too, a key the target still
11
+ * holds included: the serve reads the same setting.
14
12
  */
15
13
  export declare function deploymentApi(input: {
16
- config: AgentConfig;
14
+ config: EffectiveConfig;
17
15
  /** The host serves the API at all: not AgentCore, reached only through `/invocations`. */
18
16
  hostServes: boolean;
19
- values: ReadonlyMap<string, string>;
20
17
  }): DeploymentApi;
21
- /**
22
- * Give the deployment its key: into the value file, from which it travels with the other values, once the pre-flight
23
- * passes (a host's own check may still stop this deploy; the key stays for the next). Answers a secrets directory it
24
- * wrote into that git may track.
25
- */
26
- export declare function writeApiKey(agentDir: string): Promise<string | undefined>;
18
+ /** A key for the Agent API, made here: FastAgent mints it, so no person holds it before the deploy. */
19
+ export declare function mintApiKey(): {
20
+ name: string;
21
+ make(): string;
22
+ };
27
23
  /**
28
24
  * What a deploy says of the API, once the host has said where the deployment is reached (when it knows). The check
29
25
  * names the agent's directory when the command runs elsewhere, since that is where `invoke --url` finds the key.
26
+ * `keyInFile`: this machine's production value file holds a key, which `invoke --url` then reads. `run`: the deploy
27
+ * has set the key (or found one) on the target; without it, nothing was set.
30
28
  */
31
29
  export declare function apiReport(api: DeploymentApi, where: {
32
30
  valueFile: string;
33
31
  agentArg: string | undefined;
32
+ keyInFile: boolean;
33
+ run: boolean;
34
34
  }, reachedAt: string | undefined): string[];
@@ -1,50 +1,52 @@
1
1
  /**
2
2
  * A deployment's Agent API (SPEC.md §9.5, §9.6): served under `/v1` on every host but AgentCore, unless the
3
- * definition turns it off (`http.api: false`), behind a key `deploy` generates into the value file the first time,
4
- * which then travels with every other value. And what a deploy reports of it.
3
+ * definition turns it off (`http.api: false`), behind a key the deploy mints and sets on the target the first time
4
+ * neither the target nor the production value file has one. And what a deploy reports of it.
5
5
  */
6
6
  import { randomBytes } from "node:crypto";
7
7
  import { API_PREFIX } from "../http/contract.js";
8
- import { API_KEY_VARIABLE, API_TURNED_OFF, apiKeysFrom, apiTurnedOff } from "../http/server.js";
9
- import { writeValues } from "../value-file.js";
8
+ import { API_KEY_VARIABLE, API_TURNED_OFF, apiTurnedOff } from "../http/server.js";
10
9
  /**
11
- * Decide the API for one deploy, writing nothing: a deploy the pre-flight refuses leaves the value file as it was.
12
- * Turned off, it is off on the deployment too, a key the values or the host still hold included: the serve reads the
13
- * same setting.
10
+ * Decide the API for one deploy, writing nothing. Turned off, it is off on the deployment too, a key the target still
11
+ * holds included: the serve reads the same setting.
14
12
  */
15
13
  export function deploymentApi(input) {
16
- const { config, hostServes, values } = input;
14
+ const { config, hostServes } = input;
17
15
  if (!hostServes)
18
16
  return { served: false, why: "this host reaches the agent only through its own invocations" };
19
17
  if (apiTurnedOff(config))
20
18
  return { served: false, why: API_TURNED_OFF };
21
- return { served: true, generatesKey: apiKeysFrom(values.get(API_KEY_VARIABLE)).length === 0 };
19
+ return { served: true };
22
20
  }
23
- /**
24
- * Give the deployment its key: into the value file, from which it travels with the other values, once the pre-flight
25
- * passes (a host's own check may still stop this deploy; the key stays for the next). Answers a secrets directory it
26
- * wrote into that git may track.
27
- */
28
- export async function writeApiKey(agentDir) {
29
- const key = randomBytes(32).toString("base64url");
30
- // Named in `overwrite`: a line that holds no key (empty, or only commas) is replaced, not kept as set.
31
- const { unprotectedSecretsDir } = await writeValues(agentDir, "Agent API", [{ name: API_KEY_VARIABLE, value: key }], [API_KEY_VARIABLE]);
32
- return unprotectedSecretsDir;
21
+ /** A key for the Agent API, made here: FastAgent mints it, so no person holds it before the deploy. */
22
+ export function mintApiKey() {
23
+ return { name: API_KEY_VARIABLE, make: () => randomBytes(32).toString("base64url") };
33
24
  }
34
25
  /**
35
26
  * What a deploy says of the API, once the host has said where the deployment is reached (when it knows). The check
36
27
  * names the agent's directory when the command runs elsewhere, since that is where `invoke --url` finds the key.
28
+ * `keyInFile`: this machine's production value file holds a key, which `invoke --url` then reads. `run`: the deploy
29
+ * has set the key (or found one) on the target; without it, nothing was set.
37
30
  */
38
31
  export function apiReport(api, where, reachedAt) {
39
32
  if (!api.served)
40
33
  return [`Agent API: not served — ${api.why}`];
41
- const { valueFile, agentArg } = where;
34
+ const { valueFile, agentArg, keyInFile, run } = where;
42
35
  const base = reachedAt ?? "<the deployment's URL>";
36
+ if (!keyInFile && !run) {
37
+ return [
38
+ `Agent API: ${base}${API_PREFIX}, behind ${API_KEY_VARIABLE}, which --run makes and sets on the target when ` +
39
+ `neither it nor ${valueFile} has one`,
40
+ ];
41
+ }
42
+ if (!keyInFile) {
43
+ return [
44
+ `Agent API: ${base}${API_PREFIX}, behind ${API_KEY_VARIABLE} on the target; ${valueFile} holds none, so pass the key ` +
45
+ `to \`fastagent invoke --url\` with --key`,
46
+ ];
47
+ }
43
48
  return [
44
- ...(api.generatesKey
45
- ? [`Agent API: generated ${API_KEY_VARIABLE} in ${valueFile}; it travels with the other values`]
46
- : []),
47
- `Agent API: ${base}${API_PREFIX}, behind ${API_KEY_VARIABLE} in ${valueFile}`,
49
+ `Agent API: ${base}${API_PREFIX}, behind ${API_KEY_VARIABLE} (in ${valueFile})`,
48
50
  `check it: fastagent invoke --url ${base} "hello"${agentArg === undefined ? "" : ` ${agentArg}`}`,
49
51
  ];
50
52
  }
@@ -21,9 +21,11 @@ export interface BoxShell {
21
21
  wake?(): Promise<void>;
22
22
  /**
23
23
  * The box's storage root, for a shell that does not carry the server's environment (AgentCore's). Unset: the
24
- * shell sees `FASTAGENT_STORAGE_DIR` as the server does, or the image's default.
24
+ * shell sees `FASTAGENT_ROOT` as the server does, or the image's default.
25
25
  */
26
26
  storage?: string;
27
+ /** Where the box's image has the agent. Unset: where a generated image has it. */
28
+ program?: string;
27
29
  }
28
30
  /** A host CLI that runs a command on the box itself: `bin ...args(command)`, run from `cwd`. */
29
31
  export declare function processShell(bin: string, args: (command: string) => string[], cwd: string): BoxShell;
@@ -5,12 +5,12 @@ export interface BuildContextPaths {
5
5
  machineryPaths: string[];
6
6
  /** Credential files that exist right now — a kept ignore file that lets one through bakes it. */
7
7
  leakCandidates: string[];
8
- /** The state root, when it exists inside the context. */
8
+ /** A state root of the agent's own choosing inside the context, when it exists. */
9
9
  stateShips?: string;
10
- /** `content/`, when it exists: this machine's clones. */
10
+ /** `.fastagent/`, when it exists: the local instances' state and clones, and a build's staging. */
11
+ instancesShip?: string;
12
+ /** `content/`, when it exists: clones in the root, which a host refuses in a release. */
11
13
  contentShips?: string;
12
- /** `context.local.json`, when it exists: the directories of this machine the agent is bound to. */
13
- bindingsShip?: string;
14
14
  /** The agent's node_modules, when it exists. */
15
15
  depDirs: string[];
16
16
  }
@@ -1,9 +1,8 @@
1
1
  import { readdir } from "node:fs/promises";
2
2
  import { isAbsolute, relative, join, sep } from "node:path";
3
3
  import ignore from "ignore";
4
- import { AGENT_CONFIG_FILE, CONTENT_DIRNAME, LOCAL_CONTEXT_FILE, SECRETS_DIRNAME, exists, readTextIfExists, resolveSecretsDir, resolveStateRoot, } from "../paths.js";
5
- import { isGeneratedDockerignore } from "./container.js";
6
- import { MCP_FILE } from "../mcp/config.js";
4
+ import { AGENT_CONFIG_FILE, CONTENT_DIRNAME, INSTANCES_DIRNAME, SECRETS_DIRNAME, exists, readTextIfExists, resolveSecretsDir, resolveStateRoot, } from "../paths.js";
5
+ import { DELIVERED_CONFIG_DIR, isGeneratedDockerignore } from "./container.js";
7
6
  /**
8
7
  * "Would docker's packer drop this path?" — built from a `.dockerignore`'s text via the `ignore` matcher (the same
9
8
  * library the scaffolded ignore files use), so `!` negation and last-match-wins are the library's problem, not ours.
@@ -65,10 +64,11 @@ export async function buildContextPaths(agentDir, authPath) {
65
64
  // State gets the same treatment (a custom in-tree FASTAGENT_STATE_DIR is invisible to the name-based `**/.state`),
66
65
  // at warn level.
67
66
  const stateRel = inContext(resolveStateRoot(agentDir));
67
+ const inInstances = stateRel !== undefined && (stateRel === INSTANCES_DIRNAME || stateRel.startsWith(`${INSTANCES_DIRNAME}/`));
68
68
  // Existence gates the WARNING, never the generated exclude (same split as secretPaths vs leakCandidates).
69
- const stateShips = stateRel !== undefined && (await exists(join(agentDir, stateRel))) ? stateRel : undefined;
69
+ const stateShips = stateRel !== undefined && !inInstances && (await exists(join(agentDir, stateRel))) ? stateRel : undefined;
70
+ const instancesShip = (await exists(join(agentDir, INSTANCES_DIRNAME))) ? INSTANCES_DIRNAME : undefined;
70
71
  const contentShips = (await exists(join(agentDir, CONTENT_DIRNAME))) ? CONTENT_DIRNAME : undefined;
71
- const bindingsShip = (await exists(join(agentDir, LOCAL_CONTEXT_FILE))) ? LOCAL_CONTEXT_FILE : undefined;
72
72
  // The `.env` family at the level fastagent is RESPONSIBLE for: the agent dir's root.
73
73
  const envFiles = (await entriesIfExists(agentDir))
74
74
  .map((entry) => entry.name)
@@ -95,13 +95,13 @@ export async function buildContextPaths(agentDir, authPath) {
95
95
  ];
96
96
  // Same existence rule: a node_modules that is not there cannot be uploaded.
97
97
  const depDirs = await present(["node_modules"]);
98
- const machineryPaths = [...secretPaths, ...(stateRel ? [stateRel] : [])];
99
- return { machineryPaths, leakCandidates, stateShips, contentShips, bindingsShip, depDirs };
98
+ const machineryPaths = [...secretPaths, ...(stateRel && !inInstances ? [stateRel] : [])];
99
+ return { machineryPaths, leakCandidates, stateShips, instancesShip, contentShips, depDirs };
100
100
  }
101
101
  /** Interrogate BOTH ignore files deploy emits, when the author kept them. */
102
102
  export async function checkKeptIgnoreFiles(ctx, report) {
103
103
  const { agentDir, force } = ctx;
104
- const { leakCandidates, stateShips, contentShips, bindingsShip, depDirs } = ctx.paths;
104
+ const { leakCandidates, stateShips, instancesShip, contentShips, depDirs } = ctx.paths;
105
105
  for (const rel of [".dockerignore", "Dockerfile.dockerignore"]) {
106
106
  const kept = await readTextIfExists(join(agentDir, rel));
107
107
  if (kept === undefined)
@@ -121,9 +121,6 @@ export async function checkKeptIgnoreFiles(ctx, report) {
121
121
  `config (the deployed box has no agent to open and crash-loops). Remove that rule before deploying.`;
122
122
  report.issue(text);
123
123
  }
124
- if (excluded(MCP_FILE) && (await exists(join(agentDir, MCP_FILE)))) {
125
- report.issue(`your ${rel} excludes ${MCP_FILE}; the deployment would lose its MCP servers. Remove that rule before deploying.`);
126
- }
127
124
  // Resolved paths, not spellings: dockerignore patterns are root-anchored (unlike .gitignore).
128
125
  const leaks = leakCandidates.filter((p) => !excluded(p));
129
126
  if (leaks.length > 0) {
@@ -134,14 +131,13 @@ export async function checkKeptIgnoreFiles(ctx, report) {
134
131
  if (stateShips && !excluded(`${stateShips}/sessions`)) {
135
132
  report.warn(`your ${rel} (kept) does not exclude \`${stateShips}\` — the build machine's sessions/channel state would ship in the image. ${remedy([`/${stateShips}`])}`);
136
133
  }
134
+ if (instancesShip && !excluded(`${instancesShip}/dev/content/x`)) {
135
+ report.warn(`your ${rel} (kept) does not exclude \`${instancesShip}\` — the build machine's sessions, channel state and clones of the agent's content would ship in the image. ${remedy([`/${instancesShip}`, `!/${DELIVERED_CONFIG_DIR}`])}`);
136
+ }
137
137
  // An issue, not a warning: a release holding content/ is refused on the host, which links its own there.
138
138
  if (contentShips && !excluded(`${contentShips}/x`)) {
139
139
  report.issue(`your ${rel} (kept) does not exclude \`${contentShips}\` — this machine's clones of the agent's content would ship in the image. ${remedy([`/${contentShips}`])}`);
140
140
  }
141
- // A warning: the host reads it, and the paths it names (this machine's, the user's name in them) are not there.
142
- if (bindingsShip && !excluded(bindingsShip)) {
143
- report.warn(`your ${rel} (kept) does not exclude \`${bindingsShip}\` — the directories of this machine the agent is bound to would ship in the image, and the host would look for them at every start. ${remedy([`/${bindingsShip}`])}`);
144
- }
145
141
  const unexcludedDeps = depDirs.filter((p) => !excluded(`${p}/.package-lock.json`));
146
142
  if (unexcludedDeps.length > 0) {
147
143
  report.warn(`your ${rel} does not exclude ${unexcludedDeps.map((p) => `\`${p}\``).join(" or ")} — the ` +
@@ -1,10 +1,9 @@
1
1
  /** HOW A RUNNING CHANNEL IS REACHED — the URL-neutral half of "point this channel at the agent". */
2
2
  import type { DeclaredChannel } from "../channels/discover.ts";
3
3
  import type { RegistrationOutcome } from "../channels/registration.ts";
4
- import type { ChannelKind } from "../scaffold/add-channel.ts";
4
+ import { type WebhookKind, type WebhookRegistration } from "../channels/webhooks.ts";
5
5
  export interface Registrars {
6
6
  telegram: (baseUrl: string) => Promise<RegistrationOutcome>;
7
- slack?: (baseUrl: string) => Promise<RegistrationOutcome>;
8
7
  feishu?: (baseUrl: string, kind: "feishu" | "lark") => Promise<RegistrationOutcome>;
9
8
  /**
10
9
  * Where a channel's manual step goes beside the log, for a person who reads no log: a coding agent's user, through
@@ -12,11 +11,6 @@ export interface Registrars {
12
11
  */
13
12
  leave?: (step: string) => void;
14
13
  }
15
- /**
16
- * The first-party channels this deployment must point at a URL: webhook ingress, and a kind this tool knows how to
17
- * instruct.
18
- */
19
- export declare function webhookKinds(channels: readonly DeclaredChannel[]): ChannelKind[];
20
14
  /** Default route keys for {@link webhookKinds}, in the same order (the docker plan's ingress note). */
21
15
  export declare function webhookPaths(channels: readonly DeclaredChannel[]): string[];
22
16
  /**
@@ -31,7 +25,7 @@ export declare function pointChannelsAt(input: {
31
25
  registrars: Registrars;
32
26
  log: (msg: string) => void;
33
27
  }): Promise<{
34
- kind: ChannelKind;
28
+ kind: WebhookKind;
35
29
  outcome: RegistrationOutcome;
36
30
  }[]>;
37
31
  /** Poll a public `/health` until it answers 200; false on timeout. Injected by tests. */
@@ -70,7 +64,7 @@ export declare function loginGate(input: {
70
64
  log: (msg: string) => void;
71
65
  /** What THIS host does about its webhooks once the box is logged in — the only per-host words in the gate. */
72
66
  afterLogin: string;
73
- /** Where the channels point; unset when the host has not minted it yet (Docker's tunnel is read after). */
67
+ /** Where the channels point, for the lines that point them by hand; unset when the host has none to print. */
74
68
  baseUrl?: string;
75
69
  }): string;
76
70
  /** {@link pointChannelsAt} plus the shared gate policy, for a command that EXITS. */
@@ -82,3 +76,35 @@ export declare function registerWebhooks(input: {
82
76
  /** How THIS host retries — the only per-host words in the gate. */
83
77
  retryHint: string;
84
78
  }): Promise<string | undefined>;
79
+ /** A box's `/health` webhooks, or undefined while it cannot be read. Injected by tests. */
80
+ export type BoxWebhooksProbe = (healthUrl: string) => Promise<readonly {
81
+ channel: string;
82
+ outcome: WebhookRegistration["outcome"];
83
+ }[] | undefined>;
84
+ /**
85
+ * What a box that points its own channels made of them (channel-setup.md §5): read from its `/health` until no channel
86
+ * is `pending`, then gated as {@link registerWebhooks} gates. Slack's Request URL no credential on the box can set, so
87
+ * `registrars.slack` sets it from this machine, as before.
88
+ */
89
+ export declare function confirmWebhooks(input: {
90
+ /** Where this machine reaches the box's `/health`: the host's own URL. */
91
+ baseUrl: string;
92
+ /** Where the platforms reach it, the delivered `http.publicUrl` (a custom domain, say): every step names this one. */
93
+ publicUrl: string;
94
+ channels: readonly DeclaredChannel[];
95
+ registrars: Pick<Registrars, "leave">;
96
+ log: (msg: string) => void;
97
+ retryHint: string;
98
+ probe?: BoxWebhooksProbe;
99
+ timeoutMs?: number;
100
+ intervalMs?: number;
101
+ /** At a terminal with a `--config` file: record each URL a person set once the box confirms it. */
102
+ record?: UrlRecorder;
103
+ }): Promise<string | undefined>;
104
+ /** Where a deploy at a terminal records the URL a person set once the box confirms it (`channels.<name>.app.url`). */
105
+ export interface UrlRecorder {
106
+ /** How long to wait for the person to save the URL on the app's page. */
107
+ waitMs: number;
108
+ /** Write `url` into the binding of `kind` in the configuration `--config` names; resolves where it wrote. */
109
+ write(kind: WebhookKind, url: string): Promise<string>;
110
+ }