@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
@@ -1,12 +1,12 @@
1
1
  import type { ServedAgent, ServeOptions, Service } from "../service.ts";
2
- import type { AgentConfig } from "../harnesses/pi-durable/define.ts";
2
+ import type { EffectiveConfig } from "../harnesses/pi-durable/config.ts";
3
3
  /** Refuse `--tunnel` with a `--bind` that cloudflared cannot reach. */
4
4
  export declare function assertTunnelBindable(host: string | undefined, tunnel: boolean): void;
5
5
  /**
6
6
  * What the CLI adds to the assembly: its shutdown grace, exit on a connection that drops, and the API's keys, none
7
7
  * when the definition turns the API off, whatever key the environment holds (a host's from an earlier deploy).
8
8
  */
9
- export declare function cliMountOptions(config: AgentConfig): ServeOptions;
9
+ export declare function cliMountOptions(config: EffectiveConfig): ServeOptions;
10
10
  /**
11
11
  * Bind, report, warn about reach, open the tunnel, and close in order on a signal — the tail dev's worker and start
12
12
  * share once the service is assembled.
@@ -17,11 +17,10 @@ export declare function serveService(service: ServedAgent, bind: {
17
17
  }, posture: {
18
18
  tunnel: boolean;
19
19
  agentDir: string;
20
- stateRoot: string;
21
- config: AgentConfig;
20
+ config: EffectiveConfig;
22
21
  }): void;
23
22
  /** The "we are serving" report: the supervisor message `dev`'s watcher waits for, the addresses, and what mounted. */
24
- export declare function reportServing(service: ServedAgent, host: string | undefined, boundPort: number, config: AgentConfig): void;
23
+ export declare function reportServing(service: ServedAgent, host: string | undefined, boundPort: number, config: EffectiveConfig): void;
25
24
  /** The line that names WHERE the serve is — every posture reports it, including AgentCore's own surface. */
26
25
  export declare function bindLine(host: string | undefined, boundPort: number): string;
27
26
  /** Listen, and report ready — but only once the SERVICE is, which is not the same moment — until a signal closes it. */
package/dist/cli/serve.js CHANGED
@@ -30,10 +30,10 @@ export function cliMountOptions(config) {
30
30
  */
31
31
  export function serveService(service, bind, posture) {
32
32
  const { host } = bind;
33
- const { tunnel, agentDir, stateRoot, config } = posture;
33
+ const { tunnel, agentDir, config } = posture;
34
34
  serve(service, bind, (p) => {
35
35
  reportServing(service, host, p, config);
36
- maybeTunnel(agentDir, service.channels.routes, p, tunnel, stateRoot);
36
+ maybeTunnel(agentDir, service.channels.routes, p, tunnel);
37
37
  });
38
38
  }
39
39
  /** The "we are serving" report: the supervisor message `dev`'s watcher waits for, the addresses, and what mounted. */
@@ -108,16 +108,13 @@ export function serve(service, bind, onListening) {
108
108
  });
109
109
  }
110
110
  /** Start a Cloudflare tunnel for route channels only. */
111
- function maybeTunnel(agentDir, routeChannels, boundPort, tunnel, stateRoot) {
111
+ function maybeTunnel(agentDir, routeChannels, boundPort, tunnel) {
112
112
  if (!tunnel || process.env.FASTAGENT_DEV_WORKER === "1")
113
113
  return;
114
114
  void startCloudflareTunnel(boundPort).then((instance) => {
115
115
  if (!instance)
116
116
  return;
117
- void announceWebhooks(agentDir, instance.url, declaredChannels(routeChannels), {
118
- openUrl: openExternalUrl,
119
- stateRoot,
120
- });
117
+ void announceWebhooks(agentDir, instance.url, declaredChannels(routeChannels), { openUrl: openExternalUrl });
121
118
  const cleanup = () => instance.close();
122
119
  process.once("SIGINT", cleanup);
123
120
  process.once("SIGTERM", cleanup);
@@ -1,6 +1,10 @@
1
1
  import { type LoginIO } from "../harnesses/models/login.ts";
2
2
  import { type ModelEnvironment } from "../harnesses/models/environment.ts";
3
3
  import type { OpenedServing } from "../harnesses/pi-durable/serve.ts";
4
+ import { type Project } from "../harnesses/pi-durable/open.ts";
5
+ import { type InstanceFlags } from "./instance.ts";
6
+ /** The project a command runs, when it is not the development one (`start` runs the build). */
7
+ type LoadProject = (agentDir: string) => Promise<Project>;
4
8
  /** An agent directory a command entered, and the default model that then resolves, if any. */
5
9
  export interface EnteredAgent {
6
10
  agentDir: string;
@@ -15,7 +19,8 @@ export interface EnteredAgent {
15
19
  export declare function enterAgentDirectory(dirArg: string, opts: {
16
20
  model?: string;
17
21
  input?: boolean;
18
- }, before?: (agentDir: string) => Promise<void>): Promise<EnteredAgent>;
22
+ instance?: InstanceFlags;
23
+ }, before?: (agentDir: string) => Promise<void>, load?: LoadProject): Promise<EnteredAgent>;
19
24
  /**
20
25
  * How every command that RUNS the agent on this machine enters it (dev, start, invoke, schedules run):
21
26
  * {@link enterAgentDirectory}, plus a default model. The agent opens without one, but a process here would then fail
@@ -24,7 +29,8 @@ export declare function enterAgentDirectory(dirArg: string, opts: {
24
29
  export declare function enterAgentCommand(dirArg: string, opts: {
25
30
  model?: string;
26
31
  input?: boolean;
27
- }, before?: (agentDir: string) => Promise<void>): Promise<Required<EnteredAgent>>;
32
+ instance?: InstanceFlags;
33
+ }, before?: (agentDir: string) => Promise<void>, load?: LoadProject): Promise<Required<EnteredAgent>>;
28
34
  /** What `dev` and `start` say about the agent they just opened, in the order they say it. */
29
35
  export declare function reportAssembly(opened: OpenedServing, modelSpec: string, extras?: [label: string, value: string][]): Promise<void>;
30
36
  /** Both stdin and stdout are a terminal — the precondition for an interactive prompt. */
@@ -38,7 +44,10 @@ export declare function parsePort(value: string | undefined, source: string, fro
38
44
  export declare function parseBind(value: string | undefined): string | undefined;
39
45
  /** Report which source provides the model's credentials, surfacing a remediation hint at startup. */
40
46
  export declare function reportAuth(models: ModelEnvironment, modelSpec: string, agentDir: string): Promise<void>;
47
+ /** The line of `fastagent.config.ts` that keeps a picked model: what the picker prints, and what it must accept. */
48
+ export declare function pickedModelLine(model: string): string;
41
49
  /** Login terminal IO via @clack/prompts: a searchable list once long, a hidden prompt for keys. */
42
50
  export declare function terminalLoginIO(): LoginIO;
43
51
  /** A copy/paste-safe POSIX shell argument for a command the CLI prints. */
44
52
  export declare function shellArg(value: string): string;
53
+ export {};
@@ -2,6 +2,7 @@
2
2
  * Helpers shared across command modules: interactivity gates, port parsing, the startup auth report, first-run model
3
3
  * resolution, and the login terminal IO.
4
4
  */
5
+ import { definedValues } from "../declared-secrets.js";
5
6
  import { resolve } from "node:path";
6
7
  import { autocomplete, isCancel, log as clackLog, password, select, text as clackText } from "@clack/prompts";
7
8
  import { buildModelPickerOptions } from "./models-view.js";
@@ -9,12 +10,13 @@ import { loginFlow } from "../harnesses/models/login.js";
9
10
  import { SignInCancelled } from "../sign-in-api.js";
10
11
  import { modelEnvironment } from "../harnesses/models/environment.js";
11
12
  import { listModels, providerAuthStatuses, providerOf } from "../harnesses/models/models.js";
12
- import { definitionProviders, loadConfig, modelSpecFor } from "../harnesses/pi-durable/open.js";
13
+ import { resolveConfig } from "../harnesses/pi-durable/config.js";
14
+ import { buildDefinitionShape, definitionProviders, loadDevProject, modelSpecFor, } from "../harnesses/pi-durable/open.js";
13
15
  import { formatAuthReport } from "./auth-view.js";
14
16
  import { contentLines } from "./content-view.js";
15
17
  import { deployedHost, isDeployedWorkspace } from "../paths.js";
16
18
  import { log } from "../log.js";
17
- import { enterAgentEnv } from "../env.js";
19
+ import { enterInstance } from "./instance.js";
18
20
  import { dotEnvPath } from "../value-file.js";
19
21
  import { openExternalUrl } from "../open-url.js";
20
22
  import { bindAddress, isBindAddress } from "../bind.js";
@@ -25,11 +27,11 @@ import { agentDirOrExit, failStartup, failUsage } from "./fail.js";
25
27
  * gates a missing model itself, by what will ship. `before`: what the command checks of the directory before a model
26
28
  * is asked for (`schedules run`: that the schedule it names exists).
27
29
  */
28
- export async function enterAgentDirectory(dirArg, opts, before) {
30
+ export async function enterAgentDirectory(dirArg, opts, before, load) {
29
31
  const agentDir = agentDirOrExit(resolve(dirArg));
30
- enterAgentEnv(agentDir);
32
+ enterInstance(agentDir, opts.instance);
31
33
  await before?.(agentDir);
32
- const modelSpec = await resolveFirstRunModel(agentDir, opts);
34
+ const modelSpec = await resolveFirstRunModel(agentDir, opts, load);
33
35
  return { agentDir, ...(modelSpec ? { modelSpec } : {}) };
34
36
  }
35
37
  /**
@@ -37,11 +39,11 @@ export async function enterAgentDirectory(dirArg, opts, before) {
37
39
  * {@link enterAgentDirectory}, plus a default model. The agent opens without one, but a process here would then fail
38
40
  * every new conversation, so it stops at startup instead: exit 2, the choice a terminal's picker would have asked for.
39
41
  */
40
- export async function enterAgentCommand(dirArg, opts, before) {
41
- const entered = await enterAgentDirectory(dirArg, opts, before);
42
+ export async function enterAgentCommand(dirArg, opts, before, load) {
43
+ const entered = await enterAgentDirectory(dirArg, opts, before, load);
42
44
  const { modelSpec } = entered;
43
45
  if (!modelSpec) {
44
- failUsage(`missing model: set --model, "model" in fastagent.config.ts, or FASTAGENT_MODEL (e.g. "openai/gpt-5.5"; ` +
46
+ failUsage(`missing model: set --model, "defaults.model" in fastagent.config.ts, or FASTAGENT_MODEL (e.g. "openai/gpt-5.5"; ` +
45
47
  "`fastagent models` lists them)");
46
48
  }
47
49
  return { ...entered, modelSpec };
@@ -52,11 +54,11 @@ function reportLine(label, value) {
52
54
  }
53
55
  /** What `dev` and `start` say about the agent they just opened, in the order they say it. */
54
56
  export async function reportAssembly(opened, modelSpec, extras = []) {
55
- const { definition, code, models, agentDir, stateRoot } = opened;
56
- reportLine("agent", `${definition.name} — ${agentDir}`);
57
+ const { code, models, agentDir, workdir, stateRoot, config } = opened;
58
+ reportLine("agent", `${opened.name} — ${workdir}`);
57
59
  for (const [label, value] of contentLines(opened.content))
58
60
  reportLine(label, value);
59
- reportLine("model", `${modelSpec}${definition.config.thinkingLevel ? ` (thinking: ${definition.config.thinkingLevel})` : ""}`);
61
+ reportLine("model", `${modelSpec}${config.thinkingLevel ? ` (thinking: ${config.thinkingLevel})` : ""}`);
60
62
  await reportAuth(models, modelSpec, agentDir);
61
63
  const names = (list) => list.map(({ name }) => name).join(", ") || "(none)";
62
64
  reportLine("tools", names(code.tools));
@@ -126,24 +128,35 @@ export async function reportAuth(models, modelSpec, agentDir) {
126
128
  * The default model, for every command that runs the agent: `--model` > `FASTAGENT_MODEL` > `fastagent.config.ts`. With
127
129
  * none and a terminal, the first-run picker chooses one for this run (and logs in to its provider when needed).
128
130
  */
129
- async function resolveFirstRunModel(agentDir, options = {}) {
130
- const config = await loadConfig(agentDir).catch(failStartup);
131
- const configured = modelSpecFor(config, options.model);
132
- if (configured)
133
- return configured;
131
+ async function resolveFirstRunModel(agentDir, options = {}, load) {
132
+ const configured = async () => {
133
+ const project = await (load ?? loadDevProject)(agentDir);
134
+ return { project, config: resolveConfig(project.definition, project.instance) };
135
+ };
136
+ // A custom loader's refusal is the caller's to report: AgentCore runs this inside a request, where an exit kills the container.
137
+ const { project, config } = await (load === undefined ? configured().catch(failStartup) : configured());
138
+ const spec = modelSpecFor(config, options.model);
139
+ if (spec)
140
+ return spec;
134
141
  if (options.input === false)
135
142
  return undefined; // --no-input: never prompt (clig) — the caller decides what's missing
136
143
  if (!isInteractive())
137
144
  return undefined; // CI/deploy: the caller raises the actionable missing-model error
138
- const providers = await definitionProviders(agentDir).catch(failStartup);
139
- const environment = await modelEnvironment(agentDir, {}, providers);
145
+ const providers = load === undefined
146
+ ? await definitionProviders(agentDir, process.env).catch(failStartup)
147
+ : buildDefinitionShape(project.definition, process.env).providers;
148
+ const environment = await modelEnvironment(agentDir, {}, providers, { environment: definedValues(process.env) });
140
149
  const chosen = await pickWithCredentials(environment.models, environment.auth?.path, providers);
141
150
  if (chosen === undefined)
142
151
  return undefined; // cancelled (or auth probe failed): the caller raises its clear error
143
152
  process.env.FASTAGENT_MODEL = chosen; // this process + any spawned dev worker inherits it
144
- console.error(`[fastagent] picked ${chosen} — set \`model: ${JSON.stringify(chosen)}\` in fastagent.config.ts to keep it`);
153
+ console.error(`[fastagent] picked ${chosen} — set \`${pickedModelLine(chosen)}\` in fastagent.config.ts to keep it`);
145
154
  return chosen;
146
155
  }
156
+ /** The line of `fastagent.config.ts` that keeps a picked model: what the picker prints, and what it must accept. */
157
+ export function pickedModelLine(model) {
158
+ return `defaults: { model: ${JSON.stringify(model)} }`;
159
+ }
147
160
  /** The credential-aware pick: full catalog annotated per provider, then the post-pick auth policy. */
148
161
  async function pickWithCredentials(models, authPath, providers) {
149
162
  let statuses;
@@ -41,6 +41,13 @@ export interface Ticket {
41
41
  }
42
42
  /** What the step's process says while it runs: the place to look when it fails. */
43
43
  export declare const ticketLogPath: (id: string) => string;
44
+ /**
45
+ * End the ticket `id` with `end`, unless something ended it first: the end is a file created once, by a hard link,
46
+ * which the kernel lets only one writer make, so two that both read the ticket as running (the step finishing as a
47
+ * person stops it) end it once. Answers the ticket as whoever ended it ended it. Not public: `update` ends a ticket,
48
+ * and a test seam.
49
+ */
50
+ export declare function endTicket(id: string, end: Partial<Ticket>): Ticket;
44
51
  export interface TicketRequest {
45
52
  subject: string;
46
53
  action: string;
@@ -6,9 +6,11 @@
6
6
  * there is something for the person to do. The command's `--check <ticket>` reads where it stands from any process,
7
7
  * and hands the step the address a browser ended on when that browser could not reach this machine.
8
8
  *
9
- * A ticket is a file in the machine's own state, ~/.fastagent/.state/tickets, written whole by one process at a time:
10
- * the starting command until the step starts, then the step alone (or whoever finds its process gone). One that has
11
- * ended stays as it ended. A newer step for the same subject stops an older one still waiting on its person, whose
9
+ * A ticket is two files in the machine's own state, ~/.fastagent/.state/tickets. One is the step as its process keeps
10
+ * it, written by one process at a time: the starting command until the step starts, then the step. The other is its
11
+ * end, written once, by whoever ends it first: the step itself, or a process that stops it, replaces it or finds it
12
+ * gone. An end is decided before what it decides is done, so a stop is the end of the step it stops even when the step
13
+ * hears the consequences of being stopped first, and nothing the step writes after reopens it. A newer step for the same subject stops an older one still waiting on its person, whose
12
14
  * process would otherwise hold what it holds (a sign-in's callback port, OpenAI's fixed) until it expires; so does one
13
15
  * still getting ready (reaching a box, opening a tunnel), which may hang there. One finishing what its person did (a
14
16
  * code exchanged, an app created from a pasted token) is never stopped halfway, and the newer attempt is answered
@@ -18,9 +20,10 @@
18
20
  * stopped with it, by `stop` and when it exits.
19
21
  */
20
22
  import { randomBytes } from "node:crypto";
21
- import { closeSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, rmSync } from "node:fs";
23
+ import { closeSync, existsSync, linkSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, rmSync, statSync, writeFileSync, } from "node:fs";
22
24
  import { spawn } from "node:child_process";
23
25
  import { join } from "node:path";
26
+ import { stripVTControlCharacters } from "node:util";
24
27
  import { writeFileAtomic } from "../atomic-write.js";
25
28
  import { STATE_DIRNAME, globalHome } from "../paths.js";
26
29
  import { recordFailures } from "./fail.js";
@@ -31,6 +34,7 @@ const ADDRESS_TIMEOUT_MS = 15_000;
31
34
  /** How long a stopped step's process has to let go of what it holds before it is killed. */
32
35
  const STOP_TIMEOUT_MS = 5_000;
33
36
  const POLL_MS = 200;
37
+ const DAY_MS = 24 * 60 * 60 * 1000;
34
38
  /** Ticket files are random hex: anything else names no ticket, and no file outside the directory. */
35
39
  const TICKET_ID = /^[0-9a-f]{16}$/;
36
40
  const ticketsDir = () => join(globalHome(), STATE_DIRNAME, "tickets");
@@ -38,11 +42,10 @@ const ticketPath = (id) => join(ticketsDir(), `${id}.json`);
38
42
  const addressPath = (id) => join(ticketsDir(), `${id}.address`);
39
43
  /** What the step's process says while it runs: the place to look when it fails. */
40
44
  export const ticketLogPath = (id) => join(ticketsDir(), `${id}.log`);
41
- function readTicket(id) {
42
- if (!TICKET_ID.test(id))
43
- return undefined;
45
+ const endPath = (id) => join(ticketsDir(), `${id}.end.json`);
46
+ function readJson(path) {
44
47
  try {
45
- return JSON.parse(readFileSync(ticketPath(id), "utf8"));
48
+ return JSON.parse(readFileSync(path, "utf8"));
46
49
  }
47
50
  catch (error) {
48
51
  if (error.code === "ENOENT")
@@ -50,11 +53,45 @@ function readTicket(id) {
50
53
  throw error;
51
54
  }
52
55
  }
56
+ /** The ticket `id`: the step as its process keeps it, with its end over it once it has one. */
57
+ function readTicket(id) {
58
+ if (!TICKET_ID.test(id))
59
+ return undefined;
60
+ const kept = readJson(ticketPath(id));
61
+ if (kept === undefined)
62
+ return undefined;
63
+ const end = readJson(endPath(id));
64
+ return end === undefined ? kept : { ...kept, ...end };
65
+ }
66
+ /** The step as its process keeps it: one writer at a time, the starting command and then the step. */
53
67
  const writeTicket = (ticket) => writeFileAtomic(ticketPath(ticket.ticket), JSON.stringify(ticket));
54
68
  const waiting = (ticket) => ticket.status === "starting" || ticket.status === "need_user_action" || ticket.status === "working";
69
+ const ENDS = ["done", "failed", "expired"];
55
70
  /**
56
- * Change the ticket `id` while its step still runs; one that has ended is returned as it ended. A writer that read it
57
- * before it ended (a check that found its process gone the moment it finished) cannot reopen it.
71
+ * End the ticket `id` with `end`, unless something ended it first: the end is a file created once, by a hard link,
72
+ * which the kernel lets only one writer make, so two that both read the ticket as running (the step finishing as a
73
+ * person stops it) end it once. Answers the ticket as whoever ended it ended it. Not public: `update` ends a ticket,
74
+ * and a test seam.
75
+ */
76
+ export function endTicket(id, end) {
77
+ const draft = `${endPath(id)}.${process.pid}.${randomBytes(4).toString("hex")}.tmp`;
78
+ writeFileSync(draft, JSON.stringify(end));
79
+ try {
80
+ linkSync(draft, endPath(id));
81
+ }
82
+ catch (error) {
83
+ if (error.code !== "EEXIST")
84
+ throw error;
85
+ }
86
+ finally {
87
+ rmSync(draft, { force: true });
88
+ }
89
+ return readTicket(id);
90
+ }
91
+ /**
92
+ * Change the ticket `id` while its step still runs; one that has ended is returned as it ended. An end is
93
+ * `endTicket`'s, so the first one written is the one that stays; anything else is the step's own record, which its
94
+ * process alone writes.
58
95
  */
59
96
  function update(id, change) {
60
97
  const current = readTicket(id);
@@ -62,6 +99,8 @@ function update(id, change) {
62
99
  throw new Error(`ticket ${id} is gone from ${ticketsDir()}`);
63
100
  if (!waiting(current))
64
101
  return current;
102
+ if (change.status !== undefined && ENDS.includes(change.status))
103
+ return endTicket(id, change);
65
104
  const next = { ...current, ...change };
66
105
  writeTicket(next);
67
106
  return next;
@@ -141,24 +180,23 @@ async function supersede(subject, now) {
141
180
  if (finishing !== undefined)
142
181
  return finishing;
143
182
  for (const ticket of runs) {
183
+ update(ticket.ticket, { status: "failed", message: `replaced by a newer attempt to ${ticket.action}` });
144
184
  if (ticket.pid !== undefined)
145
185
  await stop(ticket.pid);
146
- update(ticket.ticket, { status: "failed", message: `replaced by a newer attempt to ${ticket.action}` });
147
186
  }
148
187
  return undefined;
149
188
  }
150
- /** Tickets a day past their end: each one's files go. */
189
+ /** Tickets a day past their end: every file of each one goes. */
151
190
  function prune(now) {
152
191
  if (!existsSync(ticketsDir()))
153
192
  return;
154
- for (const file of readdirSync(ticketsDir())) {
155
- const id = file.slice(0, 16);
156
- const ticket = TICKET_ID.test(id) ? readTicket(id) : undefined;
157
- if (ticket !== undefined && ticket.expiresAt < now - 24 * 60 * 60 * 1000) {
158
- for (const path of [ticketPath(id), addressPath(id), ticketLogPath(id)])
159
- rmSync(path, { force: true });
160
- }
161
- }
193
+ const files = readdirSync(ticketsDir());
194
+ const past = new Set(files
195
+ .map((file) => file.slice(0, 16))
196
+ .filter((id) => TICKET_ID.test(id) && (readTicket(id)?.expiresAt ?? Number.POSITIVE_INFINITY) < now - DAY_MS));
197
+ for (const file of files)
198
+ if (past.has(file.slice(0, 16)))
199
+ rmSync(join(ticketsDir(), file), { force: true });
162
200
  }
163
201
  /** A new ticket, its step not started yet. Not public: what `startTicket` starts, and a test seam. */
164
202
  export function createTicket(request) {
@@ -222,13 +260,10 @@ export async function startTicket(input) {
222
260
  // Claimed and at work: the step writes the ticket now, so this command reports it as it is.
223
261
  if (!exited && !input.waitsFirst && ticket.status === "working")
224
262
  return ticket;
225
- // Given up on, unclaimed or giving the person nothing: its process goes first, or it would hold its port and write
226
- // the ticket after this.
263
+ // Given up on, unclaimed or giving the person nothing: the end first, then its process, which would hold its port.
227
264
  const stoppedItself = exited;
228
- if (!stoppedItself && child.pid !== undefined)
229
- await stop(child.pid);
230
265
  const within = `${startTimeoutMs / 1000}s: ${ticketLogPath(id)} says why`;
231
- return update(id, {
266
+ const ended = update(id, {
232
267
  status: "failed",
233
268
  message: stoppedItself
234
269
  ? `it stopped before it started: ${ticketLogPath(id)} says why`
@@ -236,6 +271,9 @@ export async function startTicket(input) {
236
271
  ? `it did not start within ${within}`
237
272
  : `it gave the person nothing to do within ${within}`,
238
273
  });
274
+ if (!stoppedItself && child.pid !== undefined)
275
+ await stop(child.pid);
276
+ return ended;
239
277
  }
240
278
  /**
241
279
  * Where the step `id` stands. `address`: what the person's browser ended on (or the code its page showed), for a step
@@ -246,7 +284,7 @@ export async function checkTicket(id, address) {
246
284
  if (ticket === undefined)
247
285
  return undefined;
248
286
  if (address !== undefined && ticket.status === "need_user_action") {
249
- writeFileAtomic(addressPath(id), address);
287
+ writeFileAtomic(addressPath(id), address, undefined, true);
250
288
  for (const deadline = Date.now() + ADDRESS_TIMEOUT_MS; Date.now() < deadline;) {
251
289
  ticket = readTicket(id);
252
290
  if (ticket.status !== "need_user_action" || !running(ticket.pid))
@@ -271,12 +309,13 @@ export async function stopTicket(id) {
271
309
  throw new Error(`ticket ${id} is gone from ${ticketsDir()}`);
272
310
  if (!waiting(ticket))
273
311
  return ticket;
274
- if (ticket.pid !== undefined)
275
- await stop(ticket.pid);
276
- return update(id, {
312
+ const ended = update(id, {
277
313
  status: "failed",
278
314
  message: `stopped on request; what it was doing may be left half-done (${ticketLogPath(id)} says where it was)`,
279
315
  });
316
+ if (ticket.pid !== undefined)
317
+ await stop(ticket.pid);
318
+ return ended;
280
319
  }
281
320
  /**
282
321
  * Why the command whose `--check` is `check` cannot read ticket `id`: there is none on this machine, or it is another
@@ -385,6 +424,33 @@ async function awaitAddress(id, signal) {
385
424
  }
386
425
  throw new Error("the step no longer waits for an address");
387
426
  }
427
+ const LOG_TAIL_LINES = 12;
428
+ const LOG_TAIL_BYTES = 8192;
429
+ const LOG_LINE_CHARS = 300;
430
+ /**
431
+ * The last lines of a ticket's log, without terminal colors: where a failed step says why. A coding agent reads a
432
+ * report once, and "see the output above" points at a log it has not opened.
433
+ */
434
+ function logTail(id) {
435
+ const path = ticketLogPath(id);
436
+ if (!existsSync(path))
437
+ return [];
438
+ const size = statSync(path).size;
439
+ const start = Math.max(0, size - LOG_TAIL_BYTES);
440
+ const fd = openSync(path, "r");
441
+ try {
442
+ const buffer = Buffer.alloc(size - start);
443
+ readSync(fd, buffer, 0, buffer.length, start);
444
+ return stripVTControlCharacters(buffer.toString("utf8"))
445
+ .split("\n")
446
+ .map((line) => line.trim().slice(0, LOG_LINE_CHARS))
447
+ .filter((line) => line !== "")
448
+ .slice(-LOG_TAIL_LINES);
449
+ }
450
+ finally {
451
+ closeSync(fd);
452
+ }
453
+ }
388
454
  /**
389
455
  * A ticket as its command reports it, with what to do next (`agent_next_steps`): one JSON object, or its lines for a
390
456
  * person. `done` is what the step's `result` adds to the report, beside a sentence for the agent.
@@ -412,17 +478,27 @@ export function ticketReport(ticket, when, done = (result) => ({ fields: result,
412
478
  const message = ticket.status === "expired" ? "it was not finished in time" : ticket.message;
413
479
  // What the step reached before it ended is still the person's: where to get the value it stopped for, say.
414
480
  const reached = left.length === 0 ? "" : `Tell the user what the steps it reached leave them: ${left.join(" ")} `;
481
+ // An expired step is not a fault: nobody finished it in time, and its log says nothing more.
482
+ const expired = ticket.status === "expired";
483
+ const tail = expired ? [] : logTail(ticket.ticket);
415
484
  return {
416
485
  json: {
417
486
  ...base,
418
487
  message,
419
488
  log,
489
+ ...(tail.length === 0 ? {} : { log_tail: tail }),
420
490
  ...(ticket.afterwards ? { afterwards: ticket.afterwards } : {}),
421
- agent_next_steps: `${reached}To try again, run: ${ticket.restart} --json`,
491
+ agent_next_steps: expired
492
+ ? `${reached}The step was not finished in time, which is not a fault: ask the user to complete it as soon ` +
493
+ `as you run: ${ticket.restart} --json`
494
+ : `${reached}The cause is in log_tail (the whole log is at log): fix what it names, then run: ` +
495
+ `${ticket.restart} --json. The same inputs fail the same way unless the log shows a transient network or ` +
496
+ `builder error.`,
422
497
  },
423
498
  text: [
424
499
  `${ticket.status}: ${message}`,
425
500
  `log: ${log}`,
501
+ ...tail.map((line) => ` | ${line}`),
426
502
  ...left.map((line) => `left for the person: ${line}`),
427
503
  `to try again: ${ticket.restart}`,
428
504
  ],
@@ -1,62 +1,56 @@
1
- /** One content entry as `context.json` holds it, under its name: a repository. */
2
- export interface ContentEntry {
1
+ /** One content entry as a file holds it, under its name: a repository, or a directory of this machine. */
2
+ export type ContentEntry = {
3
3
  /** The repository, `owner/repo`. */
4
4
  github: string;
5
5
  /** The branch, tag or commit to clone. */
6
6
  ref?: string;
7
7
  /** One sentence for the agent: what this is, and how to treat it. */
8
8
  description?: string;
9
- }
9
+ } | {
10
+ /** Absolute, `~/`, or relative to the instance configuration. */
11
+ path: string;
12
+ description?: string;
13
+ };
10
14
  /** A declared entry, read. */
11
- export interface DeclaredContent {
15
+ export type DeclaredContent = {
12
16
  name: string;
17
+ description?: string;
18
+ /** The input that declared it. */
19
+ origin: "program" | "instance";
20
+ } & ({
21
+ kind: "github";
13
22
  /** `owner/repo`. */
14
23
  repo: string;
15
24
  ref?: string;
16
- description?: string;
17
- }
18
- /**
19
- * One binding as `context.local.json` holds it, under its name: a directory of this machine. Under the name of an
20
- * entry `context.json` declares it is this machine's checkout of that repository, and carries only `path`.
21
- */
22
- export interface ContentBinding {
23
- /** Absolute, `~/`, or relative to the agent directory. */
24
- path: string;
25
- description?: string;
26
- }
27
- /** A binding, read: `path` absolute. */
28
- export type BoundContent = {
29
- name: string;
30
- path: string;
31
- } & ({
32
- /** This machine's checkout of the declared entry of the same name. */
33
- kind: "checkout";
34
25
  } | {
35
- /** A directory only this machine has. */
26
+ /** A directory of this machine, `path` absolute. */
36
27
  kind: "local";
37
- description?: string;
28
+ path: string;
38
29
  });
39
- /** What an agent's content is declared as: the shared declaration, and this machine's bindings. */
40
- export interface ContentDeclaration {
41
- declared: DeclaredContent[];
42
- bound: BoundContent[];
30
+ /** Where a `content` map was read from, for what a refusal says and how a path is read. */
31
+ export interface ContentSource {
32
+ origin: DeclaredContent["origin"];
33
+ /** The file's name, or what an instance configuration is called. */
34
+ file: string;
35
+ /** What a relative path is relative to: the instance configuration's directory. None for one that is no file. */
36
+ base?: string;
43
37
  }
44
38
  /** Whether `name` can name a content entry. */
45
39
  export declare function isContentName(name: string): boolean;
46
40
  /** Whether `repo` names a GitHub repository, `owner/repo`. */
47
41
  export declare function isGithubRepo(repo: string): boolean;
48
- /** `entry` with its keys in {@link ENTRY_KEYS} order: the form written. */
42
+ /** `entry` with its keys in the order they are written. */
49
43
  export declare function canonicalEntry(entry: ContentEntry): ContentEntry;
50
- /** `binding` with its keys in {@link BINDING_KEYS} order: the form written. */
51
- export declare function canonicalBinding(binding: ContentBinding): ContentBinding;
52
- /** Read `context.json`'s `content` map (undefined is none). Every refusal names the entry; nothing is defaulted. */
53
- export declare function declareContent(raw: unknown): DeclaredContent[];
54
44
  /**
55
- * Read `context.local.json`'s `content` map (undefined is none) beside `declared`, with relative paths resolved against
56
- * `agentDir`. A binding under a declared entry's name is that entry's checkout here; any other is a directory of this
57
- * machine alone.
45
+ * Read a `content` map (undefined is none) from `from`. Every refusal names the entry; nothing is defaulted. The
46
+ * program's declares repositories only, since it is shared; an instance's may name a directory too.
47
+ */
48
+ export declare function declareContent(raw: unknown, from: ContentSource): DeclaredContent[];
49
+ /**
50
+ * The program's entries and the instance's, as one list. An instance only adds: a name both declare (ignoring case,
51
+ * as names are a directory) is refused, naming both, so nothing is shadowed.
58
52
  */
59
- export declare function declareBindings(raw: unknown, declared: readonly DeclaredContent[], agentDir: string): BoundContent[];
53
+ export declare function joinContent(program: readonly DeclaredContent[], instance: readonly DeclaredContent[]): DeclaredContent[];
60
54
  /**
61
55
  * A directory the agent is bound to and the agent directory are kept apart (agent-model.md §2): a definition is
62
56
  * released, content is kept, and one directory cannot be both. The ONE statement of the rule; resolve.ts asks