@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,4 +1,4 @@
1
- import { watchEvents, } from "@earendil-works/pi-durable";
1
+ import { AgentDoc, InboxDoc, LiveDoc, UsageDoc, } from "@earendil-works/pi-durable";
2
2
  import * as Effect from "effect/Effect";
3
3
  import * as Queue from "effect/Queue";
4
4
  import * as Stream from "effect/Stream";
@@ -6,6 +6,8 @@ import { AgentError, agentError, errorData } from "../../agent-api.js";
6
6
  import { port, portError } from "../../effect-port.js";
7
7
  import { isPublished, replyText, thinkingOf } from "./entries.js";
8
8
  import { hasEnded, isInput, sessionOf } from "./messages.js";
9
+ /** Revisions a Caller may leave unread before its stream ends: pi-durable keeps no bound for a synchronous observer. */
10
+ const UNREAD_REVISIONS = 1000;
9
11
  /**
10
12
  * The session's events after the entry `after` names (`"start"`: every entry; absent: none). An unknown session or
11
13
  * entry, and a storage that fails before the snapshot, fail the stream; a failure after it is a `stream_error`.
@@ -25,22 +27,30 @@ export function sessionEvents({ harness, reads, ids, context, failureOf, maxRetr
25
27
  }
26
28
  after = entry.id;
27
29
  }
28
- const batches = yield* Effect.acquireRelease(Queue.bounded(1), Queue.shutdown);
29
- const watch = yield* Effect.acquireRelease(port(() => watchEvents(harness, conversation.id, context)), (stream) => Effect.promise(() => stream.stop()));
30
- // Taking one batch at a time keeps pi-durable's own bound on what a slow Caller leaves undelivered.
31
- watch.start((events) => Effect.runPromiseExit(Queue.offer(batches, { events })).then(() => undefined));
32
- void watch.closed.then((end) => Effect.runPromiseExit(Queue.offer(batches, { end })));
33
- const { snapshot } = watch;
34
- const newest = snapshot.entries.reduce((max, entry) => (max === undefined || entry.id > max ? entry.id : max), undefined);
35
- const live = liveTranslator(reads, maxRetries, snapshot);
30
+ const pending = yield* Effect.acquireRelease(Queue.unbounded(), Queue.shutdown);
31
+ let behind = false;
32
+ const observation = yield* Effect.acquireRelease(port(() => harness.observeConversation(conversation.id, {
33
+ advance: (call) => {
34
+ if (behind || call.kind !== "value")
35
+ return;
36
+ behind = Queue.sizeUnsafe(pending) >= UNREAD_REVISIONS;
37
+ Queue.offerUnsafe(pending, behind ? { behind } : { view: call.value });
38
+ },
39
+ end: (end) => {
40
+ Queue.offerUnsafe(pending, { end });
41
+ },
42
+ }, context)), (attached) => Effect.sync(() => attached.detach()));
43
+ const snapshot = observation.value;
44
+ const newest = newestEntry(snapshot);
45
+ const live = liveTranslator(harness, reads, conversation, context, maxRetries, snapshot);
36
46
  const replay = after === undefined || newest === undefined || after >= newest
37
47
  ? Stream.empty
38
48
  : replayed(reads, conversation, after, newest);
39
49
  const present = Stream.succeed({
40
50
  type: "state",
41
- session: sessionOf(ids.encode(conversation.id), snapshot, snapshot.usage, snapshot.agent),
51
+ session: sessionOf(ids.encode(conversation.id), docOf(snapshot, LiveDoc), docOf(snapshot, UsageDoc), docOf(snapshot, AgentDoc)),
42
52
  });
43
- const events = Stream.fromQueue(batches).pipe(Stream.mapEffect((batch) => "end" in batch ? Effect.succeed([ended(batch.end)]) : live.translate(batch.events)), Stream.flattenIterable);
53
+ const events = Stream.fromQueue(pending).pipe(Stream.mapEffect((next) => ("view" in next ? live.translate(next.view) : Effect.succeed([ended(next)]))), Stream.flattenIterable);
44
54
  return Stream.concat(Stream.concat(replay, present), events).pipe(Stream.catchCause((cause) => Stream.succeed(streamError(failureOf(portError(cause))))), Stream.takeUntil((event) => event.type === "stream_error"));
45
55
  }).pipe(Effect.catchCause((cause) => {
46
56
  const error = portError(cause);
@@ -49,15 +59,22 @@ export function sessionEvents({ harness, reads, ids, context, failureOf, maxRetr
49
59
  }
50
60
  const streamError = (error) => ({ type: "stream_error", error: errorData(error) });
51
61
  function ended(end) {
52
- switch (end.reason) {
53
- case "session_failed":
54
- return streamError(agentError("unavailable", `The agent's storage failed: ${String(end.error)}`));
62
+ if ("behind" in end) {
63
+ return streamError(agentError("unavailable", "This stream fell behind the session; continue after its last entry"));
64
+ }
65
+ switch (end.end.kind) {
66
+ case "failed":
67
+ return streamError(agentError("unavailable", `The agent stopped serving this session: ${String(end.end.error)}`));
55
68
  case "listener_error":
56
- return streamError(agentError("internal", `Following the session failed: ${end.error.message}`));
69
+ return streamError(agentError("internal", `Following the session failed: ${end.end.error.message}`));
57
70
  default:
58
- return streamError(agentError("unavailable", `The agent stopped serving this session (${end.reason})`));
71
+ return streamError(agentError("unavailable", `The agent stopped serving this session (${end.end.kind})`));
59
72
  }
60
73
  }
74
+ /** The newest entry a view has seen: its window's last, or the one before an empty window. */
75
+ function newestEntry(view) {
76
+ return view.entries.at(-1)?.id ?? view.before;
77
+ }
61
78
  function replayed(reads, conversation, after, through) {
62
79
  return reads
63
80
  .publishedPages(conversation, {
@@ -68,145 +85,162 @@ function replayed(reads, conversation, after, through) {
68
85
  .pipe(Stream.mapEffect(reads.publish), Stream.flattenIterable, Stream.map(entryEvent));
69
86
  }
70
87
  const entryEvent = (entry) => ({ type: "entry", entry });
88
+ /** A built-in document of the view, as its token types it. */
89
+ const docOf = (view, token) => view.docs[token.definition.kind];
90
+ const liveOf = (view) => docOf(view, LiveDoc);
91
+ /** The inputs a view has not ended: queued in its inbox, or taken by its run. */
92
+ function unendedInputs(view) {
93
+ const inbox = docOf(view, InboxDoc);
94
+ return new Set([
95
+ ...(inbox?.items ?? []).flatMap((item) => (item.mode === "write" ? [] : [item.id])),
96
+ ...(liveOf(view)?.run?.inputs ?? []),
97
+ ]);
98
+ }
99
+ /** A reply's text and thinking as a Caller has been shown them. */
100
+ const shownOf = (content) => ({ text: replyText({ content }), thinking: thinkingOf({ content }) ?? "" });
71
101
  /**
72
- * What one commit's events add for a Caller: the reply's deltas, the tool calls that start and their status lines,
73
- * retries, the published entries, and a `done` for each message that ended, before the next message's entry.
102
+ * What one revision of the view adds for a Caller, against the revision before it: the reply's deltas, the tool calls
103
+ * that start and their status lines, retries, the entries appended, and a `done` for each message that ended, before
104
+ * the next message's entry.
74
105
  */
75
- function liveTranslator(reads, maxRetries, snapshot) {
76
- let reply = [...(snapshot.generation?.message?.content ?? [])];
77
- let shown = { text: replyText({ content: reply }), thinking: thinkingOf({ content: reply }) ?? "" };
78
- const outputs = new Map(snapshot.tools.map((slot) => [slot.callId, slot.output ?? ""]));
79
- const lines = new Map();
80
- const outer = new Map(snapshot.nestedTools.map((slot) => [slot.callId, slot.parentCallId]));
106
+ function liveTranslator(harness, reads, conversation, context, maxRetries, snapshot) {
107
+ const first = liveOf(snapshot);
108
+ let newest = newestEntry(snapshot);
109
+ let shown = shownOf(first?.generation?.message?.content ?? []);
110
+ let retry = retryKey(first);
111
+ let unended = unendedInputs(snapshot);
112
+ const toolArgs = new Map();
113
+ const started = new Set((first?.tools ?? []).filter((slot) => slot.status !== "pending").map((slot) => slot.callId));
114
+ const lines = new Map((first?.tools ?? []).flatMap((slot) => lineOf(slot.callId, slot.output)));
115
+ const nested = new Map((first?.nestedTools ?? []).map((slot) => [slot.callId, slot.parentCallId]));
81
116
  const outermost = (callId) => {
82
- const parent = outer.get(callId);
117
+ const parent = nested.get(callId);
83
118
  return parent === undefined ? callId : outermost(parent);
84
119
  };
85
- const restart = () => {
86
- reply = [];
87
- shown = { text: "", thinking: "" };
120
+ const learnToolArgs = (records) => {
121
+ for (const record of records) {
122
+ const model = record.model?.[0];
123
+ if (model?.role !== "assistant")
124
+ continue;
125
+ for (const block of model.content)
126
+ if (block.type === "toolCall")
127
+ toolArgs.set(block.id, block.arguments);
128
+ }
129
+ };
130
+ learnToolArgs(snapshot.entries);
131
+ /**
132
+ * The answers that made calls now starting, read back through the history when the view's window began after them
133
+ * (a Caller attached late in a long round). A call no answer made stays unknown, and `toolEvents` refuses it.
134
+ */
135
+ const recallToolArgs = (live) => {
136
+ const missing = (live?.tools ?? []).filter((slot) => slot.status !== "pending" && !started.has(slot.callId) && !toolArgs.has(slot.callId));
137
+ if (missing.length === 0)
138
+ return Effect.void;
139
+ const known = () => missing.every((slot) => toolArgs.has(slot.callId));
140
+ return reads.history(conversation, { order: "descending" }).pipe(Stream.tap((record) => Effect.sync(() => learnToolArgs([record]))), Stream.takeUntil(known), Stream.runDrain);
88
141
  };
89
- const deltas = () => {
90
- const now = { text: replyText({ content: reply }), thinking: thinkingOf({ content: reply }) ?? "" };
91
- const added = (channel) => now[channel].length > shown[channel].length && now[channel].startsWith(shown[channel])
92
- ? [{ type: "delta", channel, text: now[channel].slice(shown[channel].length) }]
93
- : [];
142
+ const deltas = (content) => {
143
+ const now = shownOf(content);
144
+ const added = (channel) => {
145
+ if (now[channel].length <= shown[channel].length)
146
+ return [];
147
+ const from = now[channel].startsWith(shown[channel]) ? shown[channel].length : 0;
148
+ return [{ type: "delta", channel, text: now[channel].slice(from) }];
149
+ };
94
150
  const events = [...added("thinking"), ...added("text")];
95
151
  shown = now;
96
152
  return events;
97
153
  };
98
- const apply = (changes) => {
99
- for (const change of changes) {
100
- switch (change.type) {
101
- case "message":
102
- reply = [...change.message.content];
103
- break;
104
- case "text_start":
105
- case "thinking_start":
106
- case "toolcall_start":
107
- case "block":
108
- reply[change.contentIndex] = change.block;
109
- break;
110
- case "text_delta": {
111
- const block = reply[change.contentIndex];
112
- if (block?.type === "text")
113
- reply[change.contentIndex] = { ...block, text: block.text + change.delta };
114
- break;
115
- }
116
- case "thinking_delta": {
117
- const block = reply[change.contentIndex];
118
- if (block?.type === "thinking") {
119
- reply[change.contentIndex] = { ...block, thinking: block.thinking + change.delta };
120
- }
121
- break;
122
- }
123
- case "toolcall_delta":
124
- break;
125
- }
126
- }
127
- };
128
154
  const progress = (toolCallId, text) => {
129
155
  if (text === undefined || lines.get(toolCallId) === text)
130
156
  return [];
131
157
  lines.set(toolCallId, text);
132
158
  return [{ type: "tool_progress", toolCallId, text }];
133
159
  };
160
+ const toolEvents = (live) => {
161
+ const events = [];
162
+ const tools = live?.tools ?? [];
163
+ for (const slot of tools) {
164
+ if (slot.status === "pending")
165
+ continue;
166
+ if (!started.has(slot.callId)) {
167
+ started.add(slot.callId);
168
+ const args = toolArgs.get(slot.callId);
169
+ if (args === undefined)
170
+ throw new Error(`tool call ${slot.callId} started with no answer that made it`);
171
+ events.push({ type: "tool_started", toolCallId: slot.callId, name: slot.name, args });
172
+ }
173
+ if (slot.status === "running")
174
+ events.push(...progress(slot.callId, lastLine(slot.output ?? "")));
175
+ }
176
+ const calls = live?.nestedTools ?? [];
177
+ for (const slot of calls) {
178
+ if (nested.has(slot.callId))
179
+ continue;
180
+ nested.set(slot.callId, slot.parentCallId);
181
+ events.push(...progress(outermost(slot.callId), callLine(slot.name, slot.arguments)));
182
+ }
183
+ const current = new Set([...tools, ...calls].map((slot) => slot.callId));
184
+ for (const callId of [...started, ...lines.keys(), ...nested.keys(), ...toolArgs.keys()]) {
185
+ if (current.has(callId))
186
+ continue;
187
+ started.delete(callId);
188
+ lines.delete(callId);
189
+ nested.delete(callId);
190
+ toolArgs.delete(callId);
191
+ }
192
+ return events;
193
+ };
194
+ /** The entries `view` appended: its window's new ones, after those a window that moved past them left out. */
195
+ const appended = (view) => Effect.gen(function* () {
196
+ const fresh = view.entries.filter((entry) => newest === undefined || entry.id > newest);
197
+ const skipped = view.before === undefined || (newest !== undefined && view.before <= newest)
198
+ ? []
199
+ : yield* reads
200
+ .history(conversation, {
201
+ order: "ascending",
202
+ minEntryId: ((newest ?? 0) + 1),
203
+ maxEntryId: view.before,
204
+ })
205
+ .pipe(Stream.runCollect);
206
+ const records = [...skipped, ...fresh];
207
+ newest = records.at(-1)?.id ?? newest;
208
+ return records;
209
+ });
210
+ const endedMessages = (view, known) => {
211
+ const now = unendedInputs(view);
212
+ const gone = [...unended].filter((id) => !now.has(id));
213
+ unended = now;
214
+ return Effect.forEach(gone, (id) => port(async () => (await harness.submission(id, context))?.status(context)).pipe(Effect.flatMap((record) => isInput(record) && hasEnded(record) ? reads.message(record, known) : Effect.undefined))).pipe(Effect.map((messages) => messages.flatMap((message) => (message === undefined ? [] : [message]))));
215
+ };
134
216
  return {
135
- translate: (events) => Effect.gen(function* () {
217
+ translate: (view) => Effect.gen(function* () {
218
+ const records = yield* appended(view);
219
+ learnToolArgs(records);
220
+ const live = liveOf(view);
136
221
  const before = [];
137
- const entries = [];
138
- const settled = [];
139
- for (const [index, event] of events.entries()) {
140
- switch (event.type) {
141
- case "snapshot":
142
- return [
143
- streamError(agentError("unavailable", "This stream fell behind the session; continue after its last entry")),
144
- ];
145
- case "message_start":
146
- // An entry's own start comes right before its end; any other is a new reply being written.
147
- if (events[index + 1]?.type !== "message_end" && event.message.role === "assistant") {
148
- reply = [...event.message.content];
149
- shown = { text: "", thinking: "" };
150
- before.push(...deltas());
151
- }
152
- break;
153
- case "message_update":
154
- apply(event.changes);
155
- before.push(...deltas());
156
- break;
157
- case "message_end":
158
- case "entry_appended":
159
- entries.push(event.entry);
160
- if (event.entry.model?.[0]?.role === "assistant")
161
- restart();
162
- break;
163
- case "tool_execution_start":
164
- if (event.parentToolCallId === undefined) {
165
- before.push({
166
- type: "tool_started",
167
- toolCallId: event.toolCallId,
168
- name: event.toolName,
169
- args: event.args,
170
- });
171
- }
172
- else {
173
- outer.set(event.toolCallId, event.parentToolCallId);
174
- before.push(...progress(outermost(event.toolCallId), callLine(event.toolName, event.args)));
175
- }
176
- break;
177
- case "tool_execution_update": {
178
- if (event.parentToolCallId !== undefined || event.output === undefined)
179
- break;
180
- const output = event.output;
181
- const current = outputs.get(event.toolCallId) ?? "";
182
- const next = "set" in output ? output.set : current.slice(output.trimStart ?? 0) + (output.append ?? "");
183
- outputs.set(event.toolCallId, next);
184
- before.push(...progress(event.toolCallId, lastLine(next)));
185
- break;
186
- }
187
- case "auto_retry_start":
188
- restart();
189
- before.push({
190
- type: "retrying",
191
- attempt: event.attempt,
192
- maxAttempts: maxRetries,
193
- delayMs: Math.max(0, event.at - Date.now()),
194
- reason: event.errorMessage,
195
- });
196
- break;
197
- case "tool_execution_end":
198
- outputs.delete(event.toolCallId);
199
- lines.delete(event.toolCallId);
200
- outer.delete(event.toolCallId);
201
- break;
202
- case "submission":
203
- if (isInput(event.record) && hasEnded(event.record))
204
- settled.push(event.record);
205
- break;
206
- }
222
+ const nextRetry = retryKey(live);
223
+ if (nextRetry !== undefined && nextRetry !== retry && live?.generation?.retry !== undefined) {
224
+ shown = shownOf([]);
225
+ before.push({
226
+ type: "retrying",
227
+ attempt: live.generation.attempt,
228
+ maxAttempts: maxRetries,
229
+ delayMs: Math.max(0, live.generation.retry.at - Date.now()),
230
+ reason: live.generation.retry.error,
231
+ });
232
+ }
233
+ retry = nextRetry;
234
+ const reply = live?.generation?.message?.content;
235
+ if (reply !== undefined)
236
+ before.push(...deltas(reply));
237
+ if (reply === undefined || records.some((record) => record.model?.[0]?.role === "assistant")) {
238
+ shown = shownOf([]);
207
239
  }
208
- const published = yield* reads.publish(entries.filter(isPublished));
209
- const dones = yield* Effect.forEach(settled, (record) => reads.message(record, entries));
240
+ yield* recallToolArgs(live);
241
+ before.push(...toolEvents(live));
242
+ const published = yield* reads.publish(records.filter(isPublished));
243
+ const dones = yield* endedMessages(view, records);
210
244
  // pi-durable places the next queued message in the commit that ends a run: its message's `done` goes first.
211
245
  const next = published.findIndex((entry) => entry.kind === "user");
212
246
  const at = next === -1 ? published.length : next;
@@ -219,6 +253,15 @@ function liveTranslator(reads, maxRetries, snapshot) {
219
253
  }),
220
254
  };
221
255
  }
256
+ /** Which retry a generation waits for, so each is reported once. */
257
+ function retryKey(live) {
258
+ const generation = live?.generation;
259
+ return generation?.retry === undefined ? undefined : `${generation.attempt}:${generation.retry.at}`;
260
+ }
261
+ function lineOf(callId, output) {
262
+ const line = lastLine(output ?? "");
263
+ return line === undefined ? [] : [[callId, line]];
264
+ }
222
265
  /** The last line with text, a running tool's status. pi-durable's output already has no control characters. */
223
266
  function lastLine(text) {
224
267
  const lines = text.split("\n");
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The agent's execution environment: pi-durable's local one, with the environment of every process it starts given
3
+ * outright. Its own `inheritEnv` would add this process's whole environment to a command's, and the bash tool asks for
4
+ * it on every call, so it is forced off here.
5
+ */
6
+ import type { Context } from "@earendil-works/chord";
7
+ import { NodeExecutionEnv } from "@earendil-works/pi-durable/env/node";
8
+ type Exec = NodeExecutionEnv["exec"];
9
+ export declare class InstanceExecutionEnv extends NodeExecutionEnv {
10
+ #private;
11
+ constructor(options: {
12
+ cwd: string;
13
+ processEnv: Readonly<Record<string, string>>;
14
+ });
15
+ exec(command: Parameters<Exec>[0], options: Parameters<Exec>[1], context: Context): ReturnType<Exec>;
16
+ }
17
+ export {};
@@ -0,0 +1,11 @@
1
+ import { NodeExecutionEnv } from "@earendil-works/pi-durable/env/node";
2
+ export class InstanceExecutionEnv extends NodeExecutionEnv {
3
+ #processEnv;
4
+ constructor(options) {
5
+ super({ cwd: options.cwd });
6
+ this.#processEnv = options.processEnv;
7
+ }
8
+ exec(command, options, context) {
9
+ return super.exec(command, { ...options, inheritEnv: false, env: { ...this.#processEnv, ...options?.env } }, context);
10
+ }
11
+ }
@@ -1,12 +1,10 @@
1
- /** MCP tools on pi-durable. No replay promise is inferred from a server's annotations. */
2
- import { Type } from "@earendil-works/pi-ai";
3
1
  import { defineTool } from "@earendil-works/pi-durable";
4
2
  import { McpError } from "../../mcp-api.js";
5
3
  export function mcpTools(tools) {
6
4
  return tools.map((tool) => defineTool({
7
5
  name: tool.name,
8
6
  description: tool.definition.description ?? `MCP tool ${tool.definition.name} from ${tool.server}`,
9
- parameters: Type.Unsafe(tool.definition.inputSchema),
7
+ parameters: tool.definition.inputSchema,
10
8
  replay: "unsafe",
11
9
  execute: async (args, api, context) => {
12
10
  try {
@@ -1,38 +1,24 @@
1
1
  import type { ModelThinkingLevel, Models, Provider } from "@earendil-works/pi-ai";
2
2
  import { type Extension, type HarnessSettings, type Storage, type ToolRegistration } from "@earendil-works/pi-durable";
3
3
  import type { ResolvedContent } from "../../content/resolve.ts";
4
- import { type DeclaredSecret } from "../../declared-secrets.ts";
5
4
  import type { ChannelModule, LongConnectionChannelModule } from "../../channel.ts";
5
+ import type { RunsAs } from "../../serving-command.ts";
6
6
  import { type Schedule } from "../../schedule/schedule.ts";
7
7
  import { type DurableAgent, type Unfinished } from "./agent.ts";
8
- import { type AgentConfig } from "./define.ts";
9
- import { type Registry } from "./registry.ts";
10
- /** An agent package as it was read: where it is, its configuration, and the modules its registry lists. */
11
- export interface AgentDefinition {
12
- dir: string;
13
- name: string;
14
- config: AgentConfig;
15
- registry: Registry;
16
- }
17
- /** The agent package's `fastagent.config.ts`, checked; `{}` without one. */
18
- export declare function loadConfig(dir: string): Promise<AgentConfig>;
8
+ import { type EffectiveConfig, type InstanceConfigSource } from "./config.ts";
9
+ import { type AgentDefinition, type IngressConfig } from "./definition.ts";
10
+ import { type TextSources } from "./text-sources.ts";
19
11
  /** The default model a process here runs with: `--model` > `FASTAGENT_MODEL` > the configuration's `model`. */
20
- export declare function modelSpecFor(config: AgentConfig, flag: string | undefined, env?: NodeJS.ProcessEnv): string | undefined;
21
- /** Read the agent package in `dir`: `fastagent.config.ts` (optional) and `.fastagent/registry.ts`, generated first. */
22
- export declare function loadDefinition(directory: string): Promise<AgentDefinition>;
23
- export declare function definitionSecrets(definition: AgentDefinition): DeclaredSecret[];
24
- /**
25
- * The list a person deploying or running the agent reads instead of its code (`.fastagent/secrets.json`): each name
26
- * once, what it is for, whether the agent runs without it, and where it is declared.
27
- */
28
- export declare function secretsManifest(definition: AgentDefinition): {
29
- name: string;
30
- description?: string;
31
- optional: boolean;
32
- declaredIn: string[];
33
- }[];
34
- /** Generate the registry, read the definition, and write its secrets manifest beside the registry. */
35
- export declare function prepareDefinition(dir: string): Promise<AgentDefinition>;
12
+ export declare function modelSpecFor(config: Pick<EffectiveConfig, "model">, flag: string | undefined, env?: NodeJS.ProcessEnv): string | undefined;
13
+ /** A definition and the instance configuration it opens with: what a command resolves before it opens an agent. */
14
+ export interface Project {
15
+ definition: AgentDefinition;
16
+ instance?: InstanceConfigSource;
17
+ }
18
+ /** The development project in `dir`, as `dev` opens it: its source and the `fastagent.json` at its root, if any. */
19
+ export declare function loadDevProject(dir: string): Promise<Project>;
20
+ /** The source project in `dir` as a definition: `fastagent.config.ts` (optional) and the modules of `src/`. */
21
+ export declare function loadSourceDefinition(directory: string): Promise<AgentDefinition>;
36
22
  /** The definition's code, built with the values of the secrets it declares. */
37
23
  export interface BuiltDefinition {
38
24
  tools: ToolRegistration[];
@@ -43,30 +29,45 @@ export interface BuiltDefinition {
43
29
  /** What the scheduler fires, each named by its file. */
44
30
  schedules: Schedule[];
45
31
  }
32
+ /** What a build reads of an instance's configuration: its settings, and how its channels receive. */
33
+ export interface BuildConfig extends IngressConfig {
34
+ settings: Readonly<Record<string, unknown>>;
35
+ }
46
36
  /**
47
37
  * Refuse to run while a required secret has no value, naming where it is declared; otherwise build every module with
48
- * the values it declared, and refuse one that is not what its directory holds.
38
+ * the values it declared, and refuse one that is not what its directory holds. A channel that receives either way is
39
+ * built the way `config` chooses, and only that way's secrets are asked for.
49
40
  */
50
- export declare function buildDefinition(definition: AgentDefinition, env?: NodeJS.ProcessEnv): BuiltDefinition;
41
+ export declare function buildDefinition(definition: AgentDefinition, env: NodeJS.ProcessEnv, instance?: BuildConfig): BuiltDefinition;
51
42
  /**
52
43
  * The definition's code built only to read its shape (which channels, with which ingress, which schedules, which
53
44
  * providers): a declared secret `env` holds no value for has a stand-in, since the shape must not depend on whether
54
45
  * this machine holds the values. Whatever a build refuses, this refuses too.
55
46
  */
56
- export declare function buildDefinitionShape(definition: AgentDefinition, env: NodeJS.ProcessEnv): BuiltDefinition;
47
+ export declare function buildDefinitionShape(definition: AgentDefinition, env: NodeJS.ProcessEnv, config?: BuildConfig): BuiltDefinition;
57
48
  /**
58
49
  * The providers of the definition in `agentDir`, for a command that reads models without serving (`models`, `login`,
59
50
  * the first-run picker): with the values `env` holds and stand-ins for the rest, so listing a model or signing in to
60
51
  * it needs no secret the serve would. Without these a provider in code, or one replacing a built-in's id, would be
61
52
  * unknown there, or verified against the built-in's endpoint.
62
53
  */
63
- export declare function definitionProviders(agentDir: string, env?: NodeJS.ProcessEnv): Promise<Provider[]>;
54
+ export declare function definitionProviders(agentDir: string, env: NodeJS.ProcessEnv): Promise<Provider[]>;
64
55
  export interface AssembleAgentOptions {
65
56
  storage: Storage;
57
+ /** Who runs the instance, as far as the prompt says where the agent runs (deployed, on AgentCore). */
58
+ runsAs: RunsAs;
66
59
  /** What the agent works on (`context.json`'s content), resolved for this instance. */
67
60
  content?: readonly ResolvedContent[];
61
+ /** The instance's name (`instanceName`): what `info().name` reports. Default: the definition's, as one segment. */
62
+ name?: string;
63
+ /** The agent's working directory: the definition's own, unless the instance is elsewhere. */
64
+ workdir?: string;
68
65
  /** The definition's code, built once with its secrets (`buildDefinition`). */
69
66
  code: BuiltDefinition;
67
+ /** The configuration this instance opens with (`resolveConfig`). */
68
+ config: EffectiveConfig;
69
+ /** The text it reads for each message (`openText`). Default: the program's own. */
70
+ text?: TextSources;
70
71
  mcpTools?: ToolRegistration[];
71
72
  /**
72
73
  * pi-ai's built-ins and the definition's providers over the instance's credentials, lists refreshed: the model
@@ -84,11 +85,8 @@ export interface AssembleAgentOptions {
84
85
  /** Whether that scheduler runs now: an app's `openAgent` runs one only while it serves. Default always. */
85
86
  wakeTicking?: () => boolean;
86
87
  unfinished?: Unfinished;
87
- /**
88
- * What the agent's environment adds to this process's, for the commands it runs: its value file's variables and its
89
- * packages' PATH.
90
- */
91
- environment?: Readonly<Record<string, string>>;
88
+ /** The whole environment of the processes the agent starts (child-env.ts): nothing of this process's is added. */
89
+ processEnv: Readonly<Record<string, string>>;
92
90
  }
93
91
  /** Open the definition as an Agent over `models`: the coding tools, then its own. */
94
92
  export declare function assembleAgent(definition: AgentDefinition, options: AssembleAgentOptions): Promise<DurableAgent>;