@fastagent-sh/fastagent 0.28.0 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (325) hide show
  1. package/README.md +10 -4
  2. package/dist/atomic-write.d.ts +4 -5
  3. package/dist/atomic-write.js +4 -5
  4. package/dist/authoring.d.ts +32 -15
  5. package/dist/authoring.js +147 -127
  6. package/dist/channels/agentcore-protocol.d.ts +8 -3
  7. package/dist/channels/agentcore-protocol.js +3 -1
  8. package/dist/channels/agentcore-service.d.ts +1 -0
  9. package/dist/channels/agentcore-service.js +14 -4
  10. package/dist/channels/agentcore.d.ts +9 -0
  11. package/dist/channels/agentcore.js +29 -4
  12. package/dist/channels/context.d.ts +2 -0
  13. package/dist/channels/context.js +5 -1
  14. package/dist/channels/feishu/feishu-api.d.ts +3 -1
  15. package/dist/channels/feishu/feishu-api.js +4 -0
  16. package/dist/channels/feishu/feishu.d.ts +0 -3
  17. package/dist/channels/feishu/feishu.js +10 -10
  18. package/dist/channels/feishu/register-app.d.ts +2 -0
  19. package/dist/channels/feishu/register-app.js +4 -6
  20. package/dist/channels/feishu/scaffold/channel.ts +32 -34
  21. package/dist/channels/feishu/scaffold/feishu-send.ts +5 -5
  22. package/dist/channels/feishu/scaffold/feishu-threads.ts +3 -3
  23. package/dist/channels/feishu/setup-mode.d.ts +2 -12
  24. package/dist/channels/feishu/setup-mode.js +2 -22
  25. package/dist/channels/feishu/setup.d.ts +12 -0
  26. package/dist/channels/feishu/setup.js +197 -0
  27. package/dist/channels/kit/abortable.d.ts +8 -0
  28. package/dist/channels/kit/abortable.js +22 -0
  29. package/dist/channels/lark/lark.d.ts +0 -3
  30. package/dist/channels/lark/lark.js +0 -5
  31. package/dist/channels/lark/scaffold/channel.ts +33 -35
  32. package/dist/channels/lark/scaffold/lark-send.ts +5 -5
  33. package/dist/channels/lark/scaffold/lark-threads.ts +3 -3
  34. package/dist/channels/lark/setup.d.ts +9 -0
  35. package/dist/channels/lark/setup.js +57 -0
  36. package/dist/channels/setup.d.ts +112 -0
  37. package/dist/channels/setup.js +31 -0
  38. package/dist/channels/slack/history.js +1 -1
  39. package/dist/channels/slack/manifest.d.ts +17 -7
  40. package/dist/channels/slack/manifest.js +12 -16
  41. package/dist/channels/slack/scaffold/channel.ts +44 -30
  42. package/dist/channels/slack/scaffold/slack-send.ts +8 -8
  43. package/dist/channels/slack/scaffold/slack-threads.ts +4 -4
  44. package/dist/channels/slack/setup.d.ts +7 -0
  45. package/dist/channels/slack/setup.js +119 -0
  46. package/dist/channels/slack/slack-api.d.ts +2 -0
  47. package/dist/channels/slack/slack-api.js +4 -0
  48. package/dist/channels/slack/slack.d.ts +21 -2
  49. package/dist/channels/slack/slack.js +398 -338
  50. package/dist/channels/slack/socket.d.ts +57 -0
  51. package/dist/channels/slack/socket.js +305 -0
  52. package/dist/channels/telegram/polling.d.ts +27 -0
  53. package/dist/channels/telegram/polling.js +105 -0
  54. package/dist/channels/telegram/scaffold/channel.ts +52 -32
  55. package/dist/channels/telegram/scaffold/telegram-send.ts +9 -9
  56. package/dist/channels/telegram/setup.d.ts +2 -0
  57. package/dist/channels/telegram/setup.js +42 -0
  58. package/dist/channels/telegram/telegram-api.d.ts +27 -1
  59. package/dist/channels/telegram/telegram-api.js +7 -6
  60. package/dist/channels/telegram/telegram.d.ts +11 -3
  61. package/dist/channels/telegram/telegram.js +184 -157
  62. package/dist/channels/wait-health.js +1 -1
  63. package/dist/channels/webhook-evidence.d.ts +11 -0
  64. package/dist/channels/webhook-evidence.js +71 -0
  65. package/dist/channels/webhooks.d.ts +37 -0
  66. package/dist/channels/webhooks.js +70 -0
  67. package/dist/child-env.d.ts +16 -0
  68. package/dist/child-env.js +30 -0
  69. package/dist/cli/box-login.js +4 -1
  70. package/dist/cli/channel-app.d.ts +3 -0
  71. package/dist/cli/channel-app.js +35 -0
  72. package/dist/cli/channel-setup.d.ts +38 -0
  73. package/dist/cli/channel-setup.js +195 -0
  74. package/dist/cli/commands/add.d.ts +6 -14
  75. package/dist/cli/commands/add.js +45 -310
  76. package/dist/cli/commands/auth.js +8 -2
  77. package/dist/cli/commands/build.d.ts +5 -0
  78. package/dist/cli/commands/build.js +16 -0
  79. package/dist/cli/commands/channels.d.ts +19 -0
  80. package/dist/cli/commands/channels.js +230 -0
  81. package/dist/cli/commands/content.d.ts +7 -3
  82. package/dist/cli/commands/content.js +16 -10
  83. package/dist/cli/commands/deploy/agentcore.js +55 -23
  84. package/dist/cli/commands/deploy/channel-apps.d.ts +17 -0
  85. package/dist/cli/commands/deploy/channel-apps.js +30 -0
  86. package/dist/cli/commands/deploy/docker.js +32 -65
  87. package/dist/cli/commands/deploy/fly.js +28 -12
  88. package/dist/cli/commands/deploy/railway.js +38 -13
  89. package/dist/cli/commands/deploy/secrets-step.d.ts +19 -0
  90. package/dist/cli/commands/deploy/secrets-step.js +66 -0
  91. package/dist/cli/commands/deploy/shared.d.ts +16 -5
  92. package/dist/cli/commands/deploy/shared.js +25 -8
  93. package/dist/cli/commands/deploy.js +53 -67
  94. package/dist/cli/commands/dev.d.ts +2 -0
  95. package/dist/cli/commands/dev.js +15 -3
  96. package/dist/cli/commands/env.js +1 -1
  97. package/dist/cli/commands/info.d.ts +2 -0
  98. package/dist/cli/commands/info.js +63 -24
  99. package/dist/cli/commands/init.js +3 -1
  100. package/dist/cli/commands/invoke.d.ts +2 -0
  101. package/dist/cli/commands/invoke.js +20 -7
  102. package/dist/cli/commands/login.d.ts +0 -7
  103. package/dist/cli/commands/login.js +22 -10
  104. package/dist/cli/commands/logout.js +1 -1
  105. package/dist/cli/commands/mcp.d.ts +12 -0
  106. package/dist/cli/commands/mcp.js +164 -0
  107. package/dist/cli/commands/models.js +3 -2
  108. package/dist/cli/commands/schedules.d.ts +4 -2
  109. package/dist/cli/commands/schedules.js +14 -10
  110. package/dist/cli/commands/secrets.d.ts +14 -0
  111. package/dist/cli/commands/secrets.js +213 -0
  112. package/dist/cli/commands/start.d.ts +14 -14
  113. package/dist/cli/commands/start.js +46 -60
  114. package/dist/cli/content-view.d.ts +4 -6
  115. package/dist/cli/content-view.js +5 -11
  116. package/dist/cli/first-run-channels.d.ts +3 -0
  117. package/dist/cli/first-run-channels.js +63 -0
  118. package/dist/cli/instance.d.ts +41 -0
  119. package/dist/cli/instance.js +155 -0
  120. package/dist/cli/invoke-stream.d.ts +5 -2
  121. package/dist/cli/invoke-stream.js +7 -2
  122. package/dist/cli/program.js +339 -108
  123. package/dist/cli/serve.d.ts +4 -5
  124. package/dist/cli/serve.js +4 -7
  125. package/dist/cli/shared.d.ts +11 -2
  126. package/dist/cli/shared.js +32 -19
  127. package/dist/cli/ticket.js +50 -6
  128. package/dist/content/declare.d.ts +31 -37
  129. package/dist/content/declare.js +59 -60
  130. package/dist/content/file.d.ts +23 -12
  131. package/dist/content/file.js +48 -35
  132. package/dist/content/git.d.ts +0 -10
  133. package/dist/content/git.js +5 -39
  134. package/dist/content/resolve.d.ts +18 -22
  135. package/dist/content/resolve.js +32 -66
  136. package/dist/content/source.d.ts +7 -8
  137. package/dist/content/source.js +10 -28
  138. package/dist/credentials-page.d.ts +26 -0
  139. package/dist/credentials-page.js +185 -0
  140. package/dist/declared-secrets.d.ts +10 -3
  141. package/dist/declared-secrets.js +8 -4
  142. package/dist/deploy/agentcore/destroy.d.ts +4 -1
  143. package/dist/deploy/agentcore/destroy.js +26 -2
  144. package/dist/deploy/agentcore/forwarder.js +1 -1
  145. package/dist/deploy/agentcore/plan.d.ts +7 -21
  146. package/dist/deploy/agentcore/plan.js +47 -49
  147. package/dist/deploy/agentcore/run.d.ts +18 -10
  148. package/dist/deploy/agentcore/run.js +93 -70
  149. package/dist/deploy/agentcore/runtime-secrets.d.ts +7 -0
  150. package/dist/deploy/agentcore/runtime-secrets.js +108 -0
  151. package/dist/deploy/agentcore/shell.js +12 -19
  152. package/dist/deploy/agentcore/sigv4.d.ts +7 -0
  153. package/dist/deploy/agentcore/sigv4.js +15 -0
  154. package/dist/deploy/agentcore/slr.d.ts +18 -0
  155. package/dist/deploy/agentcore/slr.js +27 -0
  156. package/dist/deploy/agentcore/store.d.ts +7 -0
  157. package/dist/deploy/agentcore/store.js +115 -0
  158. package/dist/deploy/api.d.ts +14 -14
  159. package/dist/deploy/api.js +26 -24
  160. package/dist/deploy/box-shell.d.ts +3 -1
  161. package/dist/deploy/build-context.d.ts +4 -4
  162. package/dist/deploy/build-context.js +11 -11
  163. package/dist/deploy/channel-ingress.d.ts +35 -9
  164. package/dist/deploy/channel-ingress.js +119 -22
  165. package/dist/deploy/container.d.ts +35 -10
  166. package/dist/deploy/container.js +80 -39
  167. package/dist/deploy/delivered-text.d.ts +13 -0
  168. package/dist/deploy/delivered-text.js +86 -0
  169. package/dist/deploy/docker/plan.d.ts +7 -8
  170. package/dist/deploy/docker/plan.js +31 -52
  171. package/dist/deploy/docker/run.d.ts +18 -43
  172. package/dist/deploy/docker/run.js +57 -79
  173. package/dist/deploy/file-store.d.ts +2 -0
  174. package/dist/deploy/file-store.js +17 -0
  175. package/dist/deploy/fly/plan.d.ts +3 -2
  176. package/dist/deploy/fly/plan.js +20 -9
  177. package/dist/deploy/fly/run.d.ts +18 -22
  178. package/dist/deploy/fly/run.js +25 -44
  179. package/dist/deploy/fly/store.d.ts +12 -0
  180. package/dist/deploy/fly/store.js +83 -0
  181. package/dist/deploy/hosts.d.ts +1 -2
  182. package/dist/deploy/hosts.js +1 -2
  183. package/dist/deploy/preflight.d.ts +18 -5
  184. package/dist/deploy/preflight.js +135 -63
  185. package/dist/deploy/railway/ignore.d.ts +8 -0
  186. package/dist/deploy/railway/ignore.js +57 -0
  187. package/dist/deploy/railway/plan.d.ts +1 -2
  188. package/dist/deploy/railway/plan.js +8 -6
  189. package/dist/deploy/railway/run.d.ts +21 -15
  190. package/dist/deploy/railway/run.js +32 -78
  191. package/dist/deploy/railway/store.d.ts +29 -0
  192. package/dist/deploy/railway/store.js +143 -0
  193. package/dist/deploy/runner.d.ts +5 -2
  194. package/dist/deploy/runner.js +4 -3
  195. package/dist/deploy/secret-store.d.ts +63 -0
  196. package/dist/deploy/secret-store.js +98 -0
  197. package/dist/deploy/secrets.d.ts +29 -46
  198. package/dist/deploy/secrets.js +49 -122
  199. package/dist/deploy/workspace.d.ts +6 -12
  200. package/dist/deploy/workspace.js +18 -94
  201. package/dist/dev-supervisor.js +6 -7
  202. package/dist/environment/declare.js +3 -2
  203. package/dist/environment/mise.d.ts +3 -1
  204. package/dist/environment/mise.js +4 -2
  205. package/dist/feishu.d.ts +1 -1
  206. package/dist/feishu.js +1 -1
  207. package/dist/harnesses/models/catalog.d.ts +1 -0
  208. package/dist/harnesses/models/catalog.js +1 -2
  209. package/dist/harnesses/models/environment.d.ts +7 -2
  210. package/dist/harnesses/models/environment.js +9 -5
  211. package/dist/harnesses/models/host.d.ts +0 -7
  212. package/dist/harnesses/models/host.js +10 -1
  213. package/dist/harnesses/models/login.d.ts +0 -8
  214. package/dist/harnesses/models/login.js +5 -4
  215. package/dist/harnesses/models/models.d.ts +7 -2
  216. package/dist/harnesses/models/models.js +28 -4
  217. package/dist/harnesses/pi-durable/agent.js +59 -38
  218. package/dist/harnesses/pi-durable/build.d.ts +12 -0
  219. package/dist/harnesses/pi-durable/build.js +270 -0
  220. package/dist/harnesses/pi-durable/built.d.ts +24 -0
  221. package/dist/harnesses/pi-durable/built.js +136 -0
  222. package/dist/harnesses/pi-durable/config.d.ts +81 -0
  223. package/dist/harnesses/pi-durable/config.js +266 -0
  224. package/dist/harnesses/pi-durable/define.d.ts +63 -13
  225. package/dist/harnesses/pi-durable/define.js +23 -2
  226. package/dist/harnesses/pi-durable/definition.d.ts +72 -0
  227. package/dist/harnesses/pi-durable/definition.js +143 -0
  228. package/dist/harnesses/pi-durable/events.d.ts +4 -3
  229. package/dist/harnesses/pi-durable/events.js +179 -136
  230. package/dist/harnesses/pi-durable/execution-env.d.ts +17 -0
  231. package/dist/harnesses/pi-durable/execution-env.js +11 -0
  232. package/dist/harnesses/pi-durable/mcp.d.ts +3 -0
  233. package/dist/harnesses/pi-durable/mcp.js +32 -0
  234. package/dist/harnesses/pi-durable/open.d.ts +36 -38
  235. package/dist/harnesses/pi-durable/open.js +97 -150
  236. package/dist/harnesses/pi-durable/reads.d.ts +0 -1
  237. package/dist/harnesses/pi-durable/reads.js +1 -3
  238. package/dist/harnesses/pi-durable/registry.d.ts +4 -6
  239. package/dist/harnesses/pi-durable/registry.js +29 -26
  240. package/dist/harnesses/pi-durable/release.d.ts +2 -2
  241. package/dist/harnesses/pi-durable/release.js +10 -7
  242. package/dist/harnesses/pi-durable/serve.d.ts +95 -15
  243. package/dist/harnesses/pi-durable/serve.js +151 -59
  244. package/dist/harnesses/pi-durable/sessions.js +15 -12
  245. package/dist/harnesses/pi-durable/text-sources.d.ts +30 -0
  246. package/dist/harnesses/pi-durable/text-sources.js +55 -0
  247. package/dist/harnesses/pi-durable/text.d.ts +16 -4
  248. package/dist/harnesses/pi-durable/text.js +115 -59
  249. package/dist/harnesses/pi-durable/wake.js +8 -8
  250. package/dist/http/client.js +9 -3
  251. package/dist/http/server.d.ts +1 -1
  252. package/dist/http/server.js +1 -1
  253. package/dist/index.d.ts +11 -4
  254. package/dist/index.js +8 -1
  255. package/dist/instance-name.d.ts +7 -0
  256. package/dist/instance-name.js +26 -0
  257. package/dist/lark.d.ts +1 -1
  258. package/dist/lark.js +1 -1
  259. package/dist/locked-file.d.ts +2 -2
  260. package/dist/locked-file.js +8 -3
  261. package/dist/mcp/auth.d.ts +22 -0
  262. package/dist/mcp/auth.js +180 -0
  263. package/dist/mcp/client.d.ts +15 -0
  264. package/dist/mcp/client.js +179 -0
  265. package/dist/mcp/config.d.ts +86 -0
  266. package/dist/mcp/config.js +225 -0
  267. package/dist/mcp/grants.d.ts +4 -0
  268. package/dist/mcp/grants.js +132 -0
  269. package/dist/mcp/host.d.ts +19 -0
  270. package/dist/mcp/host.js +128 -0
  271. package/dist/mcp/network.d.ts +12 -0
  272. package/dist/mcp/network.js +150 -0
  273. package/dist/mcp/sign-in.d.ts +2 -0
  274. package/dist/mcp/sign-in.js +204 -0
  275. package/dist/mcp-api.d.ts +107 -0
  276. package/dist/mcp-api.js +10 -0
  277. package/dist/paths.d.ts +22 -8
  278. package/dist/paths.js +44 -11
  279. package/dist/runtime-websocket.d.ts +14 -0
  280. package/dist/runtime-websocket.js +39 -0
  281. package/dist/scaffold/add-channel.d.ts +9 -16
  282. package/dist/scaffold/add-channel.js +13 -83
  283. package/dist/scaffold/templates/env.example +4 -5
  284. package/dist/scaffold/templates/fastagent.config.ts +20 -14
  285. package/dist/scaffold/templates/gitignore +9 -6
  286. package/dist/scaffold/templates.d.ts +6 -2
  287. package/dist/scaffold/templates.js +7 -2
  288. package/dist/scaffold/vendor-skill.js +4 -2
  289. package/dist/service.d.ts +44 -6
  290. package/dist/service.js +110 -10
  291. package/dist/serving-command.d.ts +10 -0
  292. package/dist/serving-command.js +1 -0
  293. package/dist/setup-page.d.ts +51 -0
  294. package/dist/setup-page.js +149 -0
  295. package/dist/slack.d.ts +1 -1
  296. package/dist/slack.js +1 -1
  297. package/dist/strip-edges.d.ts +10 -0
  298. package/dist/strip-edges.js +22 -0
  299. package/dist/telegram.d.ts +1 -1
  300. package/dist/telegram.js +1 -1
  301. package/dist/tunnel.d.ts +0 -1
  302. package/dist/tunnel.js +2 -8
  303. package/dist/value-file.d.ts +12 -6
  304. package/dist/value-file.js +36 -25
  305. package/dist/version.d.ts +7 -1
  306. package/dist/version.js +9 -3
  307. package/package.json +8 -6
  308. package/dist/channels/lark/onboard.d.ts +0 -36
  309. package/dist/channels/lark/onboard.js +0 -56
  310. package/dist/channels/slack/config-api.d.ts +0 -69
  311. package/dist/channels/slack/config-api.js +0 -162
  312. package/dist/channels/slack/onboard.d.ts +0 -40
  313. package/dist/channels/slack/onboard.js +0 -136
  314. package/dist/channels/slack/onboarding-state.d.ts +0 -31
  315. package/dist/channels/slack/onboarding-state.js +0 -64
  316. package/dist/channels/slack/register-webhook.d.ts +0 -12
  317. package/dist/channels/slack/register-webhook.js +0 -67
  318. package/dist/channels/slack/setup-server.d.ts +0 -32
  319. package/dist/channels/slack/setup-server.js +0 -201
  320. package/dist/cli/add-feishu.d.ts +0 -50
  321. package/dist/cli/add-feishu.js +0 -367
  322. package/dist/cli/add-slack.d.ts +0 -18
  323. package/dist/cli/add-slack.js +0 -216
  324. package/dist/harnesses/pi-durable/parked.d.ts +0 -18
  325. package/dist/harnesses/pi-durable/parked.js +0 -27
@@ -0,0 +1,266 @@
1
+ /**
2
+ * The configuration of an instance (agent-artifact.md §3.2, §4.2): what the program declares as its `defaults` and
3
+ * its `settings` schema, what an instance sets over them, and the one value they resolve to, each leaf with the place
4
+ * it came from. One JSON Schema describes the shape: it checks the program's defaults and an instance's file alike,
5
+ * and `fastagent build` publishes it as `fastagent.schema.json`.
6
+ */
7
+ import { existsSync } from "node:fs";
8
+ import { readFile } from "node:fs/promises";
9
+ import { dirname, join } from "node:path";
10
+ import { arr, bool, compile, int, map, obj, oneOf, opt, str, union, unknown, } from "@earendil-works/pi-schema";
11
+ import { instanceContent } from "../../content/file.js";
12
+ import { checkInstanceServers } from "../../mcp/config.js";
13
+ import { checkInstanceName } from "../../instance-name.js";
14
+ import { modelRefOf } from "../../model-ref.js";
15
+ import { INSTANCE_CONFIG_ENV, INSTANCE_CONFIG_FILE, resolveOverridePath } from "../../paths.js";
16
+ const THINKING_LEVELS = [
17
+ "off",
18
+ "minimal",
19
+ "low",
20
+ "medium",
21
+ "high",
22
+ "xhigh",
23
+ "max",
24
+ ];
25
+ const PROGRAM_DEFAULT = "program default";
26
+ const CONSTANTS = ["name", "description", "extensions", "providers"];
27
+ /** Keys that were the program's own before they became defaults. */
28
+ const FORMER_CONSTANTS = ["model", "thinkingLevel", "http", "deploy"];
29
+ /** An instance's content entry: a repository, or a directory (its detailed refusals are content/declare.ts's). */
30
+ const CONTENT_ENTRY = union([
31
+ obj({
32
+ github: str({ description: "The repository, owner/repo" }),
33
+ ref: opt(str({ description: "The branch, tag or commit to clone" })),
34
+ description: opt(str({ description: "One sentence for the agent: what this is, and how to treat it" })),
35
+ }),
36
+ obj({
37
+ path: str({ description: "A directory of this machine: absolute, ~/, or relative to this file" }),
38
+ description: opt(str({ description: "One sentence for the agent: what this is, and how to treat it" })),
39
+ }),
40
+ ]);
41
+ /** An absolute `https://` URL without a trailing slash, query or fragment: what `<publicUrl>/<path>` is built from. */
42
+ const PUBLIC_URL_PATTERN = "^https://[^/?#\\s]+(/[^?#\\s]*[^/?#\\s])?$";
43
+ /** The program's `settings` with no property required: an instance gives some of them, the program's defaults the rest. */
44
+ function partial(settings) {
45
+ const { required: _required, ...rest } = settings;
46
+ return rest;
47
+ }
48
+ /** What the program's defaults and an instance's configuration may hold. `settings` is the program's, or none. */
49
+ function shape(settings) {
50
+ return {
51
+ model: opt(str({ description: 'The default model, "provider/modelId"', minLength: 3 })),
52
+ thinkingLevel: opt(oneOf(THINKING_LEVELS)),
53
+ http: opt(obj({
54
+ port: opt(int({ minimum: 0, maximum: 65535, description: "The port a serve listens on" })),
55
+ api: opt(bool({ description: "Whether a serve answers the Agent API under /v1" })),
56
+ publicUrl: opt(str({
57
+ description: "The public base URL the platforms reach this instance at, any mount prefix included: the serve " +
58
+ "points each webhook channel there",
59
+ pattern: PUBLIC_URL_PATTERN,
60
+ })),
61
+ })),
62
+ deploy: opt(obj({
63
+ // AWS's own bounds for idleRuntimeSessionTimeout: a value outside them is refused by CloudFormation minutes
64
+ // into `deploy agentcore --run`, after the image build.
65
+ agentcore: opt(obj({ idleTimeoutSeconds: opt(int({ minimum: 60, maximum: 1209600 })) })),
66
+ })),
67
+ channels: opt(map(obj({
68
+ app: opt(obj({
69
+ id: str({ description: "The platform's app ID" }),
70
+ team: opt(str({ description: "Slack's workspace (team) ID" })),
71
+ url: opt(str({
72
+ description: "The channel's URL a person set on the platform's app page and the platform verified",
73
+ pattern: "^https://[^?#\\s]+$",
74
+ })),
75
+ })),
76
+ ingress: opt(oneOf(["webhook", "connection"], {
77
+ description: "How the channel receives: webhook (inbound HTTP) or connection (one it opens and keeps)",
78
+ })),
79
+ }))),
80
+ settings: opt(settings === undefined ? obj({}) : partial(settings)),
81
+ };
82
+ }
83
+ /** The JSON Schema of an instance's `fastagent.json` for a program with `settings`: what `fastagent build` publishes. */
84
+ export function instanceConfigSchema(settings) {
85
+ const paths = (description) => opt(arr(str({ minLength: 1 }), { description, uniqueItems: true }));
86
+ return obj({
87
+ $schema: opt(str()),
88
+ ...shape(settings),
89
+ name: opt(str({ description: "The instance's name: one path segment of letters, digits, - and _" })),
90
+ content: opt(map(CONTENT_ENTRY)),
91
+ mcpServers: opt(map(unknown(), {
92
+ description: "MCP servers this instance adds to the program's, by name, in the MCP ecosystem's entry shape (checked when the instance opens)",
93
+ })),
94
+ instructions: paths("Files of instructions added after the program's AGENTS.md, relative to this file"),
95
+ skills: paths("Directories of skills (one SKILL.md per subdirectory) added to the program's, relative to this file"),
96
+ prompts: paths("Directories of prompt templates (one .md file each) added to the program's, relative to this file"),
97
+ });
98
+ }
99
+ /** Refuse `value`, naming `where`, when `schema` does not accept it: each failing path, and why. */
100
+ function check(schema, value, where) {
101
+ const validator = compile(schema);
102
+ if (validator.check(value))
103
+ return;
104
+ const problems = validator
105
+ .errors(value)
106
+ .map(({ path, message }) => `${path === "" ? "(the value)" : path} ${message}`);
107
+ throw new Error(`${where}: ${problems.join("; ")}`);
108
+ }
109
+ /** Defaults, or an instance's values, in the shape both have; `schemaWhere` names the model's place in a refusal. */
110
+ function checkValues(values, settings, where, shapeOf) {
111
+ check(shapeOf(settings), values, where);
112
+ const { model } = values;
113
+ if (model !== undefined)
114
+ modelRefOf(model, `${where}: /model`);
115
+ }
116
+ /**
117
+ * The program's `fastagent.config.ts` default export, checked: the constants it may hold, its `settings` schema, and
118
+ * its defaults in the shape an instance's configuration has. Without one, no constants and no defaults.
119
+ */
120
+ export function checkProgramConfig(config, file) {
121
+ if (config === undefined)
122
+ return {};
123
+ if (typeof config !== "object" || config === null || Array.isArray(config)) {
124
+ throw new Error(`${file} must default-export defineAgent({ ... })`);
125
+ }
126
+ const { settings, defaults, extensions, providers } = config;
127
+ const keys = [...CONSTANTS, "settings", "defaults"];
128
+ for (const key of Object.keys(config)) {
129
+ if (keys.includes(key))
130
+ continue;
131
+ const moved = FORMER_CONSTANTS.includes(key) ? ': it is a default now, under "defaults"' : "";
132
+ throw new Error(`${file}: unknown key "${key}"${moved} (keys: ${keys.join(", ")})`);
133
+ }
134
+ for (const [key, list] of [
135
+ ["extensions", extensions],
136
+ ["providers", providers],
137
+ ]) {
138
+ if (list !== undefined && !Array.isArray(list))
139
+ throw new Error(`${file}: "${key}" must be an array`);
140
+ }
141
+ if (settings !== undefined) {
142
+ if (settings?.type !== "object") {
143
+ throw new Error(`${file}: "settings" must be an object schema (obj({ ... }) from @earendil-works/pi-schema)`);
144
+ }
145
+ try {
146
+ compile(settings);
147
+ }
148
+ catch (error) {
149
+ throw new Error(`${file}: "settings" is not a JSON Schema: ${error.message}`, { cause: error });
150
+ }
151
+ }
152
+ if (defaults !== undefined)
153
+ checkValues(defaults, settings, `${file} defaults`, (s) => obj(shape(s)));
154
+ return config;
155
+ }
156
+ /** An instance configuration, checked against the shape of a program with `settings`. */
157
+ function checkInstanceConfig(source, settings) {
158
+ const { values, label } = source;
159
+ if (typeof values === "object" && values !== null) {
160
+ // Its own refusals name the entry and say what is wrong with it, which a schema's cannot.
161
+ instanceContent(source);
162
+ checkInstanceServers(source);
163
+ const { name } = values;
164
+ if (typeof name === "string") {
165
+ try {
166
+ checkInstanceName(name);
167
+ }
168
+ catch (error) {
169
+ throw new Error(`${label}: ${error.message}`);
170
+ }
171
+ }
172
+ }
173
+ checkValues(values, settings, label, instanceConfigSchema);
174
+ const { $schema: _schema, ...config } = values;
175
+ return config;
176
+ }
177
+ /** A `fastagent.json` read: refused when it cannot be read or is not JSON, naming the file. */
178
+ export async function readInstanceConfigFile(path) {
179
+ let text;
180
+ try {
181
+ text = await readFile(path, "utf8");
182
+ }
183
+ catch (error) {
184
+ throw new Error(`cannot read the instance configuration ${path}: ${error.message}`, { cause: error });
185
+ }
186
+ try {
187
+ return { values: JSON.parse(text), label: path, directory: dirname(path) };
188
+ }
189
+ catch (error) {
190
+ throw new Error(`${path} is not JSON: ${error.message}`, { cause: error });
191
+ }
192
+ }
193
+ /** The configuration `FASTAGENT_CONFIG` names, when it names one. */
194
+ export async function namedInstanceConfig(env = process.env) {
195
+ const path = resolveOverridePath(env[INSTANCE_CONFIG_ENV]);
196
+ return path === undefined ? undefined : readInstanceConfigFile(path);
197
+ }
198
+ /** The instance configuration `dev` runs a project with: the one named by `--config` or `FASTAGENT_CONFIG`, else the project's own. */
199
+ export async function devInstanceConfig(dir) {
200
+ return (await namedInstanceConfig()) ?? (await projectInstanceConfig(dir));
201
+ }
202
+ /** The `fastagent.json` a development project holds at its root, when it holds one. */
203
+ export async function projectInstanceConfig(dir) {
204
+ const path = join(dir, INSTANCE_CONFIG_FILE);
205
+ return existsSync(path) ? readInstanceConfigFile(path) : undefined;
206
+ }
207
+ /** The channels whose platform lets only a person set the URL: Slack's always, Lark's where its config API is missing. */
208
+ const PERSON_SET_URL = ["slack", "lark"];
209
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
210
+ /** Put `layer` over `into`: objects merge, anything else replaces; each leaf written is recorded as from `label`. */
211
+ function overlay(into, layer, label, sources, at = "") {
212
+ for (const [key, value] of Object.entries(layer)) {
213
+ const path = at === "" ? key : `${at}.${key}`;
214
+ if (key === "__proto__")
215
+ throw new Error(`${label}: ${path} is not a setting: "__proto__" cannot be a key`);
216
+ if (!isRecord(value)) {
217
+ into[key] = value;
218
+ sources[path] = label;
219
+ continue;
220
+ }
221
+ if (!isRecord(into[key])) {
222
+ into[key] = {};
223
+ delete sources[path];
224
+ }
225
+ overlay(into[key], value, label, sources, path);
226
+ }
227
+ }
228
+ /**
229
+ * The configuration an instance of `definition` opens with: the program's defaults, the instance's over them, the
230
+ * merged settings checked against the program's `settings` schema (a required one no one gave refuses, naming it).
231
+ */
232
+ export function resolveConfig(definition, instance) {
233
+ const { defaults, settings } = definition.config;
234
+ const merged = {};
235
+ const sources = {};
236
+ if (defaults !== undefined) {
237
+ checkValues(defaults, settings, `${definition.name} defaults`, (s) => obj(shape(s)));
238
+ overlay(merged, defaults, PROGRAM_DEFAULT, sources);
239
+ }
240
+ if (instance !== undefined) {
241
+ overlay(merged, checkInstanceConfig(instance, settings), instance.label, sources);
242
+ }
243
+ const values = (merged.settings ?? {});
244
+ check(settings ?? obj({}), values, `the settings of ${definition.name}`);
245
+ const channels = new Set(definition.registry.channels.map(({ name }) => name));
246
+ // The file that named a channel: whichever of its leaves one set.
247
+ const namedIn = (name) => Object.entries(sources).find(([path]) => path.startsWith(`channels.${name}.`))?.[1] ?? "the configuration";
248
+ for (const [name, entry] of Object.entries((merged.channels ?? {}))) {
249
+ if (entry.app !== undefined && !PERSON_SET_URL.includes(name)) {
250
+ throw new Error(`${namedIn(name)}: channels.${name}.app is for a channel whose ` +
251
+ `platform lets only a person set its URL (${PERSON_SET_URL.join(", ")})`);
252
+ }
253
+ if (!channels.has(name)) {
254
+ throw new Error(`${namedIn(name)}: channels.${name} names no channel of ` +
255
+ `${definition.name} (${channels.size === 0 ? "it has none" : `its channels: ${[...channels].join(", ")}`})`);
256
+ }
257
+ }
258
+ return { ...merged, settings: values, sources };
259
+ }
260
+ /** The value at a dotted `path` of `config`. */
261
+ export function configValue(config, path) {
262
+ let value = config;
263
+ for (const key of path.split("."))
264
+ value = isRecord(value) ? value[key] : undefined;
265
+ return value;
266
+ }
@@ -5,18 +5,13 @@
5
5
  */
6
6
  import type { ModelThinkingLevel, Provider } from "@earendil-works/pi-ai";
7
7
  import type { Extension } from "@earendil-works/pi-durable";
8
+ import type { Schema } from "@earendil-works/pi-schema";
8
9
  import type { ScheduleConfig } from "../../schedule/schedule.ts";
9
- export interface AgentConfig {
10
- /** Default: `package.json`'s name. */
11
- name?: string;
12
- description?: string;
13
- /** `"provider/modelId"`: what a session that names none answers with; a deployment may override it. */
10
+ /** What an instance may set over the program's `defaults`, and what a program's `defaults` are (agent-artifact.md §3.2). */
11
+ export interface AgentDefaults<Settings = unknown> {
12
+ /** `"provider/modelId"`: what a session that names none answers with; a flag or `FASTAGENT_MODEL` may override it. */
14
13
  model?: string;
15
14
  thinkingLevel?: ModelThinkingLevel;
16
- /** Third-party capabilities, installed after `src/extensions/`. */
17
- extensions?: readonly (Extension | Needs<Extension>)[];
18
- /** Model providers beyond pi-ai's built-ins (a self-hosted endpoint, a gateway); a built-in's id replaces it. */
19
- providers?: readonly (Provider | Needs<Provider>)[];
20
15
  http?: {
21
16
  /** The port a serve listens on, for its channels' webhooks and its health check. */
22
17
  port?: number;
@@ -25,6 +20,11 @@ export interface AgentConfig {
25
20
  * which `deploy` gives a key. `false` keeps it to its channels and schedules, whatever key its environment holds.
26
21
  */
27
22
  api?: boolean;
23
+ /**
24
+ * The public base URL the platforms reach this instance at, any mount prefix included (`https://x.fly.dev/agent`):
25
+ * a serve points each webhook channel there. Unset, none is pointed. Never inferred from the environment.
26
+ */
27
+ publicUrl?: string;
28
28
  };
29
29
  /** How the agent is deployed, where a host needs to be told. */
30
30
  deploy?: {
@@ -32,9 +32,35 @@ export interface AgentConfig {
32
32
  idleTimeoutSeconds?: number;
33
33
  };
34
34
  };
35
+ /**
36
+ * Each channel by its file's name: how it receives, for a channel that can do either (`byIngress`). Unset, `dev`
37
+ * receives by WebSocket (no public URL there) and everything else by webhook, which a host that wakes on a request
38
+ * needs.
39
+ */
40
+ channels?: Record<string, {
41
+ ingress?: Ingress;
42
+ }>;
43
+ /** Values of the program's `settings`; an instance gives the rest. */
44
+ settings?: Partial<Settings>;
35
45
  }
46
+ export interface AgentConfig<Settings = unknown> {
47
+ /** Constant: default `package.json`'s name. */
48
+ name?: string;
49
+ /** Constant. */
50
+ description?: string;
51
+ /** Constant: third-party capabilities, installed after `src/extensions/`. */
52
+ extensions?: readonly (Extension | Needs<Extension>)[];
53
+ /** Constant: model providers beyond pi-ai's built-ins (a self-hosted endpoint, a gateway); a built-in's id replaces it. */
54
+ providers?: readonly (Provider | Needs<Provider>)[];
55
+ /** What each instance may choose, as an object schema (`obj({ ... })` from `@earendil-works/pi-schema`); no secrets. */
56
+ settings?: Schema<Settings>;
57
+ /** What an instance gets unless its configuration says otherwise. */
58
+ defaults?: AgentDefaults<Settings>;
59
+ }
60
+ /** The settings an `AgentConfig` declares: what `fastagent build` types a package's definition by. */
61
+ export type SettingsOf<Config> = Config extends AgentConfig<infer Settings> ? Settings : unknown;
36
62
  /** @public */
37
- export declare function defineAgent(config: AgentConfig): AgentConfig;
63
+ export declare function defineAgent<Settings = unknown>(config: AgentConfig<Settings>): AgentConfig<Settings>;
38
64
  /** One secret a piece of code reads: by name, or with what it is for and whether the code runs without it. */
39
65
  /** @public */
40
66
  export type SecretSpec = string | {
@@ -63,11 +89,35 @@ export interface Needs<T> {
63
89
  description?: string;
64
90
  optional: boolean;
65
91
  }[];
66
- build(values: Record<string, string | undefined>): T;
92
+ build(values: Record<string, string | undefined>, settings: Readonly<Record<string, unknown>>): T;
67
93
  }
68
- /** @public */
69
- export declare function needs<const S extends readonly SecretSpec[], T>(secrets: S, build: (values: SecretValues<S>) => T): Needs<T>;
94
+ /**
95
+ * Wrap a module so it is built when an instance opens, with the values of the secrets it declares and the instance's
96
+ * resolved `settings` (the program's `settings` schema, defaults and the instance's values, checked). Annotate the
97
+ * second parameter with `SettingsOf<typeof config>` to type it. With no secrets, `needs([], (_, settings) => ...)`.
98
+ * @public
99
+ */
100
+ export declare function needs<const S extends readonly SecretSpec[], T, Settings = Readonly<Record<string, unknown>>>(secrets: S, build: (values: SecretValues<S>, settings: Settings) => T): Needs<T>;
70
101
  export declare function isNeeds(value: unknown): value is Needs<unknown>;
102
+ /** How a channel receives: inbound HTTP requests, or a connection it opens itself and keeps. */
103
+ export type Ingress = "webhook" | "connection";
104
+ /** A channel that can receive either way, each way's module (or `needs` around it) under its name. */
105
+ export interface ByIngress<T = unknown> {
106
+ readonly kind: "fastagent.by-ingress";
107
+ readonly webhook: T | Needs<T>;
108
+ readonly connection: T | Needs<T>;
109
+ }
110
+ /**
111
+ * Default-export from `src/channels/<name>.ts` a channel that receives either way: each instance takes the one its
112
+ * configuration names (`channels.<name>.ingress`), and without one `dev` takes `connection` and everything else
113
+ * `webhook`. Each way declares its own secrets, so an instance is asked only for its way's.
114
+ * @public
115
+ */
116
+ export declare function byIngress<T>(ways: {
117
+ webhook: T | Needs<T>;
118
+ connection: T | Needs<T>;
119
+ }): ByIngress<T>;
120
+ export declare function isByIngress(value: unknown): value is ByIngress;
71
121
  /** What `src/schedules/<name>.ts` default-exports. */
72
122
  export interface DefinedSchedule extends ScheduleConfig {
73
123
  readonly kind: "fastagent.schedule";
@@ -3,7 +3,12 @@ import { isEnvName } from "../../declared-secrets.js";
3
3
  export function defineAgent(config) {
4
4
  return config;
5
5
  }
6
- /** @public */
6
+ /**
7
+ * Wrap a module so it is built when an instance opens, with the values of the secrets it declares and the instance's
8
+ * resolved `settings` (the program's `settings` schema, defaults and the instance's values, checked). Annotate the
9
+ * second parameter with `SettingsOf<typeof config>` to type it. With no secrets, `needs([], (_, settings) => ...)`.
10
+ * @public
11
+ */
7
12
  export function needs(secrets, build) {
8
13
  const declared = secrets.map((spec) => typeof spec === "string"
9
14
  ? { name: spec, optional: false }
@@ -17,11 +22,27 @@ export function needs(secrets, build) {
17
22
  if (!isEnvName(name))
18
23
  throw new Error(`needs: ${JSON.stringify(name)} is not an environment variable name`);
19
24
  }
20
- return { kind: "fastagent.needs", secrets: declared, build: (values) => build(values) };
25
+ return {
26
+ kind: "fastagent.needs",
27
+ secrets: declared,
28
+ build: (values, settings) => build(values, settings),
29
+ };
21
30
  }
22
31
  export function isNeeds(value) {
23
32
  return value?.kind === "fastagent.needs";
24
33
  }
34
+ /**
35
+ * Default-export from `src/channels/<name>.ts` a channel that receives either way: each instance takes the one its
36
+ * configuration names (`channels.<name>.ingress`), and without one `dev` takes `connection` and everything else
37
+ * `webhook`. Each way declares its own secrets, so an instance is asked only for its way's.
38
+ * @public
39
+ */
40
+ export function byIngress(ways) {
41
+ return { kind: "fastagent.by-ingress", webhook: ways.webhook, connection: ways.connection };
42
+ }
43
+ export function isByIngress(value) {
44
+ return value?.kind === "fastagent.by-ingress";
45
+ }
25
46
  /** @public */
26
47
  export function defineSchedule(config) {
27
48
  return { kind: "fastagent.schedule", ...config };
@@ -0,0 +1,72 @@
1
+ import type { DeclaredSecret } from "../../declared-secrets.ts";
2
+ import { type McpInstance } from "../../mcp/config.ts";
3
+ import { type AgentConfig, type Ingress } from "./define.ts";
4
+ import { type Registry } from "./registry.ts";
5
+ /** What an opener of this FastAgent reads; a definition made for another refuses. */
6
+ export declare const DEFINITION_FORMAT = 1;
7
+ declare const DEFINITION: unique symbol;
8
+ declare const SETTINGS: unique symbol;
9
+ /** A way of receiving that reads a secret: one channel's, by its name. */
10
+ interface IngressCondition {
11
+ channel: string;
12
+ ingress: Ingress;
13
+ }
14
+ /** A secret an instance must or may be given (SPEC.md §8): each name once, required when any declaration requires it. */
15
+ interface DeclaredInput {
16
+ name: string;
17
+ description?: string;
18
+ optional: boolean;
19
+ declaredIn: readonly string[];
20
+ /** Only these channels' ways read it; absent, every instance does. */
21
+ whenIngress?: readonly IngressCondition[];
22
+ }
23
+ /** `AgentDefinition<Settings>`: typed by the program's settings, so an embedder's `settings` input is checked. */
24
+ export interface AgentDefinition<Settings = unknown> {
25
+ /** Phantom: carries `Settings` to the open's input types; nothing exists at run time. */
26
+ readonly [SETTINGS]?: Settings;
27
+ readonly [DEFINITION]: true;
28
+ readonly format: number;
29
+ /** The FastAgent version that made it: what a refusal of its format asks the opener to install. */
30
+ readonly builtWith: string;
31
+ readonly name: string;
32
+ readonly version: string;
33
+ /** Absolute: the program's files, its text, `context.json` and `mcp.json`. */
34
+ readonly dir: string;
35
+ readonly config: AgentConfig;
36
+ readonly registry: Registry;
37
+ readonly inputs: {
38
+ readonly secrets: readonly DeclaredInput[];
39
+ };
40
+ }
41
+ /** The value an opener was given, checked to be a definition this FastAgent assembles; a refusal says why. */
42
+ export declare function assertDefinition(value: unknown): AgentDefinition;
43
+ type DefinitionSpec = Omit<AgentDefinition, typeof DEFINITION | "inputs">;
44
+ /** The value, frozen down to its lists and entries; the modules' own exports stay as their authors made them. */
45
+ export declare function defineDefinition(spec: DefinitionSpec): AgentDefinition;
46
+ /** The channels part of a configuration: how each channel that receives either way receives. */
47
+ interface ChannelsConfig {
48
+ channels?: Readonly<Record<string, {
49
+ ingress?: Ingress;
50
+ }>>;
51
+ }
52
+ /** A channels configuration and whether `fastagent dev` serves the instance (else the process's own say, `servedByDev`). */
53
+ export interface IngressConfig extends ChannelsConfig {
54
+ dev?: boolean;
55
+ }
56
+ /**
57
+ * How the channel `name` receives under `config`: what it names, else WebSocket under `dev` (no public URL there) and
58
+ * webhook everywhere else (a host that wakes on a request reaches it only that way).
59
+ */
60
+ export declare function chosenIngress(config: IngressConfig, name: string, dev?: boolean): Ingress;
61
+ type Declaring = Pick<DefinitionSpec, "registry" | "config" | "dir">;
62
+ /**
63
+ * The secrets the definition's code reads (`needs()`) and its commands do (`context.json`'s variables): an instance's,
64
+ * under its `config`, or every way's with none.
65
+ */
66
+ export declare function codeSecrets(definition: Declaring, config?: IngressConfig): DeclaredSecret[];
67
+ /**
68
+ * Every declaration of a secret, each where it is: the code's, then the references of the MCP servers, the program's
69
+ * and, given `instance`, the ones it adds.
70
+ */
71
+ export declare function definitionSecrets(definition: Declaring, config?: IngressConfig, instance?: McpInstance): DeclaredSecret[];
72
+ export {};
@@ -0,0 +1,143 @@
1
+ /**
2
+ * A definition as a value (agent-artifact.md §3.5): what `dev` loads from a source project and what a built package
3
+ * will export, one frozen shape every opener assembles from. Its declared inputs are read once, when it is made.
4
+ */
5
+ import { loadEnvironment } from "../../content/file.js";
6
+ import { declaredVariables } from "../../environment/declare.js";
7
+ import { declareMcp, mcpSecrets } from "../../mcp/config.js";
8
+ import { AGENT_CONFIG_FILE as CONFIG_FILE } from "../../paths.js";
9
+ import { servedByDev } from "../../serving-command.js";
10
+ import { isByIngress, isNeeds } from "./define.js";
11
+ import { REGISTRY_KINDS } from "./registry.js";
12
+ /** What an opener of this FastAgent reads; a definition made for another refuses. */
13
+ export const DEFINITION_FORMAT = 1;
14
+ const DEFINITION = Symbol.for("fastagent.definition");
15
+ /** The value an opener was given, checked to be a definition this FastAgent assembles; a refusal says why. */
16
+ export function assertDefinition(value) {
17
+ if (typeof value !== "object" || value === null || !(DEFINITION in value)) {
18
+ throw new Error("openAgent needs an agent package's default export (a definition `fastagent build` made)");
19
+ }
20
+ const definition = value;
21
+ if (definition.format !== DEFINITION_FORMAT) {
22
+ throw new Error(`${definition.name} was built as definition format ${definition.format}, and this FastAgent opens format ` +
23
+ `${DEFINITION_FORMAT}: install @fastagent-sh/fastagent ${definition.builtWith}`);
24
+ }
25
+ return definition;
26
+ }
27
+ /** The value, frozen down to its lists and entries; the modules' own exports stay as their authors made them. */
28
+ export function defineDefinition(spec) {
29
+ const registry = Object.freeze(Object.fromEntries(REGISTRY_KINDS.map((kind) => [
30
+ kind,
31
+ Object.freeze(spec.registry[kind].map((entry) => Object.freeze({ ...entry }))),
32
+ ])));
33
+ const config = Object.freeze({
34
+ ...spec.config,
35
+ ...(spec.config.extensions === undefined ? {} : { extensions: Object.freeze([...spec.config.extensions]) }),
36
+ ...(spec.config.providers === undefined ? {} : { providers: Object.freeze([...spec.config.providers]) }),
37
+ ...(spec.config.settings === undefined ? {} : { settings: deepFreeze(structuredClone(spec.config.settings)) }),
38
+ ...(spec.config.defaults === undefined ? {} : { defaults: deepFreeze(structuredClone(spec.config.defaults)) }),
39
+ });
40
+ return Object.freeze({
41
+ [DEFINITION]: true,
42
+ ...spec,
43
+ config,
44
+ registry,
45
+ inputs: Object.freeze({ secrets: Object.freeze(byName(definitionSecrets(spec))) }),
46
+ });
47
+ }
48
+ /** Plain data, frozen all the way down: a schema and defaults are JSON, which no instance may change. */
49
+ function deepFreeze(value) {
50
+ if (typeof value === "object" && value !== null) {
51
+ for (const inner of Object.values(value))
52
+ deepFreeze(inner);
53
+ Object.freeze(value);
54
+ }
55
+ return value;
56
+ }
57
+ /**
58
+ * One input per name, in name order: required when any declaration requires it, every place it is declared, and the
59
+ * ways of receiving that read it when only some do.
60
+ */
61
+ function byName(declared) {
62
+ const groups = new Map();
63
+ for (const secret of declared)
64
+ groups.set(secret.name, [...(groups.get(secret.name) ?? []), secret]);
65
+ return [...groups.entries()]
66
+ .sort(([a], [b]) => a.localeCompare(b))
67
+ .map(([name, all]) => {
68
+ const optional = all.every((secret) => secret.optional);
69
+ const description = all.find((secret) => secret.description !== undefined)?.description;
70
+ const whenIngress = conditionsOf(optional ? all : all.filter((secret) => !secret.optional));
71
+ return Object.freeze({
72
+ name,
73
+ ...(description === undefined ? {} : { description }),
74
+ optional,
75
+ declaredIn: Object.freeze([...new Set(all.map((secret) => secret.declaredIn))]),
76
+ ...(whenIngress === undefined ? {} : { whenIngress: Object.freeze(whenIngress) }),
77
+ });
78
+ });
79
+ }
80
+ /**
81
+ * The ways of receiving under which `deciding` reads a name: none when one of them always does, or when a channel's
82
+ * both ways do, since every instance then receives one of them.
83
+ */
84
+ function conditionsOf(deciding) {
85
+ if (deciding.some((secret) => secret.when === undefined))
86
+ return undefined;
87
+ const conditions = [];
88
+ for (const { when } of deciding) {
89
+ const { channel, ingress } = when;
90
+ if (!conditions.some((c) => c.channel === channel && c.ingress === ingress))
91
+ conditions.push({ channel, ingress });
92
+ }
93
+ const bothWays = conditions.some(({ channel }) => conditions.filter((c) => c.channel === channel).length === 2);
94
+ return bothWays ? undefined : conditions;
95
+ }
96
+ /**
97
+ * How the channel `name` receives under `config`: what it names, else WebSocket under `dev` (no public URL there) and
98
+ * webhook everywhere else (a host that wakes on a request reaches it only that way).
99
+ */
100
+ export function chosenIngress(config, name, dev = config.dev ?? servedByDev()) {
101
+ return config.channels?.[name]?.ingress ?? (dev ? "connection" : "webhook");
102
+ }
103
+ /**
104
+ * Every value the definition can hold `Needs` in, with where it is: modules by file, configuration by key. A channel
105
+ * that receives either way is the way `config` chooses; with no configuration, both ways, each with its condition.
106
+ */
107
+ function declarers({ registry, config }, instance) {
108
+ const ways = ({ file, name, value }) => {
109
+ if (!isByIngress(value))
110
+ return [{ declaredIn: file, value }];
111
+ if (instance !== undefined)
112
+ return [{ declaredIn: file, value: value[chosenIngress(instance, name)] }];
113
+ return ["webhook", "connection"].map((ingress) => ({
114
+ declaredIn: file,
115
+ value: value[ingress],
116
+ when: { channel: name, ingress },
117
+ }));
118
+ };
119
+ return [
120
+ ...REGISTRY_KINDS.flatMap((kind) => registry[kind].flatMap(ways)),
121
+ ...(config.extensions ?? []).map((value, i) => ({ declaredIn: `${CONFIG_FILE} extensions[${i}]`, value })),
122
+ ...(config.providers ?? []).map((value, i) => ({ declaredIn: `${CONFIG_FILE} providers[${i}]`, value })),
123
+ ];
124
+ }
125
+ /**
126
+ * The secrets the definition's code reads (`needs()`) and its commands do (`context.json`'s variables): an instance's,
127
+ * under its `config`, or every way's with none.
128
+ */
129
+ export function codeSecrets(definition, config) {
130
+ return [
131
+ ...declarers(definition, config).flatMap(({ declaredIn, value, when }) => isNeeds(value)
132
+ ? value.secrets.map((secret) => ({ ...secret, declaredIn, ...(when === undefined ? {} : { when }) }))
133
+ : []),
134
+ ...declaredVariables(loadEnvironment(definition.dir)),
135
+ ];
136
+ }
137
+ /**
138
+ * Every declaration of a secret, each where it is: the code's, then the references of the MCP servers, the program's
139
+ * and, given `instance`, the ones it adds.
140
+ */
141
+ export function definitionSecrets(definition, config, instance) {
142
+ return [...codeSecrets(definition, config), ...mcpSecrets(declareMcp(definition.dir, instance))];
143
+ }
@@ -1,7 +1,8 @@
1
1
  /**
2
- * `sessions.events` (SPEC.md §6.1) over pi-durable's event stream of one conversation: the entries asked for, a
3
- * `state` snapshot, then what each commit adds. pi-durable captures its snapshot and the registration for later
4
- * commits atomically, so the replay ends at the snapshot's newest entry and the live part starts after it.
2
+ * `sessions.events` (SPEC.md §6.1) over pi-durable's view of one conversation: the entries asked for, a `state`
3
+ * snapshot, then what each commit changes. The view is observed synchronously, one revision per commit, so the
4
+ * replay ends at the first revision's newest entry and the live part, diffed from one revision to the next, starts
5
+ * after it. Every revision is kept: a message that is placed and answered between two reads still gets its `done`.
5
6
  */
6
7
  import type { Context } from "@earendil-works/chord/context";
7
8
  import { type Harness } from "@earendil-works/pi-durable";