@fastagent-sh/fastagent 0.24.3 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (480) hide show
  1. package/README.md +53 -82
  2. package/dist/agent-api.d.ts +452 -0
  3. package/dist/agent-api.js +201 -0
  4. package/dist/answer-text.d.ts +8 -0
  5. package/dist/answer-text.js +18 -0
  6. package/dist/atomic-write.d.ts +2 -2
  7. package/dist/atomic-write.js +2 -2
  8. package/dist/authoring.d.ts +77 -0
  9. package/dist/authoring.js +238 -0
  10. package/dist/channel.d.ts +29 -15
  11. package/dist/channel.js +4 -1
  12. package/dist/channels/agentcore-protocol.d.ts +36 -50
  13. package/dist/channels/agentcore-protocol.js +3 -3
  14. package/dist/channels/agentcore-service.d.ts +8 -18
  15. package/dist/channels/agentcore-service.js +108 -127
  16. package/dist/channels/agentcore.d.ts +8 -24
  17. package/dist/channels/agentcore.js +32 -65
  18. package/dist/channels/body.d.ts +0 -16
  19. package/dist/channels/body.js +0 -25
  20. package/dist/channels/busy.js +6 -1
  21. package/dist/channels/context.d.ts +4 -0
  22. package/dist/channels/context.js +35 -0
  23. package/dist/channels/discover.d.ts +18 -20
  24. package/dist/channels/discover.js +41 -99
  25. package/dist/channels/feishu/feishu-api.d.ts +54 -4
  26. package/dist/channels/feishu/feishu-api.js +192 -149
  27. package/dist/channels/feishu/feishu.d.ts +3 -0
  28. package/dist/channels/feishu/feishu.js +117 -106
  29. package/dist/channels/feishu/history.d.ts +55 -0
  30. package/dist/channels/feishu/history.js +199 -0
  31. package/dist/channels/feishu/invoke-turn.d.ts +13 -11
  32. package/dist/channels/feishu/invoke-turn.js +9 -10
  33. package/dist/channels/feishu/model.d.ts +2 -1
  34. package/dist/channels/feishu/normalize.js +100 -0
  35. package/dist/channels/feishu/preview.d.ts +2 -2
  36. package/dist/channels/feishu/register-webhook.d.ts +1 -0
  37. package/dist/channels/feishu/register-webhook.js +3 -2
  38. package/dist/channels/feishu/scaffold/channel.ts +65 -46
  39. package/dist/channels/feishu/scaffold/feishu-send.ts +38 -33
  40. package/dist/channels/feishu/scaffold/feishu-threads.ts +25 -0
  41. package/dist/channels/feishu/setup-mode.d.ts +45 -25
  42. package/dist/channels/feishu/setup-mode.js +74 -23
  43. package/dist/channels/feishu/shared-api.d.ts +24 -6
  44. package/dist/channels/feishu/shared-api.js +30 -24
  45. package/dist/channels/kit/context-buffer.d.ts +11 -2
  46. package/dist/channels/kit/context-buffer.js +7 -4
  47. package/dist/channels/kit/delivery.d.ts +19 -3
  48. package/dist/channels/kit/delivery.js +74 -15
  49. package/dist/channels/kit/event-stream.js +2 -1
  50. package/dist/channels/kit/invoke-turn-kit.d.ts +41 -41
  51. package/dist/channels/kit/invoke-turn-kit.js +62 -71
  52. package/dist/channels/kit/place-history.d.ts +107 -0
  53. package/dist/channels/kit/place-history.js +182 -0
  54. package/dist/channels/kit/preview-kit.d.ts +9 -5
  55. package/dist/channels/kit/preview-kit.js +36 -15
  56. package/dist/channels/kit/room-threads.d.ts +46 -0
  57. package/dist/channels/kit/room-threads.js +81 -0
  58. package/dist/channels/kit/seen.js +1 -1
  59. package/dist/channels/kit/stop-command.d.ts +6 -3
  60. package/dist/channels/kit/stop-command.js +20 -18
  61. package/dist/channels/kit/thread-fork.d.ts +17 -0
  62. package/dist/channels/kit/thread-fork.js +43 -0
  63. package/dist/channels/kit/thread-participants.js +1 -1
  64. package/dist/channels/kit/transport.d.ts +5 -0
  65. package/dist/channels/kit/transport.js +5 -0
  66. package/dist/channels/kit/turn-runner.d.ts +10 -9
  67. package/dist/channels/kit/turn-runner.js +17 -19
  68. package/dist/channels/kit/turn-store.d.ts +5 -4
  69. package/dist/channels/kit/turn-store.js +5 -5
  70. package/dist/channels/lark/lark.d.ts +3 -0
  71. package/dist/channels/lark/lark.js +5 -0
  72. package/dist/channels/lark/onboard.d.ts +5 -3
  73. package/dist/channels/lark/onboard.js +14 -14
  74. package/dist/channels/lark/scaffold/channel.ts +67 -44
  75. package/dist/channels/lark/scaffold/lark-send.ts +38 -33
  76. package/dist/channels/lark/scaffold/lark-threads.ts +25 -0
  77. package/dist/channels/respond.d.ts +0 -4
  78. package/dist/channels/respond.js +1 -1
  79. package/dist/channels/router.d.ts +18 -0
  80. package/dist/channels/router.js +68 -0
  81. package/dist/channels/serve.d.ts +5 -70
  82. package/dist/channels/serve.js +105 -287
  83. package/dist/channels/slack/history.d.ts +42 -0
  84. package/dist/channels/slack/history.js +282 -0
  85. package/dist/channels/slack/invoke-turn.d.ts +12 -5
  86. package/dist/channels/slack/invoke-turn.js +7 -5
  87. package/dist/channels/slack/manifest.d.ts +1 -4
  88. package/dist/channels/slack/manifest.js +22 -10
  89. package/dist/channels/slack/model.d.ts +2 -1
  90. package/dist/channels/slack/onboard.d.ts +0 -2
  91. package/dist/channels/slack/onboard.js +2 -4
  92. package/dist/channels/slack/onboarding-state.d.ts +0 -2
  93. package/dist/channels/slack/onboarding-state.js +0 -1
  94. package/dist/channels/slack/parse.d.ts +0 -5
  95. package/dist/channels/slack/parse.js +1 -16
  96. package/dist/channels/slack/preview.d.ts +2 -3
  97. package/dist/channels/slack/preview.js +78 -52
  98. package/dist/channels/slack/register-webhook.d.ts +1 -0
  99. package/dist/channels/slack/register-webhook.js +6 -5
  100. package/dist/channels/slack/scaffold/channel.ts +23 -24
  101. package/dist/channels/slack/scaffold/slack-send.ts +50 -43
  102. package/dist/channels/slack/scaffold/slack-threads.ts +29 -0
  103. package/dist/channels/slack/setup-server.d.ts +19 -0
  104. package/dist/channels/slack/setup-server.js +113 -11
  105. package/dist/channels/slack/shared-api.d.ts +15 -4
  106. package/dist/channels/slack/shared-api.js +19 -14
  107. package/dist/channels/slack/slack-api.d.ts +61 -2
  108. package/dist/channels/slack/slack-api.js +28 -5
  109. package/dist/channels/slack/slack.js +97 -52
  110. package/dist/channels/slack/welcomed.js +1 -1
  111. package/dist/channels/telegram/context-buffer.d.ts +6 -1
  112. package/dist/channels/telegram/context-buffer.js +19 -0
  113. package/dist/channels/telegram/invoke-turn.d.ts +8 -4
  114. package/dist/channels/telegram/invoke-turn.js +7 -7
  115. package/dist/channels/telegram/parse.d.ts +7 -2
  116. package/dist/channels/telegram/parse.js +7 -0
  117. package/dist/channels/telegram/preview.d.ts +2 -2
  118. package/dist/channels/telegram/register-webhook.d.ts +1 -0
  119. package/dist/channels/telegram/register-webhook.js +3 -2
  120. package/dist/channels/telegram/scaffold/channel.ts +27 -28
  121. package/dist/channels/telegram/scaffold/telegram-send.ts +61 -108
  122. package/dist/channels/telegram/shared-api.d.ts +34 -0
  123. package/dist/channels/telegram/shared-api.js +79 -0
  124. package/dist/channels/telegram/telegram-api.d.ts +15 -5
  125. package/dist/channels/telegram/telegram-api.js +10 -6
  126. package/dist/channels/telegram/telegram.js +27 -21
  127. package/dist/channels/telegram/turn-store.js +1 -1
  128. package/dist/channels/{kit/turn-queue.d.ts → turn-queue.d.ts} +2 -2
  129. package/dist/channels/{kit/turn-queue.js → turn-queue.js} +10 -10
  130. package/dist/cli/add-feishu.d.ts +42 -12
  131. package/dist/cli/add-feishu.js +177 -124
  132. package/dist/cli/add-slack.d.ts +8 -3
  133. package/dist/cli/add-slack.js +115 -63
  134. package/dist/cli/auth-view.d.ts +10 -3
  135. package/dist/cli/auth-view.js +8 -6
  136. package/dist/cli/box-login.d.ts +17 -7
  137. package/dist/cli/box-login.js +46 -12
  138. package/dist/cli/commands/add.d.ts +15 -4
  139. package/dist/cli/commands/add.js +252 -143
  140. package/dist/cli/commands/auth.d.ts +6 -0
  141. package/dist/cli/commands/auth.js +50 -0
  142. package/dist/cli/commands/content.d.ts +6 -0
  143. package/dist/cli/commands/content.js +45 -0
  144. package/dist/cli/commands/deploy/agentcore.js +50 -77
  145. package/dist/cli/commands/deploy/docker.js +38 -39
  146. package/dist/cli/commands/deploy/fly.js +21 -18
  147. package/dist/cli/commands/deploy/host-login.d.ts +26 -0
  148. package/dist/cli/commands/deploy/host-login.js +90 -0
  149. package/dist/cli/commands/deploy/railway.js +21 -26
  150. package/dist/cli/commands/deploy/shared.d.ts +45 -17
  151. package/dist/cli/commands/deploy/shared.js +34 -15
  152. package/dist/cli/commands/deploy.d.ts +0 -4
  153. package/dist/cli/commands/deploy.js +237 -50
  154. package/dist/cli/commands/destroy.js +8 -6
  155. package/dist/cli/commands/dev.d.ts +0 -2
  156. package/dist/cli/commands/dev.js +20 -19
  157. package/dist/cli/commands/env.d.ts +12 -0
  158. package/dist/cli/commands/env.js +190 -0
  159. package/dist/cli/commands/info.js +95 -156
  160. package/dist/cli/commands/init.d.ts +2 -2
  161. package/dist/cli/commands/init.js +35 -33
  162. package/dist/cli/commands/invoke.d.ts +6 -0
  163. package/dist/cli/commands/invoke.js +131 -16
  164. package/dist/cli/commands/login.d.ts +31 -2
  165. package/dist/cli/commands/login.js +413 -64
  166. package/dist/cli/commands/logout.d.ts +4 -0
  167. package/dist/cli/commands/logout.js +53 -0
  168. package/dist/cli/commands/logs.js +8 -6
  169. package/dist/cli/commands/models.d.ts +2 -1
  170. package/dist/cli/commands/models.js +39 -37
  171. package/dist/cli/commands/schedules.d.ts +21 -0
  172. package/dist/cli/commands/schedules.js +143 -0
  173. package/dist/cli/commands/start.d.ts +11 -10
  174. package/dist/cli/commands/start.js +35 -62
  175. package/dist/cli/content-view.d.ts +9 -0
  176. package/dist/cli/content-view.js +26 -0
  177. package/dist/cli/fail.d.ts +9 -12
  178. package/dist/cli/fail.js +17 -14
  179. package/dist/cli/invoke-stream.d.ts +12 -3
  180. package/dist/cli/invoke-stream.js +85 -23
  181. package/dist/cli/kernel.d.ts +9 -2
  182. package/dist/cli/kernel.js +15 -6
  183. package/dist/cli/login-relay.d.ts +3 -2
  184. package/dist/cli/login-relay.js +18 -6
  185. package/dist/cli/models-view.d.ts +5 -1
  186. package/dist/cli/models-view.js +1 -1
  187. package/dist/cli/person.d.ts +10 -0
  188. package/dist/cli/person.js +23 -0
  189. package/dist/cli/program.js +421 -203
  190. package/dist/cli/serve.d.ts +13 -63
  191. package/dist/cli/serve.js +31 -121
  192. package/dist/cli/shared.d.ts +21 -48
  193. package/dist/cli/shared.js +68 -89
  194. package/dist/cli/ticket.d.ts +136 -0
  195. package/dist/cli/ticket.js +476 -0
  196. package/dist/client.d.ts +7 -0
  197. package/dist/client.js +2 -0
  198. package/dist/content/declare.d.ts +70 -0
  199. package/dist/content/declare.js +163 -0
  200. package/dist/content/file.d.ts +32 -0
  201. package/dist/content/file.js +119 -0
  202. package/dist/content/git.d.ts +41 -0
  203. package/dist/content/git.js +217 -0
  204. package/dist/content/mount.d.ts +2 -0
  205. package/dist/content/mount.js +15 -0
  206. package/dist/content/resolve.d.ts +52 -0
  207. package/dist/content/resolve.js +151 -0
  208. package/dist/content/source.d.ts +27 -0
  209. package/dist/content/source.js +53 -0
  210. package/dist/conversation-sessions.d.ts +26 -0
  211. package/dist/conversation-sessions.js +99 -0
  212. package/dist/declared-secrets.d.ts +19 -37
  213. package/dist/declared-secrets.js +16 -58
  214. package/dist/deploy/agentcore/destroy.d.ts +3 -3
  215. package/dist/deploy/agentcore/destroy.js +46 -28
  216. package/dist/deploy/agentcore/forwarder.js +105 -22
  217. package/dist/deploy/agentcore/logs.js +1 -1
  218. package/dist/deploy/agentcore/plan.d.ts +10 -24
  219. package/dist/deploy/agentcore/plan.js +31 -135
  220. package/dist/deploy/agentcore/run.d.ts +1 -1
  221. package/dist/deploy/agentcore/run.js +8 -5
  222. package/dist/deploy/agentcore/shell.d.ts +1 -1
  223. package/dist/deploy/agentcore/shell.js +5 -5
  224. package/dist/deploy/api.d.ts +34 -0
  225. package/dist/deploy/api.js +50 -0
  226. package/dist/deploy/box-shell.d.ts +6 -0
  227. package/dist/deploy/box-shell.js +7 -0
  228. package/dist/deploy/build-context.d.ts +7 -5
  229. package/dist/deploy/build-context.js +41 -35
  230. package/dist/deploy/channel-ingress.d.ts +6 -1
  231. package/dist/deploy/channel-ingress.js +18 -5
  232. package/dist/deploy/container.d.ts +22 -9
  233. package/dist/deploy/container.js +96 -55
  234. package/dist/deploy/docker/plan.d.ts +3 -2
  235. package/dist/deploy/docker/plan.js +13 -14
  236. package/dist/deploy/docker/run.d.ts +2 -2
  237. package/dist/deploy/docker/run.js +8 -4
  238. package/dist/deploy/fly/plan.d.ts +1 -1
  239. package/dist/deploy/fly/plan.js +12 -21
  240. package/dist/deploy/fly/run.d.ts +10 -6
  241. package/dist/deploy/fly/run.js +11 -6
  242. package/dist/deploy/hosts.d.ts +2 -1
  243. package/dist/deploy/hosts.js +2 -1
  244. package/dist/deploy/preflight.d.ts +21 -23
  245. package/dist/deploy/preflight.js +222 -201
  246. package/dist/deploy/railway/plan.d.ts +8 -6
  247. package/dist/deploy/railway/plan.js +15 -58
  248. package/dist/deploy/railway/run.d.ts +8 -3
  249. package/dist/deploy/railway/run.js +12 -8
  250. package/dist/deploy/residency.d.ts +5 -11
  251. package/dist/deploy/residency.js +0 -6
  252. package/dist/deploy/runner.d.ts +1 -1
  253. package/dist/deploy/runner.js +1 -1
  254. package/dist/deploy/secrets.d.ts +16 -2
  255. package/dist/deploy/secrets.js +33 -6
  256. package/dist/deploy/workspace.d.ts +11 -6
  257. package/dist/deploy/workspace.js +55 -38
  258. package/dist/dev-supervisor.d.ts +11 -2
  259. package/dist/dev-supervisor.js +51 -26
  260. package/dist/effect-port.d.ts +4 -4
  261. package/dist/effect-port.js +4 -4
  262. package/dist/environment/apt.d.ts +4 -0
  263. package/dist/environment/apt.js +32 -0
  264. package/dist/environment/declare.d.ts +59 -0
  265. package/dist/environment/declare.js +210 -0
  266. package/dist/environment/mise.d.ts +50 -0
  267. package/dist/environment/mise.js +268 -0
  268. package/dist/feishu.d.ts +2 -2
  269. package/dist/feishu.js +2 -2
  270. package/dist/git.d.ts +13 -0
  271. package/dist/git.js +55 -0
  272. package/dist/harnesses/models/catalog.d.ts +20 -0
  273. package/dist/harnesses/models/catalog.js +254 -0
  274. package/dist/{engines/pi/auth.d.ts → harnesses/models/credentials.d.ts} +3 -3
  275. package/dist/{engines/pi/auth.js → harnesses/models/credentials.js} +3 -3
  276. package/dist/harnesses/models/environment.d.ts +43 -0
  277. package/dist/harnesses/models/environment.js +82 -0
  278. package/dist/{engines/pi → harnesses/models}/login.d.ts +57 -26
  279. package/dist/{engines/pi → harnesses/models}/login.js +187 -79
  280. package/dist/harnesses/models/models.d.ts +83 -0
  281. package/dist/harnesses/models/models.js +158 -0
  282. package/dist/harnesses/pi-durable/agent.d.ts +42 -0
  283. package/dist/harnesses/pi-durable/agent.js +579 -0
  284. package/dist/harnesses/pi-durable/defaults.d.ts +28 -0
  285. package/dist/harnesses/pi-durable/defaults.js +26 -0
  286. package/dist/harnesses/pi-durable/define.d.ts +77 -0
  287. package/dist/harnesses/pi-durable/define.js +28 -0
  288. package/dist/harnesses/pi-durable/entries.d.ts +32 -0
  289. package/dist/harnesses/pi-durable/entries.js +126 -0
  290. package/dist/harnesses/pi-durable/events.d.ts +25 -0
  291. package/dist/harnesses/pi-durable/events.js +238 -0
  292. package/dist/harnesses/pi-durable/ids.d.ts +16 -0
  293. package/dist/harnesses/pi-durable/ids.js +55 -0
  294. package/dist/harnesses/pi-durable/messages.d.ts +41 -0
  295. package/dist/harnesses/pi-durable/messages.js +116 -0
  296. package/dist/harnesses/pi-durable/open.d.ts +88 -0
  297. package/dist/harnesses/pi-durable/open.js +329 -0
  298. package/dist/harnesses/pi-durable/reads.d.ts +30 -0
  299. package/dist/harnesses/pi-durable/reads.js +94 -0
  300. package/dist/harnesses/pi-durable/registry.d.ts +18 -0
  301. package/dist/harnesses/pi-durable/registry.js +56 -0
  302. package/dist/harnesses/pi-durable/release.d.ts +13 -0
  303. package/dist/harnesses/pi-durable/release.js +40 -0
  304. package/dist/harnesses/pi-durable/serve.d.ts +80 -0
  305. package/dist/harnesses/pi-durable/serve.js +156 -0
  306. package/dist/harnesses/pi-durable/sessions.d.ts +27 -0
  307. package/dist/harnesses/pi-durable/sessions.js +47 -0
  308. package/dist/harnesses/pi-durable/storage.d.ts +4 -0
  309. package/dist/harnesses/pi-durable/storage.js +49 -0
  310. package/dist/harnesses/pi-durable/stream.d.ts +20 -0
  311. package/dist/harnesses/pi-durable/stream.js +221 -0
  312. package/dist/harnesses/pi-durable/text.d.ts +30 -0
  313. package/dist/harnesses/pi-durable/text.js +292 -0
  314. package/dist/harnesses/pi-durable/wake.d.ts +4 -0
  315. package/dist/harnesses/pi-durable/wake.js +81 -0
  316. package/dist/http/client.d.ts +6 -0
  317. package/dist/http/client.js +256 -0
  318. package/dist/http/contract.d.ts +541 -0
  319. package/dist/http/contract.js +179 -0
  320. package/dist/http/server.d.ts +34 -0
  321. package/dist/http/server.js +194 -0
  322. package/dist/index.d.ts +17 -4
  323. package/dist/index.js +10 -5
  324. package/dist/lark.d.ts +2 -2
  325. package/dist/lark.js +2 -2
  326. package/dist/locked-file.d.ts +17 -0
  327. package/dist/{engines/pi/locked-file.js → locked-file.js} +16 -10
  328. package/dist/once.d.ts +6 -0
  329. package/dist/once.js +23 -0
  330. package/dist/paths.d.ts +36 -52
  331. package/dist/paths.js +66 -182
  332. package/dist/runtime.d.ts +25 -2
  333. package/dist/runtime.js +27 -0
  334. package/dist/scaffold/add-channel.d.ts +14 -27
  335. package/dist/scaffold/add-channel.js +82 -180
  336. package/dist/scaffold/init.d.ts +17 -18
  337. package/dist/scaffold/init.js +87 -99
  338. package/dist/scaffold/templates/SYSTEM.md +12 -0
  339. package/dist/scaffold/templates/env.example +4 -10
  340. package/dist/scaffold/templates/fastagent.config.ts +21 -33
  341. package/dist/scaffold/templates/gitignore +8 -1
  342. package/dist/scaffold/templates/skills/writing-great-skills/SKILL.md +1 -2
  343. package/dist/scaffold/templates.d.ts +9 -4
  344. package/dist/scaffold/templates.js +7 -5
  345. package/dist/scaffold/vendor-skill.d.ts +1 -4
  346. package/dist/scaffold/vendor-skill.js +5 -6
  347. package/dist/schedule/cron.d.ts +13 -0
  348. package/dist/schedule/cron.js +52 -0
  349. package/dist/schedule/eventbridge-cron.d.ts +17 -0
  350. package/dist/schedule/eventbridge-cron.js +88 -0
  351. package/dist/schedule/schedule.d.ts +23 -0
  352. package/dist/schedule/schedule.js +34 -0
  353. package/dist/schedule/scheduler.d.ts +34 -33
  354. package/dist/schedule/scheduler.js +251 -137
  355. package/dist/schedule/state.d.ts +4 -14
  356. package/dist/schedule/state.js +7 -7
  357. package/dist/schedule/wake-alarm.d.ts +22 -2
  358. package/dist/schedule/wake-alarm.js +66 -20
  359. package/dist/schedule/wakeups.d.ts +16 -15
  360. package/dist/schedule/wakeups.js +47 -45
  361. package/dist/service.d.ts +56 -169
  362. package/dist/service.js +91 -187
  363. package/dist/serving-command.d.ts +10 -0
  364. package/dist/serving-command.js +16 -0
  365. package/dist/slack.d.ts +1 -1
  366. package/dist/slack.js +1 -1
  367. package/dist/{channels/kit/state.js → state-file.js} +6 -6
  368. package/dist/telegram.d.ts +1 -0
  369. package/dist/telegram.js +1 -0
  370. package/dist/tunnel.d.ts +3 -0
  371. package/dist/tunnel.js +12 -4
  372. package/dist/value-file.d.ts +21 -0
  373. package/dist/value-file.js +108 -0
  374. package/dist/version.d.ts +5 -0
  375. package/dist/version.js +14 -1
  376. package/package.json +18 -28
  377. package/dist/agent.d.ts +0 -106
  378. package/dist/agent.js +0 -21
  379. package/dist/channels/control.d.ts +0 -31
  380. package/dist/channels/control.js +0 -329
  381. package/dist/channels/define-channel.d.ts +0 -39
  382. package/dist/channels/define-channel.js +0 -9
  383. package/dist/channels/feishu/context-buffer.d.ts +0 -35
  384. package/dist/channels/feishu/context-buffer.js +0 -65
  385. package/dist/channels/feishu/scaffold/channel.websocket.ts +0 -31
  386. package/dist/channels/http.d.ts +0 -23
  387. package/dist/channels/http.js +0 -57
  388. package/dist/channels/lark/scaffold/channel.websocket.ts +0 -31
  389. package/dist/channels/slack/context-buffer.d.ts +0 -23
  390. package/dist/channels/slack/context-buffer.js +0 -37
  391. package/dist/channels/sse.d.ts +0 -4
  392. package/dist/channels/sse.js +0 -66
  393. package/dist/cli/commands/chat.d.ts +0 -3
  394. package/dist/cli/commands/chat.js +0 -14
  395. package/dist/cli/commands/routine-run.d.ts +0 -6
  396. package/dist/cli/commands/routine-run.js +0 -46
  397. package/dist/cli/commands/routine.d.ts +0 -25
  398. package/dist/cli/commands/routine.js +0 -122
  399. package/dist/cli/commands/tool.d.ts +0 -1
  400. package/dist/cli/commands/tool.js +0 -57
  401. package/dist/collect.d.ts +0 -63
  402. package/dist/collect.js +0 -90
  403. package/dist/core.d.ts +0 -13
  404. package/dist/core.js +0 -15
  405. package/dist/engines/pi/agent-models.d.ts +0 -83
  406. package/dist/engines/pi/agent-models.js +0 -120
  407. package/dist/engines/pi/agent-session-factory.d.ts +0 -121
  408. package/dist/engines/pi/agent-session-factory.js +0 -517
  409. package/dist/engines/pi/chat.d.ts +0 -3
  410. package/dist/engines/pi/chat.js +0 -8
  411. package/dist/engines/pi/config.d.ts +0 -71
  412. package/dist/engines/pi/config.js +0 -199
  413. package/dist/engines/pi/create.d.ts +0 -170
  414. package/dist/engines/pi/create.js +0 -343
  415. package/dist/engines/pi/definition.d.ts +0 -74
  416. package/dist/engines/pi/definition.js +0 -236
  417. package/dist/engines/pi/entry-images.d.ts +0 -14
  418. package/dist/engines/pi/entry-images.js +0 -23
  419. package/dist/engines/pi/invoke-session.d.ts +0 -18
  420. package/dist/engines/pi/invoke-session.js +0 -403
  421. package/dist/engines/pi/locked-file.d.ts +0 -9
  422. package/dist/engines/pi/machine.d.ts +0 -53
  423. package/dist/engines/pi/machine.js +0 -137
  424. package/dist/engines/pi/models.d.ts +0 -200
  425. package/dist/engines/pi/models.js +0 -533
  426. package/dist/engines/pi/open.d.ts +0 -180
  427. package/dist/engines/pi/open.js +0 -270
  428. package/dist/engines/pi/openai-account-models.d.ts +0 -35
  429. package/dist/engines/pi/openai-account-models.js +0 -116
  430. package/dist/engines/pi/report.d.ts +0 -18
  431. package/dist/engines/pi/report.js +0 -59
  432. package/dist/engines/pi/retry-event.d.ts +0 -6
  433. package/dist/engines/pi/retry-event.js +0 -15
  434. package/dist/engines/pi/service.d.ts +0 -9
  435. package/dist/engines/pi/service.js +0 -14
  436. package/dist/engines/pi/session-builder.d.ts +0 -13
  437. package/dist/engines/pi/session-builder.js +0 -117
  438. package/dist/engines/pi/session-control.d.ts +0 -77
  439. package/dist/engines/pi/session-control.js +0 -908
  440. package/dist/engines/pi/session-effects.d.ts +0 -14
  441. package/dist/engines/pi/session-effects.js +0 -30
  442. package/dist/engines/pi/session-inheritance.d.ts +0 -21
  443. package/dist/engines/pi/session-inheritance.js +0 -204
  444. package/dist/engines/pi/session-markers.d.ts +0 -49
  445. package/dist/engines/pi/session-markers.js +0 -60
  446. package/dist/engines/pi/session-settings.d.ts +0 -67
  447. package/dist/engines/pi/session-settings.js +0 -119
  448. package/dist/engines/pi/session-store.d.ts +0 -69
  449. package/dist/engines/pi/session-store.js +0 -430
  450. package/dist/engines/pi/tool-context.d.ts +0 -45
  451. package/dist/engines/pi/tool-context.js +0 -31
  452. package/dist/engines/pi/tool.d.ts +0 -99
  453. package/dist/engines/pi/tool.js +0 -118
  454. package/dist/engines/pi/turn-kit.d.ts +0 -83
  455. package/dist/engines/pi/turn-kit.js +0 -275
  456. package/dist/engines/pi/wake-tool.d.ts +0 -9
  457. package/dist/engines/pi/wake-tool.js +0 -92
  458. package/dist/loader.d.ts +0 -41
  459. package/dist/loader.js +0 -116
  460. package/dist/node.d.ts +0 -3
  461. package/dist/node.js +0 -4
  462. package/dist/observe.d.ts +0 -6
  463. package/dist/observe.js +0 -47
  464. package/dist/pi.d.ts +0 -25
  465. package/dist/pi.js +0 -15
  466. package/dist/scaffold/templates/APPEND_SYSTEM.md +0 -12
  467. package/dist/scaffold/templates/tools/fetch-url.ts +0 -25
  468. package/dist/schedule/discover.d.ts +0 -17
  469. package/dist/schedule/discover.js +0 -61
  470. package/dist/schedule/routine.d.ts +0 -43
  471. package/dist/schedule/routine.js +0 -32
  472. package/dist/schedule/run.d.ts +0 -55
  473. package/dist/schedule/run.js +0 -146
  474. package/dist/secrets-gate.d.ts +0 -32
  475. package/dist/secrets-gate.js +0 -33
  476. package/dist/session-remote.d.ts +0 -47
  477. package/dist/session-remote.js +0 -460
  478. package/dist/session.d.ts +0 -466
  479. package/dist/session.js +0 -57
  480. /package/dist/{channels/kit/state.d.ts → state-file.d.ts} +0 -0
@@ -0,0 +1,579 @@
1
+ /**
2
+ * The Agent API (SPEC.md) over pi-durable: a Harness opened on the instance's storage, and the operations read
3
+ * from and submitted to it. Admission (a key's lookup, the limits and the submission) is one step per process, since
4
+ * one process owns a storage.
5
+ */
6
+ import { BACKGROUND_CONTEXT, withAbortSignal } from "@earendil-works/chord/context";
7
+ import { AgentDoc, ConversationBusy, DEFAULT_RETRY_POLICY, Harness, InboxDoc, LiveDoc, SessionFailed, configure, } from "@earendil-works/pi-durable";
8
+ import * as Cause from "effect/Cause";
9
+ import * as Duration from "effect/Duration";
10
+ import * as Effect from "effect/Effect";
11
+ import * as Exit from "effect/Exit";
12
+ import * as Option from "effect/Option";
13
+ import * as Schema from "effect/Schema";
14
+ import * as Scope from "effect/Scope";
15
+ import * as Semaphore from "effect/Semaphore";
16
+ import * as Stream from "effect/Stream";
17
+ import { AgentError, CompactOptions, CreateSessionOptions, EntriesOptions, LIMITS, ListOptions, MessageKey, SendRequest, SessionChanges, Wait, agentError, hasEnded as messageHasEnded, } from "../../agent-api.js";
18
+ import { beginWork } from "../../channels/busy.js";
19
+ import { port, portAbort, portError } from "../../effect-port.js";
20
+ import { log } from "../../log.js";
21
+ import { admitSettings } from "./defaults.js";
22
+ import { USER_ENTRY, imageAt, isPublished, parseImageRef } from "./entries.js";
23
+ import { sessionEvents } from "./events.js";
24
+ import { storageIds } from "./ids.js";
25
+ import { isInput, queuedInputs, requestIdFor, usageOfState } from "./messages.js";
26
+ import { durableReads } from "./reads.js";
27
+ import { releaseWaitingQueues } from "./release.js";
28
+ import { trackSessions } from "./sessions.js";
29
+ import { messageStream } from "./stream.js";
30
+ const OWNERLESS = { ownership: { kind: "ownerless" } };
31
+ const COMMAND = /^\/([^\s]+)(?:\s+([\s\S]*))?$/;
32
+ export async function openDurableAgent(options) {
33
+ const context = BACKGROUND_CONTEXT;
34
+ const harness = await Harness.open(options.storage, {
35
+ models: options.models,
36
+ registry: options.registry,
37
+ ...(options.settings === undefined ? {} : { settings: options.settings }),
38
+ ...(options.env === undefined ? {} : { env: options.env }),
39
+ onReport: (error) => log.warn(`[fastagent] pi-durable reported: ${String(error)}`),
40
+ }, context);
41
+ const lifetime = Scope.makeUnsafe();
42
+ try {
43
+ const ids = await storageIds(harness, context);
44
+ const sessions = await Effect.runPromise(Scope.provide(trackSessions(harness, options.storage, context), lifetime));
45
+ // Before `resume()`, which runs an earlier process's work. An abort enables the scheduler too, but commits first, so
46
+ // the aborted work does not run (measured).
47
+ const unsettled = (await harness.inspect(context)).submissions.filter(isInput);
48
+ const unfinished = options.unfinished ?? "resume";
49
+ if (unsettled.length > 0 && unfinished !== "resume") {
50
+ const where = [...new Set(unsettled.map((record) => record.conversationId))]
51
+ .filter((id) => sessions.has(id))
52
+ .map((id) => ids.encode(id));
53
+ const said = `${unsettled.length} message(s) an earlier process accepted and stopped before they ended (sessions: ${where.join(", ")})`;
54
+ if (unfinished === "refuse") {
55
+ throw new Error(`the storage holds ${said}, which this process would not finish: serve the agent until they end, then try again`);
56
+ }
57
+ for (const conversationId of new Set(unsettled.map((record) => record.conversationId))) {
58
+ await (await harness.conversation(conversationId, context))?.abort(context);
59
+ }
60
+ log.warn(`[fastagent] aborted ${said}: none of it runs again`);
61
+ }
62
+ else {
63
+ // What an earlier process accepted and did not settle runs on in this one: it is this process's work now.
64
+ for (const record of unsettled) {
65
+ const submission = await harness.submission(record.id, context);
66
+ if (submission !== undefined)
67
+ countUntilSettled(submission);
68
+ }
69
+ }
70
+ harness.resume();
71
+ await Effect.runPromise(Scope.provide(releaseWaitingQueues(harness, context), lifetime));
72
+ return durableAgent(harness, ids, sessions, lifetime, options);
73
+ }
74
+ catch (error) {
75
+ await Effect.runPromise(Scope.close(lifetime, Exit.void));
76
+ await harness.close(context);
77
+ throw error;
78
+ }
79
+ }
80
+ /**
81
+ * A message not yet settled is the process's work in flight (busy.ts): what AgentCore's ping and `dev`'s restart wait
82
+ * for. Whatever ends the wait, settling or the harness closing, ends the count.
83
+ */
84
+ function countUntilSettled(submission) {
85
+ const done = beginWork();
86
+ submission.wait(BACKGROUND_CONTEXT).then(done, done);
87
+ }
88
+ /** A wait its Caller's signal let go of: the call rejects with the signal's reason, and the message goes on. */
89
+ class Released {
90
+ reason;
91
+ constructor(reason) {
92
+ this.reason = reason;
93
+ }
94
+ }
95
+ const released = (signal) => Effect.callback((resume) => {
96
+ const onAbort = () => resume(Effect.fail(new Released(signal.reason)));
97
+ if (signal.aborted)
98
+ return onAbort();
99
+ signal.addEventListener("abort", onAbort, { once: true });
100
+ return Effect.sync(() => signal.removeEventListener("abort", onAbort));
101
+ });
102
+ /** An input as `schema` reads it, or `invalid_request` saying where it is not. */
103
+ const decode = (schema, input, what) => Exit.mapError(Schema.decodeUnknownExit(schema)(input), (error) => agentError("invalid_request", `${what}: ${error.message.replace(/\s*\n\s*/g, " ")}`));
104
+ function durableAgent(harness, ids, sessions, lifetime, options) {
105
+ const context = BACKGROUND_CONTEXT;
106
+ const reads = durableReads(harness, ids, sessions, context);
107
+ const admission = Semaphore.makeUnsafe(1);
108
+ const commands = options.commands ?? (() => []);
109
+ const inFlightLimit = options.inFlightPerProcess ?? LIMITS.inFlightPerProcess;
110
+ let closing = false;
111
+ harness.subscribeClose(() => {
112
+ closing = true;
113
+ });
114
+ /** A failed call as a Caller's error: once the harness is closing (its storage failed, or it was closed), `unavailable`. */
115
+ const failureOf = (error) => {
116
+ const message = error instanceof Error ? error.message : String(error);
117
+ if (closing || error instanceof SessionFailed)
118
+ return agentError("unavailable", `The agent cannot answer now: ${message}`);
119
+ log.error(`[fastagent] the agent failed: ${error instanceof Error ? (error.stack ?? message) : message}`);
120
+ return agentError("internal", message);
121
+ };
122
+ /** An operation as the Promise the API promises: what it names, or a rejection with an AgentError (§4). */
123
+ const settle = (program) => Effect.runPromise(program.pipe(Effect.catchCause((cause) => {
124
+ const error = portError(cause);
125
+ if (error instanceof Released)
126
+ return Effect.fail(error.reason);
127
+ return Effect.fail(error instanceof AgentError ? error : failureOf(error));
128
+ })));
129
+ const conversationOf = (sessionId) => reads
130
+ .conversation(sessionId)
131
+ .pipe(Effect.flatMap((conversation) => conversation === undefined
132
+ ? Effect.fail(agentError("not_found", `No session ${sessionId}`))
133
+ : Effect.succeed(conversation)));
134
+ /** The agent's tools by name: the registry's, read for each use, so a tool the definition gains is offered at once. */
135
+ const toolsByName = () => {
136
+ const tools = new Map();
137
+ for (const { tool } of options.registry.snapshot().tools())
138
+ if (!tools.has(tool.name))
139
+ tools.set(tool.name, tool);
140
+ return tools;
141
+ };
142
+ /** What pi-durable stores for `changes`: tools by their registrations, which it stores by name. */
143
+ const changeOf = (changes) => Effect.suspend(() => {
144
+ const { model, thinkingLevel, instructions, tools } = changes;
145
+ let registrations;
146
+ if (tools !== undefined && tools !== null) {
147
+ const known = toolsByName();
148
+ const unknown = tools.filter((name) => !known.has(name));
149
+ if (unknown.length > 0) {
150
+ return Effect.fail(agentError("invalid_request", `This agent has no tool named ${unknown.join(", ")}`, { tools: unknown }));
151
+ }
152
+ registrations = tools.map((name) => known.get(name));
153
+ }
154
+ else
155
+ registrations = tools;
156
+ return Effect.succeed({
157
+ ...(model === undefined
158
+ ? {}
159
+ : { model: model === null ? null : { provider: model.provider, modelId: model.modelId } }),
160
+ ...(thinkingLevel === undefined ? {} : { thinkingLevel }),
161
+ ...(instructions === undefined ? {} : { instructions }),
162
+ ...(registrations === undefined ? {} : { tools: registrations }),
163
+ });
164
+ });
165
+ const info = () => settle(Effect.gen(function* () {
166
+ const available = yield* port(() => options.models.getAvailable());
167
+ const named = ({ name, description }) => ({
168
+ name,
169
+ ...(description ? { description } : {}),
170
+ });
171
+ return {
172
+ name: options.name,
173
+ ...(options.description === undefined ? {} : { description: options.description }),
174
+ commands: commands().map(named),
175
+ tools: [...toolsByName().values()].map(named),
176
+ models: available.map((model) => ({
177
+ provider: model.provider,
178
+ modelId: model.id,
179
+ ...(model.name ? { name: model.name } : {}),
180
+ })),
181
+ ...(options.model === undefined
182
+ ? {}
183
+ : { defaultModel: { provider: options.model.provider, modelId: options.model.modelId } }),
184
+ };
185
+ }));
186
+ const inFlight = port(() => harness.inspect(context)).pipe(Effect.map(({ submissions }) => submissions.filter((record) => record.type === "input").length));
187
+ const defaults = {
188
+ ...(options.model === undefined ? {} : { model: options.model }),
189
+ ...(options.thinkingLevel === undefined ? {} : { thinkingLevel: options.thinkingLevel }),
190
+ };
191
+ const has = {
192
+ model: (model) => options.models.getModel(model.provider, model.modelId) !== undefined,
193
+ tool: (name) => toolsByName().has(name),
194
+ };
195
+ /** A message no setting of its session could answer is refused, recording nothing, rather than accepted to fail. */
196
+ const unanswerable = (refused) => {
197
+ if (refused.missing === "tools") {
198
+ const names = refused.tools.join(", ");
199
+ return agentError("missing_tool", `This session enables ${names}, which this agent no longer has`, {
200
+ tools: refused.tools,
201
+ });
202
+ }
203
+ const { model } = refused;
204
+ return agentError("missing_model", model === undefined
205
+ ? "Neither this session nor the agent names a model"
206
+ : `This session answers with ${model.provider}/${model.modelId}, which is not among this agent's models`, model === undefined ? undefined : { model: `${model.provider}/${model.modelId}` });
207
+ };
208
+ const unconfirmed = (conversation, messageId, error) => agentError("unconfirmed", `${messageId === undefined ? "The message may have been recorded, and that could not be confirmed" : "The message was recorded, and reading it back failed"}: ${failureOf(error).message}`, {
209
+ sessionId: ids.encode(conversation.id),
210
+ ...(messageId === undefined ? {} : { messageId: ids.encode(messageId) }),
211
+ });
212
+ /** Admission: a key's lookup, the limits, the settings and the submission, one at a time per process. */
213
+ const admit = (request) => Semaphore.withPermits(admission, 1)(Effect.gen(function* () {
214
+ const requestId = requestIdFor(request.idempotencyKey);
215
+ // Before anything is written: reading or expanding a command reads the definition's files, and may throw.
216
+ const { content, sent } = expandCommand(request.content, commands());
217
+ const conversation = yield* conversationOf(request.sessionId);
218
+ if (request.idempotencyKey !== undefined) {
219
+ const seen = yield* reads.submissionByKey(conversation, requestId);
220
+ if (seen !== undefined)
221
+ return { message: yield* reads.message(seen), replayed: true };
222
+ }
223
+ if ((yield* inFlight) >= inFlightLimit) {
224
+ return yield* agentError("busy", `This agent already holds ${inFlightLimit} messages`, {
225
+ reason: "capacity",
226
+ });
227
+ }
228
+ if (request.whenBusy !== "reject") {
229
+ const inbox = yield* reads.inbox(conversation);
230
+ if (queuedInputs(inbox).length >= LIMITS.queuedPerSession) {
231
+ return yield* agentError("busy", `The session already has ${LIMITS.queuedPerSession} queued messages`, {
232
+ reason: "queue_full",
233
+ });
234
+ }
235
+ }
236
+ const refused = yield* port(() => conversation.commit((tx) => admitSettings(tx, conversation.id, defaults, has), context));
237
+ if (refused !== undefined)
238
+ return yield* unanswerable(refused);
239
+ const data = { requestId, ...(sent === undefined ? {} : { sent }) };
240
+ let messageId;
241
+ return yield* Effect.gen(function* () {
242
+ const submission = yield* port(() => conversation.submit({
243
+ type: "input",
244
+ content,
245
+ requestId,
246
+ whenBusy: request.whenBusy === "steer" ? "steer" : request.whenBusy === "reject" ? "reject" : "followUp",
247
+ entry: { kind: USER_ENTRY, data },
248
+ }, context));
249
+ messageId = submission.id;
250
+ // Counted once it is read back, whether or not that read succeeds: the wait must not be what reads the
251
+ // storage first, or a failing read would fail it instead of answering this call.
252
+ const record = yield* port(() => submission.status(context)).pipe(Effect.ensuring(Effect.sync(() => countUntilSettled(submission))));
253
+ if (!isInput(record))
254
+ throw new Error(`submission ${record.id} was admitted as a write`);
255
+ return { message: yield* reads.message(record), replayed: false };
256
+ }).pipe(
257
+ // Once submission starts, only pi-durable's explicit refusal proves nothing was recorded.
258
+ Effect.catchCause((cause) => {
259
+ const error = portError(cause);
260
+ return Effect.fail(messageId === undefined && error instanceof ConversationBusy
261
+ ? agentError("busy", "The session is answering another message", { reason: "running" })
262
+ : unconfirmed(conversation, messageId, error));
263
+ }));
264
+ }));
265
+ /** The message as it is now. */
266
+ const current = (message) => reads.submission(message.id).pipe(Effect.flatMap((record) => {
267
+ if (record === undefined)
268
+ throw new Error(`message ${message.id} is not in the storage it was accepted into`);
269
+ return reads.message(record);
270
+ }));
271
+ /** The message once it has ended, or after `seconds` as it is then; `signal` lets go of the wait. */
272
+ const awaitEnd = (message, seconds, signal) => {
273
+ if (messageHasEnded(message) || seconds === 0)
274
+ return Effect.succeed(message);
275
+ const ended = port(() => harness.submission(ids.decode(message.id), context)).pipe(Effect.flatMap((handle) => {
276
+ if (handle === undefined)
277
+ throw new Error(`message ${message.id} is not in the storage it was accepted into`);
278
+ const controller = new AbortController();
279
+ return portAbort("a message's wait", () => handle.wait(withAbortSignal(controller.signal, context)), () => controller.abort());
280
+ }), Effect.flatMap((record) => reads.message(record)));
281
+ const timed = ended.pipe(Effect.timeoutOption(Duration.seconds(seconds)), Effect.flatMap(Option.match({ onNone: () => current(message), onSome: Effect.succeed })));
282
+ return signal === undefined ? timed : Effect.raceFirst(timed, released(signal));
283
+ };
284
+ const send = (request, sendOptions) => settle(Effect.gen(function* () {
285
+ const decoded = yield* decode(SendRequest, request, "messages.send");
286
+ const seconds = sendOptions?.wait === undefined ? undefined : yield* decode(Wait, sendOptions.wait, "wait");
287
+ const signal = sendOptions?.signal;
288
+ // A send whose Caller let go before it was made is not made, as `fetch` does not send one.
289
+ if (signal?.aborted)
290
+ return yield* Effect.fail(new Released(signal.reason));
291
+ const { message } = yield* admit(decoded);
292
+ if (seconds === undefined)
293
+ return message;
294
+ return yield* awaitEnd(message, seconds, signal).pipe(Effect.catchCause((cause) => {
295
+ const error = portError(cause);
296
+ if (error instanceof Released)
297
+ return Effect.fail(error);
298
+ return Effect.fail(agentError("unconfirmed", `The message was recorded, and waiting for it failed: ${failureOf(error).message}`, {
299
+ sessionId: message.sessionId,
300
+ messageId: message.id,
301
+ }));
302
+ }));
303
+ }));
304
+ const get = (key, getOptions) => settle(Effect.gen(function* () {
305
+ const decoded = yield* decode(MessageKey, key, "messages.get");
306
+ const seconds = getOptions?.wait === undefined ? undefined : yield* decode(Wait, getOptions.wait, "wait");
307
+ const signal = getOptions?.signal;
308
+ if (signal?.aborted)
309
+ return yield* Effect.fail(new Released(signal.reason));
310
+ const conversation = yield* conversationOf(decoded.sessionId);
311
+ const record = "messageId" in decoded
312
+ ? yield* reads.submission(decoded.messageId)
313
+ : yield* reads.submissionByKey(conversation, requestIdFor(decoded.idempotencyKey));
314
+ if (record === undefined || record.conversationId !== conversation.id) {
315
+ const which = "messageId" in decoded ? decoded.messageId : `with key ${decoded.idempotencyKey}`;
316
+ return yield* agentError("not_found", `No message ${which} in session ${decoded.sessionId}`);
317
+ }
318
+ const message = yield* reads.message(record);
319
+ return seconds === undefined ? message : yield* awaitEnd(message, seconds, signal);
320
+ }));
321
+ const create = (createOptions) => settle(Effect.gen(function* () {
322
+ const { forkFrom, ...settings } = yield* decode(CreateSessionOptions, createOptions ?? {}, "sessions.create");
323
+ const creation = { ...OWNERLESS, agent: yield* changeOf(settings) };
324
+ if (forkFrom === undefined) {
325
+ return yield* reads.session(yield* port(() => harness.createConversation(creation, context)));
326
+ }
327
+ const source = yield* conversationOf(forkFrom.sessionId);
328
+ const at = forkFrom.entryId === undefined
329
+ ? yield* reads
330
+ .history(source, { order: "descending" })
331
+ .pipe(Stream.runHead, Effect.map(Option.getOrUndefined))
332
+ : yield* reads.visibleEntry(source, forkFrom.entryId);
333
+ if (at === undefined && forkFrom.entryId !== undefined) {
334
+ return yield* agentError("not_found", `No entry ${forkFrom.entryId} in session ${forkFrom.sessionId}`);
335
+ }
336
+ const fork = at === undefined
337
+ ? yield* port(async () => {
338
+ // pi-durable forks at an entry; an empty source has only settings to inherit.
339
+ const inherited = await harness.snapshot(AgentDoc, source.id, context);
340
+ return harness.createConversation({
341
+ ...creation,
342
+ init: async (tx, id) => {
343
+ Object.assign(await tx.doc(AgentDoc, id), inherited);
344
+ await configure(tx, id, creation.agent);
345
+ },
346
+ }, context);
347
+ })
348
+ : yield* port(() => source.fork(at.id, creation, context));
349
+ return yield* reads.session(fork);
350
+ }));
351
+ const update = (sessionId, changes) => settle(Effect.gen(function* () {
352
+ const decoded = yield* decode(SessionChanges, changes, "sessions.update");
353
+ if (Object.values(decoded).every((value) => value === undefined)) {
354
+ return yield* agentError("invalid_request", "Nothing to update: give a model, a thinking level, instructions or tools");
355
+ }
356
+ const conversation = yield* conversationOf(sessionId);
357
+ const change = yield* changeOf(decoded);
358
+ yield* port(() => conversation.configure(change, context));
359
+ return yield* reads.session(conversation);
360
+ }));
361
+ const list = (listOptions) => settle(Effect.gen(function* () {
362
+ const { after, limit = LIMITS.pageDefault } = yield* decode(ListOptions, listOptions ?? {}, "sessions.list");
363
+ const position = after === undefined ? undefined : ids.decodeCursor(after);
364
+ if (after !== undefined && position === undefined) {
365
+ return yield* agentError("invalid_request", `${JSON.stringify(after)} is not a cursor of this agent's`);
366
+ }
367
+ const ordered = sessions.byActivity();
368
+ const rest = position === undefined ? ordered : ordered.filter(({ activity }) => activity < position);
369
+ const page = rest.slice(0, limit);
370
+ const last = page.at(-1);
371
+ const data = yield* reads.sessionsOf(page.map(({ conversationId }) => conversationId));
372
+ return {
373
+ data,
374
+ ...(rest.length > limit && last !== undefined ? { next: ids.encodeCursor(last.activity) } : {}),
375
+ };
376
+ }));
377
+ const entries = (sessionId, entriesOptions) => settle(Effect.gen(function* () {
378
+ const decoded = yield* decode(EntriesOptions, entriesOptions ?? {}, "sessions.entries");
379
+ if (decoded.after !== undefined && decoded.before !== undefined) {
380
+ return yield* agentError("invalid_request", "A page is after an entry or before one, not both");
381
+ }
382
+ return yield* readPage(reads, yield* conversationOf(sessionId), sessionId, decoded);
383
+ }));
384
+ /** One entry the session published, by its id: where `messages.stream` starts a message that is already running. */
385
+ const entry = (sessionId, entryId) => settle(Effect.gen(function* () {
386
+ const record = yield* reads.visibleEntry(yield* conversationOf(sessionId), entryId);
387
+ const [published] = record === undefined || !isPublished(record) ? [] : yield* reads.publish([record]);
388
+ if (published === undefined)
389
+ return yield* agentError("not_found", `No entry ${entryId} in session ${sessionId}`);
390
+ return published;
391
+ }));
392
+ const events = (sessionId, eventsOptions) => {
393
+ const stream = sessionEvents({
394
+ harness,
395
+ reads,
396
+ ids,
397
+ context,
398
+ failureOf,
399
+ maxRetries: options.settings?.retry?.maxRetries ?? DEFAULT_RETRY_POLICY.maxRetries,
400
+ }, sessionId, eventsOptions?.after);
401
+ const signal = eventsOptions?.signal;
402
+ return Stream.toAsyncIterable(signal === undefined
403
+ ? stream
404
+ : stream.pipe(Stream.interruptWhen(released(signal).pipe(Effect.mapError((error) => error.reason)))));
405
+ };
406
+ /**
407
+ * Stops everything the session is doing, and answers once that stop is recorded rather than once it has ended
408
+ * (§4). pi-durable runs commits one at a time in the order they are called, and `abort()` calls its commit at once:
409
+ * so the read before it sees what it stops, and the check after it runs once the stop is in the storage.
410
+ */
411
+ const cancel = (sessionId) => settle(Effect.gen(function* () {
412
+ const conversation = yield* conversationOf(sessionId);
413
+ let abortFailed;
414
+ const recorded = yield* port(() => {
415
+ const before = harness.commit(async (tx) => {
416
+ // Copied out: a document read in a transaction cannot be read once it has settled.
417
+ const run = (await tx.doc(LiveDoc, conversation.id))?.run;
418
+ return {
419
+ run: run === undefined ? undefined : { taskId: run.taskId, inputs: [...run.inputs] },
420
+ queued: queuedInputs(await tx.doc(InboxDoc, conversation.id)),
421
+ };
422
+ }, context);
423
+ conversation.abort(context).catch((error) => {
424
+ abortFailed = error;
425
+ if (!closing)
426
+ log.warn(`[fastagent] stopping session ${sessionId} failed: ${String(error)}`);
427
+ });
428
+ const check = harness.commit(async (tx) => {
429
+ const { run, queued } = await before;
430
+ const task = run === undefined ? undefined : await tx.task(run.taskId);
431
+ const stillQueued = new Set(queuedInputs(await tx.doc(InboxDoc, conversation.id)));
432
+ return {
433
+ marked: (task === undefined || task.abortRequested || task.state.status === "terminal") &&
434
+ queued.every((id) => !stillQueued.has(id)),
435
+ stopped: [...(run?.inputs ?? []), ...queued],
436
+ };
437
+ }, context);
438
+ // Both, so a harness that refuses every commit (closing, or its storage failed) fails this cancel once,
439
+ // leaving neither refusal unheard: the check's callback, which awaits the read, never runs then.
440
+ return Promise.all([before, check]).then(([, checked]) => checked);
441
+ });
442
+ if (!recorded.marked) {
443
+ throw new Error(`the stop of session ${sessionId} was not recorded: ${String(abortFailed ?? "no reason given")}`);
444
+ }
445
+ return { stopped: recorded.stopped.map((id) => ids.encode(id)) };
446
+ }));
447
+ const compact = (sessionId, compactOptions) => settle(Effect.gen(function* () {
448
+ const { instructions } = yield* decode(CompactOptions, compactOptions ?? {}, "sessions.compact");
449
+ const conversation = yield* conversationOf(sessionId);
450
+ yield* port(() => conversation.compact(instructions, context));
451
+ }));
452
+ const attachment = (sessionId, ref) => settle(Effect.gen(function* () {
453
+ const missing = agentError("not_found", `No attachment ${ref} in session ${sessionId}`);
454
+ const parsed = parseImageRef(ref);
455
+ if (parsed === undefined)
456
+ return yield* missing;
457
+ const record = yield* reads.visibleEntry(yield* conversationOf(sessionId), parsed.entryId);
458
+ const image = record === undefined ? undefined : imageAt(record, parsed.index);
459
+ if (image === undefined)
460
+ return yield* missing;
461
+ return { mimeType: image.mimeType, data: new Uint8Array(Buffer.from(image.data, "base64")) };
462
+ }));
463
+ const stream = (request, streamOptions) => {
464
+ const decoded = decode(SendRequest, request, "messages.stream");
465
+ if (Exit.isFailure(decoded))
466
+ return failing(Cause.squash(decoded.cause));
467
+ return messageStream({
468
+ events: (sessionId, after) => events(sessionId, after === undefined ? {} : { after }),
469
+ admit: (sent) => settle(admit(sent)),
470
+ get: (sessionId, messageId) => get({ sessionId, messageId }),
471
+ entry,
472
+ entries,
473
+ }, decoded.value, streamOptions?.signal);
474
+ };
475
+ return {
476
+ harness,
477
+ info,
478
+ usage: () => settle(port(() => harness.usage(context)).pipe(Effect.map((spent) => usageOfState(spent)))),
479
+ sessions: {
480
+ create,
481
+ get: (sessionId) => settle(conversationOf(sessionId).pipe(Effect.flatMap(reads.session))),
482
+ list,
483
+ update,
484
+ entries,
485
+ events,
486
+ cancel,
487
+ compact,
488
+ attachment,
489
+ },
490
+ messages: { send, stream, get },
491
+ conversation: (sessionId) => Effect.runPromise(reads.conversation(sessionId)),
492
+ close: async () => {
493
+ closing = true;
494
+ await Effect.runPromise(Scope.close(lifetime, Exit.void));
495
+ await harness.close(context);
496
+ },
497
+ };
498
+ }
499
+ /** An iterable whose first read rejects with `error`: an operation refused before it began. */
500
+ function failing(error) {
501
+ return {
502
+ [Symbol.asyncIterator]: () => ({
503
+ next: () => Promise.reject(error),
504
+ return: async () => ({ done: true, value: undefined }),
505
+ }),
506
+ };
507
+ }
508
+ /** The content the model sees, and the first text as sent when it was a command the agent expanded. */
509
+ function expandCommand(content, commands) {
510
+ const parts = (typeof content === "string" ? [{ type: "text", text: content }] : content).map((part) => part.type === "text"
511
+ ? { type: "text", text: part.text }
512
+ : { type: "image", mimeType: part.mimeType, data: part.data });
513
+ const first = parts[0];
514
+ const match = first?.type === "text" ? COMMAND.exec(first.text) : null;
515
+ const command = match === null ? undefined : commands.find(({ name }) => name === match[1]);
516
+ if (first?.type !== "text" || match === null || command === undefined)
517
+ return { content: parts };
518
+ return { content: [{ type: "text", text: command.expand(match[2] ?? "") }, ...parts.slice(1)], sent: first.text };
519
+ }
520
+ /** One page of a session's published entries (§4): after a cursor, before one, or the newest. */
521
+ function readPage(reads, conversation, sessionId, { after, before, limit = LIMITS.pageDefault }) {
522
+ return Effect.gen(function* () {
523
+ const cursor = after ?? before;
524
+ const at = cursor === undefined ? undefined : yield* reads.visibleEntry(conversation, cursor);
525
+ if (cursor !== undefined && at === undefined) {
526
+ return yield* agentError("not_found", `No entry ${cursor} in session ${sessionId}`);
527
+ }
528
+ const bound = (id) => id;
529
+ const take = (query) => reads.published(conversation, query).pipe(Stream.take(limit + 1), Stream.runCollect);
530
+ if (after !== undefined && at !== undefined) {
531
+ const records = yield* take({ order: "ascending", minEntryId: bound(at.id + 1) });
532
+ const page = records.slice(0, limit);
533
+ const first = page[0];
534
+ if (first === undefined)
535
+ return { data: [] };
536
+ const earlier = yield* reads.newestPublished(conversation, first.id - 1);
537
+ const data = withinBudget(yield* reads.publish(page), "oldest");
538
+ return {
539
+ data,
540
+ ...(earlier === undefined ? {} : { prev: data[0].id }),
541
+ ...(records.length > limit || data.length < page.length ? { next: data.at(-1).id } : {}),
542
+ };
543
+ }
544
+ const records = yield* take({
545
+ order: "descending",
546
+ ...(at === undefined ? {} : { maxEntryId: bound(at.id - 1) }),
547
+ });
548
+ const page = records.slice(0, limit).reverse();
549
+ if (page.length === 0)
550
+ return { data: [] };
551
+ const older = records[limit];
552
+ const published = yield* reads.publish(page);
553
+ const data = withinBudget(published, "newest");
554
+ const newer = at === undefined
555
+ ? undefined
556
+ : yield* reads
557
+ .published(conversation, { order: "ascending", minEntryId: bound(at.id) })
558
+ .pipe(Stream.take(1), Stream.runHead);
559
+ return {
560
+ data,
561
+ ...(older !== undefined || data.length < published.length ? { prev: data[0].id } : {}),
562
+ ...(newer !== undefined && newer._tag === "Some" ? { next: data.at(-1).id } : {}),
563
+ };
564
+ });
565
+ }
566
+ /** A page stops at the byte budget (§7), keeping at least one entry: the end nearest its cursor. */
567
+ function withinBudget(entries, keep) {
568
+ const ordered = keep === "oldest" ? entries : [...entries].reverse();
569
+ const kept = [];
570
+ let bytes = 0;
571
+ for (const entry of ordered) {
572
+ const size = Buffer.byteLength(JSON.stringify(entry));
573
+ if (kept.length > 0 && bytes + size > LIMITS.pageBytes)
574
+ break;
575
+ kept.push(entry);
576
+ bytes += size;
577
+ }
578
+ return keep === "oldest" ? kept : kept.reverse();
579
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * The deployment's default model and thinking level, for a conversation whose own settings name none. pi-durable reads
3
+ * them only from the conversation's `pi.agent`, so the default is written there when a message is admitted; from then
4
+ * on they are the conversation's own, as one set through pi-durable's API is (architecture.md §6.1).
5
+ */
6
+ import type { ModelThinkingLevel } from "@earendil-works/pi-ai";
7
+ import { type ConversationId, type ModelRef, type Tx } from "@earendil-works/pi-durable";
8
+ export interface Defaults {
9
+ model?: ModelRef;
10
+ thinkingLevel?: ModelThinkingLevel;
11
+ }
12
+ /** Why a session's settings cannot answer a message: a model the agent lacks, or tools it no longer has. */
13
+ export type Unanswerable = {
14
+ missing: "model";
15
+ model: ModelRef | undefined;
16
+ } | {
17
+ missing: "tools";
18
+ tools: string[];
19
+ };
20
+ /**
21
+ * Take the defaults the conversation's settings lack, unless the settings cannot answer a message: the model it would
22
+ * answer with is not among the agent's models, or a tool it enables is not among the agent's tools. Then nothing is
23
+ * written, and what is missing is returned so the message can be refused.
24
+ */
25
+ export declare function admitSettings(tx: Tx, conversationId: ConversationId, defaults: Defaults, has: {
26
+ model(model: ModelRef): boolean;
27
+ tool(name: string): boolean;
28
+ }): Promise<Unanswerable | undefined>;
@@ -0,0 +1,26 @@
1
+ import { AgentDoc, configure } from "@earendil-works/pi-durable";
2
+ /**
3
+ * Take the defaults the conversation's settings lack, unless the settings cannot answer a message: the model it would
4
+ * answer with is not among the agent's models, or a tool it enables is not among the agent's tools. Then nothing is
5
+ * written, and what is missing is returned so the message can be refused.
6
+ */
7
+ export async function admitSettings(tx, conversationId, defaults, has) {
8
+ const agent = await tx.doc(AgentDoc, conversationId);
9
+ const model = agent.model ?? defaults.model;
10
+ if (model === undefined || !has.model(model)) {
11
+ return {
12
+ missing: "model",
13
+ model: model === undefined ? undefined : { provider: model.provider, modelId: model.modelId },
14
+ };
15
+ }
16
+ const missing = Array.isArray(agent.tools) ? agent.tools.filter((name) => !has.tool(name)) : [];
17
+ if (missing.length > 0)
18
+ return { missing: "tools", tools: missing };
19
+ await configure(tx, conversationId, {
20
+ ...(agent.model === undefined ? { model } : {}),
21
+ ...(agent.thinkingLevel === undefined && defaults.thinkingLevel !== undefined
22
+ ? { thinkingLevel: defaults.thinkingLevel }
23
+ : {}),
24
+ });
25
+ return undefined;
26
+ }