@fastagent-sh/fastagent 0.21.1 → 0.22.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 (388) hide show
  1. package/README.md +9 -12
  2. package/dist/agent.d.ts +24 -37
  3. package/dist/agent.js +7 -15
  4. package/dist/atomic-write.d.ts +13 -17
  5. package/dist/atomic-write.js +18 -29
  6. package/dist/bind.d.ts +12 -22
  7. package/dist/bind.js +18 -27
  8. package/dist/channel.d.ts +11 -33
  9. package/dist/channels/agentcore-limits.d.ts +2 -13
  10. package/dist/channels/agentcore-limits.js +2 -13
  11. package/dist/channels/agentcore-protocol.d.ts +67 -58
  12. package/dist/channels/agentcore-protocol.js +23 -15
  13. package/dist/channels/agentcore-service.d.ts +19 -33
  14. package/dist/channels/agentcore-service.js +176 -59
  15. package/dist/channels/agentcore.d.ts +39 -33
  16. package/dist/channels/agentcore.js +90 -224
  17. package/dist/channels/body.d.ts +15 -2
  18. package/dist/channels/body.js +24 -2
  19. package/dist/channels/busy.d.ts +1 -21
  20. package/dist/channels/busy.js +2 -39
  21. package/dist/channels/control.d.ts +17 -62
  22. package/dist/channels/control.js +74 -182
  23. package/dist/channels/define-channel.d.ts +39 -0
  24. package/dist/channels/define-channel.js +9 -0
  25. package/dist/channels/discover.d.ts +10 -31
  26. package/dist/channels/discover.js +37 -31
  27. package/dist/channels/feishu/bootstrap-token.d.ts +1 -8
  28. package/dist/channels/feishu/bootstrap-token.js +7 -23
  29. package/dist/channels/feishu/card.d.ts +14 -28
  30. package/dist/channels/feishu/card.js +16 -30
  31. package/dist/channels/feishu/cloud.d.ts +1 -5
  32. package/dist/channels/feishu/context-buffer.d.ts +4 -23
  33. package/dist/channels/feishu/context-buffer.js +8 -31
  34. package/dist/channels/feishu/crypto.d.ts +10 -5
  35. package/dist/channels/feishu/crypto.js +12 -19
  36. package/dist/channels/feishu/feishu-api.d.ts +2 -1
  37. package/dist/channels/feishu/feishu-api.js +5 -4
  38. package/dist/channels/feishu/feishu.d.ts +4 -10
  39. package/dist/channels/feishu/feishu.js +113 -227
  40. package/dist/channels/feishu/invoke-turn.d.ts +5 -4
  41. package/dist/channels/feishu/invoke-turn.js +36 -54
  42. package/dist/channels/feishu/model.d.ts +4 -21
  43. package/dist/channels/feishu/model.js +1 -8
  44. package/dist/channels/feishu/normalize.d.ts +2 -10
  45. package/dist/channels/feishu/normalize.js +7 -42
  46. package/dist/channels/feishu/parse.d.ts +9 -29
  47. package/dist/channels/feishu/parse.js +8 -26
  48. package/dist/channels/feishu/preview.d.ts +12 -14
  49. package/dist/channels/feishu/preview.js +108 -207
  50. package/dist/channels/feishu/register-app.d.ts +8 -23
  51. package/dist/channels/feishu/register-app.js +7 -20
  52. package/dist/channels/feishu/register-webhook.d.ts +2 -9
  53. package/dist/channels/feishu/register-webhook.js +9 -33
  54. package/dist/channels/feishu/scaffold/channel.ts +27 -19
  55. package/dist/channels/feishu/scaffold/channel.websocket.ts +31 -0
  56. package/dist/channels/feishu/setup-mode.d.ts +11 -36
  57. package/dist/channels/feishu/setup-mode.js +7 -26
  58. package/dist/channels/feishu/ws-ingress.d.ts +1 -2
  59. package/dist/channels/feishu/ws-ingress.js +6 -8
  60. package/dist/channels/http.d.ts +15 -15
  61. package/dist/channels/http.js +19 -9
  62. package/dist/channels/kit/attachment-path.d.ts +4 -5
  63. package/dist/channels/kit/attachment-path.js +10 -34
  64. package/dist/channels/kit/context-buffer.d.ts +14 -21
  65. package/dist/channels/kit/context-buffer.js +25 -38
  66. package/dist/channels/kit/delivery.d.ts +35 -0
  67. package/dist/channels/kit/delivery.js +129 -0
  68. package/dist/channels/kit/event-stream.d.ts +5 -0
  69. package/dist/channels/kit/event-stream.js +29 -0
  70. package/dist/channels/kit/invoke-turn-kit.d.ts +53 -26
  71. package/dist/channels/kit/invoke-turn-kit.js +89 -32
  72. package/dist/channels/kit/preview-kit.d.ts +18 -72
  73. package/dist/channels/kit/preview-kit.js +22 -88
  74. package/dist/channels/kit/signature.d.ts +3 -9
  75. package/dist/channels/kit/signature.js +3 -9
  76. package/dist/channels/kit/state.d.ts +1 -2
  77. package/dist/channels/kit/state.js +5 -14
  78. package/dist/channels/kit/stop-command.d.ts +1 -2
  79. package/dist/channels/kit/stop-command.js +6 -9
  80. package/dist/channels/kit/tasks.d.ts +1 -1
  81. package/dist/channels/kit/tasks.js +8 -21
  82. package/dist/channels/kit/text.d.ts +2 -16
  83. package/dist/channels/kit/text.js +23 -31
  84. package/dist/channels/kit/thread-participants.d.ts +4 -17
  85. package/dist/channels/kit/thread-participants.js +8 -55
  86. package/dist/channels/kit/transport.d.ts +26 -0
  87. package/dist/channels/kit/transport.js +20 -0
  88. package/dist/channels/kit/turn-queue.d.ts +7 -11
  89. package/dist/channels/kit/turn-queue.js +21 -39
  90. package/dist/channels/kit/turn-runner.d.ts +32 -18
  91. package/dist/channels/kit/turn-runner.js +98 -53
  92. package/dist/channels/kit/turn-store.d.ts +32 -32
  93. package/dist/channels/kit/turn-store.js +45 -88
  94. package/dist/channels/lark/lark.d.ts +1 -5
  95. package/dist/channels/lark/onboard.d.ts +3 -12
  96. package/dist/channels/lark/onboard.js +3 -13
  97. package/dist/channels/lark/scaffold/channel.ts +27 -19
  98. package/dist/channels/lark/scaffold/channel.websocket.ts +31 -0
  99. package/dist/channels/registration.d.ts +9 -35
  100. package/dist/channels/registration.js +6 -37
  101. package/dist/channels/secret.js +2 -5
  102. package/dist/channels/serve.d.ts +47 -44
  103. package/dist/channels/serve.js +165 -101
  104. package/dist/channels/slack/config-api.d.ts +4 -27
  105. package/dist/channels/slack/config-api.js +5 -34
  106. package/dist/channels/slack/context-buffer.d.ts +2 -3
  107. package/dist/channels/slack/context-buffer.js +5 -5
  108. package/dist/channels/slack/invoke-turn.d.ts +3 -1
  109. package/dist/channels/slack/invoke-turn.js +25 -34
  110. package/dist/channels/slack/manifest.js +1 -3
  111. package/dist/channels/slack/model.d.ts +1 -2
  112. package/dist/channels/slack/model.js +1 -1
  113. package/dist/channels/slack/onboard.d.ts +4 -3
  114. package/dist/channels/slack/onboard.js +8 -11
  115. package/dist/channels/slack/onboarding-state.d.ts +3 -5
  116. package/dist/channels/slack/onboarding-state.js +9 -6
  117. package/dist/channels/slack/parse.d.ts +4 -13
  118. package/dist/channels/slack/parse.js +7 -28
  119. package/dist/channels/slack/preview.d.ts +12 -2
  120. package/dist/channels/slack/preview.js +292 -269
  121. package/dist/channels/slack/reaction.d.ts +11 -3
  122. package/dist/channels/slack/reaction.js +19 -8
  123. package/dist/channels/slack/register-webhook.js +4 -7
  124. package/dist/channels/slack/scaffold/channel.ts +20 -13
  125. package/dist/channels/slack/scaffold/slack-send.ts +2 -2
  126. package/dist/channels/slack/setup-server.d.ts +1 -5
  127. package/dist/channels/slack/setup-server.js +1 -5
  128. package/dist/channels/slack/shared-api.d.ts +1 -4
  129. package/dist/channels/slack/shared-api.js +2 -13
  130. package/dist/channels/slack/slack-api.d.ts +2 -1
  131. package/dist/channels/slack/slack-api.js +6 -4
  132. package/dist/channels/slack/slack.d.ts +11 -14
  133. package/dist/channels/slack/slack.js +121 -122
  134. package/dist/channels/sse.d.ts +1 -1
  135. package/dist/channels/sse.js +1 -1
  136. package/dist/channels/telegram/context-buffer.d.ts +7 -22
  137. package/dist/channels/telegram/context-buffer.js +6 -23
  138. package/dist/channels/telegram/invoke-turn.d.ts +7 -7
  139. package/dist/channels/telegram/invoke-turn.js +24 -43
  140. package/dist/channels/telegram/parse.d.ts +24 -51
  141. package/dist/channels/telegram/parse.js +32 -84
  142. package/dist/channels/telegram/preview.d.ts +10 -1
  143. package/dist/channels/telegram/preview.js +79 -100
  144. package/dist/channels/telegram/register-webhook.js +5 -23
  145. package/dist/channels/telegram/scaffold/channel.ts +25 -18
  146. package/dist/channels/telegram/scaffold/telegram-send.ts +6 -4
  147. package/dist/channels/telegram/telegram-api.d.ts +4 -3
  148. package/dist/channels/telegram/telegram-api.js +9 -7
  149. package/dist/channels/telegram/telegram.d.ts +4 -18
  150. package/dist/channels/telegram/telegram.js +38 -94
  151. package/dist/channels/telegram/turn-store.d.ts +11 -14
  152. package/dist/channels/telegram/turn-store.js +8 -6
  153. package/dist/channels/wait-health.d.ts +4 -6
  154. package/dist/channels/wait-health.js +10 -14
  155. package/dist/cli/add-feishu.d.ts +2 -12
  156. package/dist/cli/add-feishu.js +24 -71
  157. package/dist/cli/add-slack.d.ts +5 -3
  158. package/dist/cli/add-slack.js +53 -76
  159. package/dist/cli/auth-view.d.ts +2 -16
  160. package/dist/cli/auth-view.js +2 -16
  161. package/dist/cli/commands/add.js +25 -40
  162. package/dist/cli/commands/chat.d.ts +0 -1
  163. package/dist/cli/commands/chat.js +5 -6
  164. package/dist/cli/commands/deploy/agentcore.js +60 -65
  165. package/dist/cli/commands/deploy/docker.js +50 -23
  166. package/dist/cli/commands/deploy/fly.js +23 -61
  167. package/dist/cli/commands/deploy/railway.js +23 -18
  168. package/dist/cli/commands/deploy/shared.d.ts +38 -57
  169. package/dist/cli/commands/deploy/shared.js +40 -59
  170. package/dist/cli/commands/deploy.d.ts +10 -27
  171. package/dist/cli/commands/deploy.js +38 -60
  172. package/dist/cli/commands/destroy.d.ts +4 -0
  173. package/dist/cli/commands/destroy.js +56 -0
  174. package/dist/cli/commands/dev.d.ts +2 -1
  175. package/dist/cli/commands/dev.js +20 -23
  176. package/dist/cli/commands/info.d.ts +0 -2
  177. package/dist/cli/commands/info.js +83 -49
  178. package/dist/cli/commands/init.d.ts +1 -2
  179. package/dist/cli/commands/init.js +16 -55
  180. package/dist/cli/commands/invoke.d.ts +0 -1
  181. package/dist/cli/commands/invoke.js +5 -7
  182. package/dist/cli/commands/login.d.ts +2 -1
  183. package/dist/cli/commands/login.js +33 -38
  184. package/dist/cli/commands/logs.js +3 -4
  185. package/dist/cli/commands/models.js +1 -5
  186. package/dist/cli/commands/routine-run.d.ts +6 -0
  187. package/dist/cli/commands/routine-run.js +47 -0
  188. package/dist/cli/commands/routine.d.ts +25 -0
  189. package/dist/cli/commands/routine.js +122 -0
  190. package/dist/cli/commands/start.d.ts +31 -3
  191. package/dist/cli/commands/start.js +131 -81
  192. package/dist/cli/commands/tool.js +26 -12
  193. package/dist/cli/fail.d.ts +13 -23
  194. package/dist/cli/fail.js +20 -23
  195. package/dist/cli/invoke-stream.d.ts +1 -8
  196. package/dist/cli/kernel.d.ts +6 -38
  197. package/dist/cli/kernel.js +14 -77
  198. package/dist/cli/models-view.d.ts +1 -9
  199. package/dist/cli/models-view.js +11 -22
  200. package/dist/cli/program.d.ts +5 -6
  201. package/dist/cli/program.js +120 -157
  202. package/dist/cli/serve.d.ts +57 -41
  203. package/dist/cli/serve.js +94 -121
  204. package/dist/cli/shared.d.ts +15 -61
  205. package/dist/cli/shared.js +55 -181
  206. package/dist/cli.js +1 -8
  207. package/dist/collect.d.ts +6 -0
  208. package/dist/collect.js +6 -0
  209. package/dist/core.d.ts +4 -1
  210. package/dist/core.js +9 -7
  211. package/dist/declared-secrets.d.ts +55 -0
  212. package/dist/declared-secrets.js +88 -0
  213. package/dist/deploy/agentcore/aws-cli.d.ts +91 -0
  214. package/dist/deploy/agentcore/aws-cli.js +64 -0
  215. package/dist/deploy/agentcore/destroy.d.ts +51 -0
  216. package/dist/deploy/agentcore/destroy.js +273 -0
  217. package/dist/deploy/agentcore/forwarder.js +28 -79
  218. package/dist/deploy/agentcore/logs.d.ts +2 -18
  219. package/dist/deploy/agentcore/logs.js +27 -51
  220. package/dist/deploy/agentcore/plan.d.ts +85 -105
  221. package/dist/deploy/agentcore/plan.js +152 -314
  222. package/dist/deploy/agentcore/run.d.ts +28 -28
  223. package/dist/deploy/agentcore/run.js +227 -306
  224. package/dist/deploy/agentcore/zip.d.ts +2 -15
  225. package/dist/deploy/agentcore/zip.js +3 -17
  226. package/dist/deploy/build-context.d.ts +21 -0
  227. package/dist/deploy/build-context.js +137 -0
  228. package/dist/deploy/channel-ingress.d.ts +26 -39
  229. package/dist/deploy/channel-ingress.js +57 -28
  230. package/dist/deploy/container.d.ts +20 -34
  231. package/dist/deploy/container.js +49 -100
  232. package/dist/deploy/docker/plan.d.ts +12 -11
  233. package/dist/deploy/docker/plan.js +43 -24
  234. package/dist/deploy/docker/run.d.ts +22 -25
  235. package/dist/deploy/docker/run.js +39 -37
  236. package/dist/deploy/fly/plan.d.ts +15 -44
  237. package/dist/deploy/fly/plan.js +41 -73
  238. package/dist/deploy/fly/run.d.ts +16 -51
  239. package/dist/deploy/fly/run.js +42 -95
  240. package/dist/deploy/hosts.d.ts +24 -3
  241. package/dist/deploy/hosts.js +24 -3
  242. package/dist/deploy/preflight.d.ts +47 -41
  243. package/dist/deploy/preflight.js +282 -383
  244. package/dist/deploy/railway/plan.d.ts +16 -42
  245. package/dist/deploy/railway/plan.js +42 -60
  246. package/dist/deploy/railway/run.d.ts +30 -68
  247. package/dist/deploy/railway/run.js +86 -100
  248. package/dist/deploy/registration-gate.d.ts +6 -13
  249. package/dist/deploy/residency.d.ts +37 -0
  250. package/dist/deploy/residency.js +24 -0
  251. package/dist/deploy/runner.d.ts +10 -9
  252. package/dist/deploy/runner.js +7 -18
  253. package/dist/deploy/secrets.d.ts +40 -41
  254. package/dist/deploy/secrets.js +117 -103
  255. package/dist/deploy/workspace.d.ts +50 -0
  256. package/dist/deploy/workspace.js +197 -0
  257. package/dist/dev-supervisor.d.ts +1 -9
  258. package/dist/dev-supervisor.js +21 -64
  259. package/dist/effect-port.d.ts +50 -0
  260. package/dist/effect-port.js +77 -0
  261. package/dist/engines/pi/agent-session-factory.d.ts +50 -69
  262. package/dist/engines/pi/agent-session-factory.js +249 -146
  263. package/dist/engines/pi/auth.d.ts +15 -11
  264. package/dist/engines/pi/auth.js +96 -99
  265. package/dist/engines/pi/chat.d.ts +1 -5
  266. package/dist/engines/pi/chat.js +2 -14
  267. package/dist/engines/pi/config.d.ts +53 -51
  268. package/dist/engines/pi/config.js +83 -92
  269. package/dist/engines/pi/create.d.ts +60 -75
  270. package/dist/engines/pi/create.js +108 -141
  271. package/dist/engines/pi/definition.d.ts +14 -58
  272. package/dist/engines/pi/definition.js +21 -85
  273. package/dist/engines/pi/invoke-session.d.ts +5 -25
  274. package/dist/engines/pi/invoke-session.js +209 -244
  275. package/dist/engines/pi/login.d.ts +95 -35
  276. package/dist/engines/pi/login.js +259 -107
  277. package/dist/engines/pi/machine.d.ts +31 -0
  278. package/dist/engines/pi/machine.js +106 -0
  279. package/dist/engines/pi/models.d.ts +70 -89
  280. package/dist/engines/pi/models.js +188 -81
  281. package/dist/engines/pi/open.d.ts +65 -53
  282. package/dist/engines/pi/open.js +129 -69
  283. package/dist/engines/pi/report.d.ts +3 -12
  284. package/dist/engines/pi/report.js +2 -16
  285. package/dist/engines/pi/search-tools.d.ts +1 -9
  286. package/dist/engines/pi/search-tools.js +26 -57
  287. package/dist/engines/pi/service.d.ts +2 -16
  288. package/dist/engines/pi/service.js +2 -16
  289. package/dist/engines/pi/session-builder.d.ts +5 -9
  290. package/dist/engines/pi/session-builder.js +44 -82
  291. package/dist/engines/pi/session-control.js +286 -269
  292. package/dist/engines/pi/session-effects.d.ts +14 -0
  293. package/dist/engines/pi/session-effects.js +30 -0
  294. package/dist/engines/pi/session-inheritance.d.ts +9 -18
  295. package/dist/engines/pi/session-inheritance.js +103 -160
  296. package/dist/engines/pi/session-markers.d.ts +31 -30
  297. package/dist/engines/pi/session-markers.js +26 -25
  298. package/dist/engines/pi/session-settings.d.ts +6 -22
  299. package/dist/engines/pi/session-settings.js +10 -21
  300. package/dist/engines/pi/session-store.d.ts +26 -92
  301. package/dist/engines/pi/session-store.js +96 -310
  302. package/dist/engines/pi/tool-context.d.ts +17 -45
  303. package/dist/engines/pi/tool-context.js +7 -26
  304. package/dist/engines/pi/tool.d.ts +45 -36
  305. package/dist/engines/pi/tool.js +43 -57
  306. package/dist/engines/pi/turn-kit.d.ts +14 -52
  307. package/dist/engines/pi/turn-kit.js +18 -67
  308. package/dist/engines/pi/wake-tool.d.ts +2 -10
  309. package/dist/engines/pi/wake-tool.js +7 -27
  310. package/dist/env.d.ts +27 -28
  311. package/dist/env.js +45 -50
  312. package/dist/index.js +0 -2
  313. package/dist/lark.d.ts +1 -2
  314. package/dist/lark.js +1 -2
  315. package/dist/loader.d.ts +14 -55
  316. package/dist/loader.js +24 -43
  317. package/dist/log.d.ts +2 -12
  318. package/dist/log.js +3 -24
  319. package/dist/node.d.ts +2 -15
  320. package/dist/node.js +2 -18
  321. package/dist/observe.d.ts +2 -8
  322. package/dist/open-url.d.ts +1 -1
  323. package/dist/open-url.js +1 -1
  324. package/dist/paths.d.ts +70 -134
  325. package/dist/paths.js +165 -232
  326. package/dist/pi.d.ts +4 -6
  327. package/dist/pi.js +3 -3
  328. package/dist/proxy.d.ts +16 -6
  329. package/dist/proxy.js +52 -16
  330. package/dist/runtime.d.ts +18 -12
  331. package/dist/runtime.js +18 -11
  332. package/dist/scaffold/add-channel.d.ts +14 -31
  333. package/dist/scaffold/add-channel.js +48 -112
  334. package/dist/scaffold/init.d.ts +9 -31
  335. package/dist/scaffold/init.js +45 -138
  336. package/dist/scaffold/templates/env.example +6 -4
  337. package/dist/scaffold/templates/fastagent.config.ts +36 -0
  338. package/dist/scaffold/templates.d.ts +3 -3
  339. package/dist/scaffold/templates.js +5 -8
  340. package/dist/scaffold/vendor-skill.d.ts +2 -5
  341. package/dist/scaffold/vendor-skill.js +19 -29
  342. package/dist/schedule/cron.d.ts +9 -5
  343. package/dist/schedule/cron.js +9 -11
  344. package/dist/schedule/discover.d.ts +12 -12
  345. package/dist/schedule/discover.js +43 -34
  346. package/dist/schedule/routine.d.ts +43 -0
  347. package/dist/schedule/routine.js +32 -0
  348. package/dist/schedule/run.d.ts +55 -0
  349. package/dist/schedule/run.js +146 -0
  350. package/dist/schedule/scheduler.d.ts +44 -55
  351. package/dist/schedule/scheduler.js +291 -265
  352. package/dist/schedule/state.d.ts +105 -10
  353. package/dist/schedule/state.js +293 -19
  354. package/dist/schedule/wake-alarm.d.ts +10 -23
  355. package/dist/schedule/wake-alarm.js +91 -121
  356. package/dist/schedule/wakeups.d.ts +23 -39
  357. package/dist/schedule/wakeups.js +33 -75
  358. package/dist/secrets-gate.d.ts +32 -0
  359. package/dist/secrets-gate.js +33 -0
  360. package/dist/service.d.ts +137 -98
  361. package/dist/service.js +250 -252
  362. package/dist/session-remote.d.ts +33 -44
  363. package/dist/session-remote.js +203 -192
  364. package/dist/session.d.ts +153 -228
  365. package/dist/session.js +33 -55
  366. package/dist/tunnel.d.ts +8 -36
  367. package/dist/tunnel.js +24 -65
  368. package/dist/version.d.ts +2 -2
  369. package/dist/version.js +2 -2
  370. package/package.json +10 -16
  371. package/dist/channels/agentcore-state.d.ts +0 -77
  372. package/dist/channels/agentcore-state.js +0 -276
  373. package/dist/channels/github/github.d.ts +0 -31
  374. package/dist/channels/github/github.js +0 -89
  375. package/dist/channels/github/scaffold/channel.ts +0 -25
  376. package/dist/cli/commands/attach.d.ts +0 -108
  377. package/dist/cli/commands/attach.js +0 -637
  378. package/dist/cli/commands/fire.d.ts +0 -7
  379. package/dist/cli/commands/fire.js +0 -38
  380. package/dist/cli/commands/schedule.d.ts +0 -12
  381. package/dist/cli/commands/schedule.js +0 -87
  382. package/dist/github.d.ts +0 -3
  383. package/dist/github.js +0 -2
  384. package/dist/scaffold/templates/fastagent.config.mjs +0 -23
  385. package/dist/schedule/audit.d.ts +0 -18
  386. package/dist/schedule/audit.js +0 -57
  387. package/dist/schedule/schedule.d.ts +0 -35
  388. package/dist/schedule/schedule.js +0 -23
@@ -1,39 +1,90 @@
1
1
  /**
2
- * Open a definition directory into an agent — the single agent opener BOTH `fastagent dev` and
3
- * `fastagent start` drive.
4
- *
5
- * A thin command-posture composition over L2 `createPiAgentFromDefinition`: open the directory →
6
- * resolve model (flag > env > config) and tools (append-after-defaults) → pick session storage →
7
- * call L2. dev and start share the SAME assembly here (what you iterate is what you serve); they
8
- * differ only at the CLI — dev watches and uses the in-tree sessions default, start runs without
9
- * watch and can point sessions at a mounted volume.
2
+ * Open a definition directory into an agent — the single agent opener BOTH `fastagent dev` and `fastagent start`
3
+ * drive.
10
4
  */
11
5
  import { mkdir } from "node:fs/promises";
12
- import { defaultSessionsDir, loadConfig, resolveAuthPath, resolveModelSpec, } from "./config.js";
13
- import { resolveStateRoot, resolvePlacement } from "../../paths.js";
14
- import { agentOf, assemblePiFromDefinition, resolveAgentTools } from "./create.js";
6
+ import { loadConfig, resolveAuthLayers, resolveModelSpec, } from "./config.js";
7
+ import { resolveSessionsDir, resolveStateRoot, resolvePlacement } from "../../paths.js";
8
+ import { agentOf, assemblePiFromDefinition, definitionModelRuntime, resolveAgentTools } from "./create.js";
15
9
  import { createPiSessionControl } from "./session-control.js";
16
10
  import { withWakeTool } from "./wake-tool.js";
11
+ import { refuseBrokenDeclarations } from "../../loader.js";
17
12
  import { loadAgentSkills } from "./definition.js";
13
+ import { servedExtensionCommands } from "./agent-session-factory.js";
18
14
  import { reportFindingsIfChanged } from "./report.js";
15
+ import { readMachine, withMachine } from "./machine.js";
19
16
  import { piSessionRecordStore } from "./session-store.js";
17
+ import { gateSecrets } from "../../secrets-gate.js";
18
+ /**
19
+ * The names a `/` composer completes: this agent's skills — the definition's, plus the ones its machine lends
20
+ * (machine.ts) —, the commands its `extensions/` register, and the machine's prompt templates.
21
+ *
22
+ * `source` says how a name is INVOKED, which is the one thing a client needs from it: a `skill` is sent as
23
+ * `/skill:<name>`, an `extension` command and a `prompt` as `/<name>` (docs/design/session-control.md §5.1.1).
24
+ *
25
+ * The definition is read live, like every turn reads it; the machine is the process's one read, the same snapshot a
26
+ * bound session runs on, so the menu cannot offer a name the next prompt would not expand. The same holds for
27
+ * extensions: `served` is what sessions load — the assembly's entry points, discovered once, and the runtime turns run
28
+ * on (asked only when there are extensions) — never a fresh scan of `extensions/`.
29
+ */
30
+ export async function agentCommands(agentDir, workspace, served) {
31
+ const own = await loadAgentSkills(agentDir, { cwd: workspace });
32
+ // A skill whose frontmatter broke simply is not in `skills` — it would vanish from the composer with no signal.
33
+ reportFindingsIfChanged(own.dir, own);
34
+ const machine = await readMachine(workspace);
35
+ const { extensionPaths } = served;
36
+ const extensionCommands = extensionPaths.length === 0
37
+ ? []
38
+ : await servedExtensionCommands({ cwd: workspace, modelRuntime: await served.modelRuntime(), extensionPaths });
39
+ // pi dispatches an extension command before it expands a template, so a template it shadows never runs.
40
+ const shadowed = new Set(extensionCommands.map((command) => command.invocationName));
41
+ return [
42
+ ...withMachine(own.skills, machine.skills).map((skill) => ({
43
+ name: skill.name,
44
+ description: skill.description,
45
+ source: "skill",
46
+ })),
47
+ ...extensionCommands.map((command) => ({
48
+ name: command.invocationName,
49
+ ...(command.description ? { description: command.description } : {}),
50
+ source: "extension",
51
+ })),
52
+ ...machine.prompts
53
+ .filter((prompt) => !shadowed.has(prompt.name))
54
+ .map((prompt) => ({
55
+ name: prompt.name,
56
+ ...(prompt.description ? { description: prompt.description } : {}),
57
+ source: "prompt",
58
+ })),
59
+ ];
60
+ }
20
61
  export async function resolveAgentAssembly(dir, options = {}) {
21
- // Placement is structural (resolvePlacement): the AGENT DIR carries definition + config + machinery;
22
- // its parent — the WORKSPACE — is what the agent works on: its cwd and the start of the ②-context
23
- // walk (that is where it reads the project's AGENTS.md from).
62
+ // Placement is structural (resolvePlacement): the AGENT DIR carries definition + config + machinery; its parent.
24
63
  const { agentDir, workspace } = resolvePlacement(dir);
25
64
  const { config, path: configPath } = await loadConfig(agentDir);
26
65
  const modelSpec = resolveModelSpec(options.model, config);
27
66
  if (!modelSpec) {
28
67
  throw new Error(`missing model: set --model, "model" in fastagent.config.ts, or FASTAGENT_MODEL (e.g. "openai-codex/gpt-5.5")`);
29
68
  }
30
- const { tools, toolNames, deferredToolNames, toolCollisions, toolFailures } = await resolveAgentTools(config, agentDir, workspace);
31
- // The state root: sessions/channel state/schedule state derive from it (FASTAGENT_STATE_DIR moves it
32
- // in one knob — a container points it at its volume); the finer overrides below still win.
69
+ const { tools, toolNames, deferredToolNames, toolCollisions, toolFailures, toolSecrets } = await resolveAgentTools(config, agentDir, workspace);
70
+ // THE serving-path gate for tool declarations, in the order that costs the fewest round trips. A file that could
71
+ // not be imported declares nothing, so its `secrets:` are missing from `toolSecrets` — gating secrets first would
72
+ // report an incomplete set, and fixing the file could then reveal more missing values. Refuse the broken file
73
+ // first and one pass reports every secret the definition actually wants.
74
+ refuseBrokenDeclarations(toolFailures);
75
+ // Both gates live here rather than inside resolveAgentTools, which `info` and `fastagent tool` call to REPORT on a
76
+ // definition and must survive both faults; every path through this function is about to run ALL of the tools, so
77
+ // no `owner`.
78
+ //
79
+ // DEFERRED tools gate too, deliberately: `search_tools` can activate one mid-turn, so "registered"
80
+ // means "may run in this process" — letting it start would put the empty-credential failure back
81
+ // inside a turn. An author who does not want that opts out per tool by not declaring.
82
+ gateSecrets({ declared: toolSecrets, failures: [] });
83
+ // The state root: sessions/channel state/schedule state derive from it (FASTAGENT_STATE_DIR moves it in one knob —
84
+ // a container points it at its volume).
33
85
  const stateRoot = resolveStateRoot(agentDir);
34
- // The credentials file: project-level by default (under `<agentDir>/.secrets`); only resolved here, never
35
- // created (a missing file reads as not-configured — `fastagent login` creates it).
36
- const authPath = resolveAuthPath(agentDir, options.authPath);
86
+ // The credentials file: project-level by default (under `<agentDir>/.secrets`).
87
+ const auth = resolveAuthLayers(agentDir, options.authPath);
37
88
  return {
38
89
  config,
39
90
  configPath,
@@ -41,26 +92,43 @@ export async function resolveAgentAssembly(dir, options = {}) {
41
92
  agentDir,
42
93
  workspace,
43
94
  stateRoot,
44
- authPath,
95
+ auth,
45
96
  tools,
46
97
  toolNames,
47
98
  deferredToolNames,
48
99
  toolCollisions,
49
- toolFailures,
100
+ toolSecrets,
50
101
  };
51
102
  }
52
103
  /**
53
- * "Point at a directory → agent": resolve the placement (`dir` may be either end — the workspace or
54
- * the agent dir itself), load the config, resolve model and tools, then L2. Throws a clear error when
55
- * no model source is set (fail visibly at startup). Returns everything an entry point needs to report
56
- * what it assembled.
104
+ * The model specs `createPiAgentFromDir(dir, { authPath })` could run now: the agent's registry (pi's built-ins plus
105
+ * its `models.json`) filtered to the providers whose credentials are configured, sorted. The directory needs no model
106
+ * set, and the same placement and credential layers as the opener apply, so a listed spec authenticates there.
107
+ * Configuration is checked, not validity: no OAuth token is refreshed and no provider is called.
108
+ *
109
+ * `warn` reaches the credential store. An unreadable or corrupt credentials file otherwise reads as "nothing
110
+ * configured", so a client that must not show that as an empty list passes a sink that throws.
111
+ */
112
+ export async function availableModelsFromDir(dir, options = {}) {
113
+ const { agentDir } = resolvePlacement(dir);
114
+ const models = await definitionModelRuntime(agentDir, {
115
+ auth: resolveAuthLayers(agentDir, options.authPath),
116
+ ...(options.warn ? { warn: options.warn } : {}),
117
+ });
118
+ return (await models.getAvailable()).map((model) => `${model.provider}/${model.id}`).sort();
119
+ }
120
+ /**
121
+ * "Point at a directory → agent": resolve the placement (`dir` may be either end — the workspace or the agent dir
122
+ * itself), load the config, resolve model and tools, then L2.
57
123
  */
58
124
  export async function createPiAgentFromDir(dir, options = {}) {
59
- const { config, configPath, modelSpec, agentDir, workspace, stateRoot, authPath, tools, toolNames, deferredToolNames, toolCollisions, toolFailures, } = await resolveAgentAssembly(dir, options);
60
- // Mount the built-in `wake` tool only when BOTH: this is a long-running serve (the poller honors it) AND
61
- // the author opted into self-scheduling (config.selfSchedule). The agent's own `wake` wins if defined.
62
- const mountedTools = withWakeTool(tools, stateRoot, !!options.serving && !!config.selfSchedule);
63
- const sessionsDir = options.sessionsDir ?? defaultSessionsDir(stateRoot);
125
+ const { config, configPath, modelSpec, agentDir, workspace, stateRoot, auth, tools, toolNames, deferredToolNames, toolCollisions, } = await resolveAgentAssembly(dir, options);
126
+ // Every serve mounts the built-in `wake` tool: the agent's own follow-up work is a default capability, and a serve
127
+ // is where the poller that honors it runs. A one-shot `invoke` has no poller, so nothing there would fire it.
128
+ const mountedTools = withWakeTool(tools, stateRoot, !!options.serving);
129
+ // An explicit value is used as given (the store resolves a relative one against the WORKSPACE); without one, the
130
+ // resolution every reader shares (config.ts), so a serve and an `info` never report on different directories.
131
+ const sessionsDir = options.sessionsDir ?? resolveSessionsDir(agentDir);
64
132
  await mkdir(sessionsDir, { recursive: true });
65
133
  const sessions = piSessionRecordStore({ dir: sessionsDir, cwd: workspace });
66
134
  const { assembly, definition } = await assemblePiFromDefinition(agentDir, {
@@ -68,51 +136,43 @@ export async function createPiAgentFromDir(dir, options = {}) {
68
136
  thinkingLevel: config.thinkingLevel,
69
137
  cwd: workspace,
70
138
  tools: mountedTools,
71
- authPath,
139
+ auth,
72
140
  // Skills are definition-only (the agent is its directory), so dev mirrors deployment exactly.
73
141
  sessions,
74
142
  });
75
- // The hub is wired HERE because the store is created here (an external `createPiSessionControl`
76
- // cannot exist before the store does). Its writes contend on the assembly's own lease and validate
77
- // against its own registry — the value above is what makes "same" true. An extra caller observer
78
- // composes after the hub's (TRUSTED seam).
143
+ // The hub is wired HERE because the store is created here (an external `createPiSessionControl` cannot exist before
144
+ // the store does).
79
145
  const caller = options.observer;
80
- const wantControl = options.sessionControl ?? (config.sessionControl === true && options.serving === true);
146
+ // TWO decisions, not one. The hub is in-process bookkeeping over the run observer: a serve gets it unconditionally,
147
+ // because `/stop` in a chat is an ordinary thing to ask for and reaching the live run is the only way to answer it.
148
+ // Publishing `/control/*` — steer, rewrite, delete, unauthenticated at whatever URL this serves on — is the
149
+ // separate decision `config.sessionControl` makes, and it is the only one that also wires the boundary.
150
+ const publish = options.sessionControl ?? config.sessionControl === true;
151
+ const wantControl = publish || options.serving === true;
81
152
  let hub;
82
153
  if (wantControl) {
83
- // The hub's surface is synchronous (`capabilities()` lists the allowed models), while building
84
- // the registry reads credentials and is not. Resolve it ONCE here — the opener is async anyway,
85
- // and the first turn would have paid the same read — so every boundary mutation validates
86
- // against the same registry the runs use.
87
- const { modelRuntime, model } = await assembly.engine();
88
- hub = createPiSessionControl({
89
- sessions,
90
- boundary: {
154
+ // The hub's surface is synchronous (`capabilities()` lists the allowed models), while building the registry reads
155
+ // credentials and is not. Resolved only when the boundary is wired, so an ordinary serve does not pay for it.
156
+ const boundary = publish
157
+ ? await assembly.engine().then(({ modelRuntime, model }) => ({
91
158
  lease: assembly.lease,
92
159
  models: modelRuntime,
93
160
  sessionFactory: assembly.sessionFactory,
94
161
  defaults: { model, thinkingLevel: assembly.thinkingLevel },
95
- },
96
- // Skills ARE the names a client offers — the resolved set, after collisions were decided
97
- // first-wins, which a client cannot reconstruct from the directory. Read LIVE (the directory
98
- // is the agent: a skill added while serving is in play on the next turn, so it must be
99
- // listable now) and SKILLS-ONLY: this is called when a composer opens its completion list,
100
- // and the full load's ② context walk buys nothing here.
101
- commands: async () => {
102
- const loaded = await loadAgentSkills(agentDir, { cwd: workspace });
103
- // A skill whose frontmatter broke simply is not in `skills` — it would disappear from the
104
- // author's composer with no signal anywhere. The memo is SHARED with the turn path (keyed
105
- // by dir), so a finding is warned when it appears, not once per reader that notices it.
106
- reportFindingsIfChanged(loaded.dir, loaded);
107
- return loaded.skills.map((skill) => ({
108
- name: skill.name,
109
- description: skill.description,
110
- source: "skill",
111
- }));
112
- },
113
- // The caller tap's boundary-event half: state_changed/compaction_* originate in the hub
114
- // and never cross the data plane's observer seam — without this, an audit tap wired here
115
- // would miss exactly the mutations it most needs to see (`update({ model })`).
162
+ }))
163
+ : undefined;
164
+ hub = createPiSessionControl({
165
+ sessions,
166
+ boundary,
167
+ // What a client offers is what a turn WOULD run, which since this agent inherits its machine is not the
168
+ // definition alone. Built through the same resource posture the turn uses, so the menu cannot list a name
169
+ // the prompt would not expand — a second reading of "which skills exist" is how those two come to disagree.
170
+ commands: () => agentCommands(agentDir, workspace, {
171
+ extensionPaths: assembly.extensionPaths,
172
+ modelRuntime: async () => (await assembly.engine()).modelRuntime,
173
+ }),
174
+ // The caller tap's boundary-event half: state_changed/compaction_* originate in the hub and never cross the
175
+ // data plane's observer seam.
116
176
  tap: caller ? (session, event) => caller(session, event) : undefined,
117
177
  });
118
178
  }
@@ -130,7 +190,8 @@ export async function createPiAgentFromDir(dir, options = {}) {
130
190
  definition,
131
191
  sessions,
132
192
  sessionControl: hub?.control,
133
- selfSchedule: config.selfSchedule ?? false,
193
+ publishControl: publish,
194
+ http: config.http,
134
195
  agentDir,
135
196
  workspace,
136
197
  config,
@@ -138,10 +199,9 @@ export async function createPiAgentFromDir(dir, options = {}) {
138
199
  modelSpec,
139
200
  stateRoot,
140
201
  sessionsDir,
141
- authPath,
202
+ auth,
142
203
  toolNames,
143
204
  deferredToolNames,
144
205
  toolCollisions,
145
- toolFailures,
146
206
  };
147
207
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Render assembly warnings to stderr — the one place both the CLI runners and the `chat` runtime show
3
- * the non-fatal definition/tool findings the loaders return as data. One copy so the wording can't drift.
2
+ * Render assembly warnings to stderr — the one place both the CLI runners and the `chat` runtime show the non-fatal
3
+ * definition/tool findings the loaders return as data.
4
4
  */
5
5
  import type { SkillDiagnostic } from "@earendil-works/pi-agent-core";
6
6
  import type { SkillCollision } from "./definition.ts";
@@ -9,16 +9,7 @@ type Findings = {
9
9
  collisions: SkillCollision[];
10
10
  diagnostics: SkillDiagnostic[];
11
11
  };
12
- /**
13
- * THE door for definition findings: warns only when this dir's set CHANGED since the last report.
14
- * Every reader calls it — boot, the per-turn live read, the control plane's command list — so a
15
- * finding is announced when it appears and never repeated. There is deliberately no "record without
16
- * printing" variant: a memo entry that trusts some other caller to have printed would silently
17
- * swallow findings for a caller that does not.
18
- *
19
- * `dir` must be the RESOLVED definition root (`LoadedDefinition.dir`), or two spellings of one path
20
- * become two memos and warn twice.
21
- */
12
+ /** THE door for definition findings: warns only when this dir's set CHANGED since the last report. */
22
13
  export declare function reportFindingsIfChanged(dir: string, def: Findings): void;
23
14
  export declare function reportDefinitionWarnings(collisions: SkillCollision[], diagnostics: SkillDiagnostic[]): void;
24
15
  export declare function reportToolCollisions(collisions: ToolCollision[]): void;
@@ -5,23 +5,9 @@ function findingsSignature(def) {
5
5
  const diagnostics = def.diagnostics.map((d) => `d:${d.code}:${d.path}`);
6
6
  return [...collisions, ...diagnostics].sort().join("\n");
7
7
  }
8
- /**
9
- * The last reported finding set PER DEFINITION DIR. One memo for every reader of a definition (the
10
- * per-turn live read, the control plane's command list), because the thing being deduped is the
11
- * FINDING, not the reader: a newly broken skill deserves one warning, not one per code path that
12
- * noticed it, and a static one deserves none at all.
13
- */
8
+ /** The last reported finding set PER DEFINITION DIR. */
14
9
  const lastFindings = new Map();
15
- /**
16
- * THE door for definition findings: warns only when this dir's set CHANGED since the last report.
17
- * Every reader calls it — boot, the per-turn live read, the control plane's command list — so a
18
- * finding is announced when it appears and never repeated. There is deliberately no "record without
19
- * printing" variant: a memo entry that trusts some other caller to have printed would silently
20
- * swallow findings for a caller that does not.
21
- *
22
- * `dir` must be the RESOLVED definition root (`LoadedDefinition.dir`), or two spellings of one path
23
- * become two memos and warn twice.
24
- */
10
+ /** THE door for definition findings: warns only when this dir's set CHANGED since the last report. */
25
11
  export function reportFindingsIfChanged(dir, def) {
26
12
  const sig = findingsSignature(def);
27
13
  if (lastFindings.get(dir) === sig)
@@ -1,12 +1,4 @@
1
1
  import { type MountedTool } from "./tool.ts";
2
- /** Mount the built-in loader iff any mounted tool is deferred and the author didn't define their own.
3
- * Typed on the MOUNTED tool, not the authored one: it inspects names and deferral and never executes,
4
- * so narrowing here would reject the very set it is handed (pi's defaults take the turn's context). */
2
+ /** Mount the built-in loader iff any mounted tool is deferred and the author didn't define their own. */
5
3
  export declare function withSearchTool(tools: MountedTool[]): MountedTool[];
6
- /** Build the `search_tools` loader. Keyword search over the inactive tools' name+description.
7
- *
8
- * `executionMode: "sequential"` — pi turns any batch containing a sequential tool serial. Required for
9
- * correct load-point attribution everywhere an OUTER active-set diff exists: pi wraps SDK customTools
10
- * (the chat path) in a before/after diff, and two parallel loader calls would both snapshot the
11
- * pre-activation set and get stamped with the same activation. Custom loader authors must set it too. */
12
4
  export declare function makeSearchToolsTool(): MountedTool;
@@ -1,16 +1,8 @@
1
- /**
2
- * The built-in `search_tools` loader — the discovery surface for deferred tools (defineTool
3
- * `deferred: true`). A deferred tool's schema is not in the request and the model cannot see it; this
4
- * loader is how it finds and activates one. Mounted automatically (withSearchTool) only when a
5
- * deferred tool exists; a workspace tool named `search_tools` wins — the author owns the concept then
6
- * (same rule as the wake pair).
7
- */
1
+ /** The built-in `search_tools` loader — the discovery surface for deferred tools (defineTool `deferred: true`). */
8
2
  import { z } from "zod";
9
3
  import { log } from "../../log.js";
10
4
  import { defineTool, isDeferredTool, stripDeferredMarker } from "./tool.js";
11
- /** Mount the built-in loader iff any mounted tool is deferred and the author didn't define their own.
12
- * Typed on the MOUNTED tool, not the authored one: it inspects names and deferral and never executes,
13
- * so narrowing here would reject the very set it is handed (pi's defaults take the turn's context). */
5
+ /** Mount the built-in loader iff any mounted tool is deferred and the author didn't define their own. */
14
6
  export function withSearchTool(tools) {
15
7
  if (!tools.some(isDeferredTool))
16
8
  return tools;
@@ -19,41 +11,23 @@ export function withSearchTool(tools) {
19
11
  return [...tools, makeSearchToolsTool()];
20
12
  let fixed = authored;
21
13
  if (isDeferredTool(fixed)) {
22
- // A deferred LOADER is a contradiction — it is the only entry point to the deferred tools, so
23
- // nothing could ever activate it (or, through it, them): every deferred tool would be silently
24
- // unreachable. Ignore the marker and keep the loader active (fail visibly, keep the capability).
14
+ // A deferred LOADER is a contradiction — it is the only entry point to the deferred tools, so nothing could ever
15
+ // activate it (or, through it, them).
25
16
  log.warn("[fastagent] search_tools is marked deferred — ignoring the marker: the loader must stay active, or no deferred tool could ever be activated");
26
17
  fixed = stripDeferredMarker(fixed);
27
18
  }
28
- if (fixed.executionMode !== "sequential") {
29
- // A non-sequential loader silently revives the parallel double-attribution (pi's diff around SDK
30
- // tools in chat) — enforce the mode rather than hope the author read the docs; warn so they know.
31
- log.warn('[fastagent] search_tools lacks executionMode: "sequential" — forcing it: parallel loader calls would misattribute activations');
32
- fixed = { ...fixed, executionMode: "sequential" };
33
- }
34
19
  return fixed === authored ? tools : tools.map((t) => (t === authored ? fixed : t));
35
20
  }
36
- /** Activation cap per search: activation is additive, session-persisted, and has NO deactivate path —
37
- * without a cap, one broad token ("get", "file") would permanently activate half the catalog and
38
- * silently spend the entire deferral benefit for the rest of the conversation. Over the cap nothing
39
- * activates; the model gets the candidates and narrows the query. */
21
+ /** Activation cap per search: activation is additive, session-persisted, and has NO deactivate path. */
40
22
  const MAX_ACTIVATIONS_PER_SEARCH = 5;
41
- /** Miss-path listing cap — same rationale as the activation cap: a typo query must not pour the whole
42
- * catalog (the thing deferral keeps OUT of the context) back in as a tool result. */
23
+ /** Miss-path listing cap — same rationale as the activation cap. */
43
24
  const MAX_MISS_LISTING = 10;
44
- /** Build the `search_tools` loader. Keyword search over the inactive tools' name+description.
45
- *
46
- * `executionMode: "sequential"` — pi turns any batch containing a sequential tool serial. Required for
47
- * correct load-point attribution everywhere an OUTER active-set diff exists: pi wraps SDK customTools
48
- * (the chat path) in a before/after diff, and two parallel loader calls would both snapshot the
49
- * pre-activation set and get stamped with the same activation. Custom loader authors must set it too. */
50
25
  export function makeSearchToolsTool() {
51
26
  return defineTool({
52
27
  name: "search_tools",
53
- executionMode: "sequential",
54
28
  description:
55
- // First line short on purpose: the base prompt's tools list truncates at the first newline, and
56
- // the discovery guidance below would otherwise flood it (and duplicate its deferred note).
29
+ // First line short on purpose: the base prompt's tools list truncates at the first newline, and the discovery
30
+ // guidance below would otherwise flood it (and duplicate its deferred note).
57
31
  "Discover and activate additional tools.\n" +
58
32
  "Part of this agent's toolset is inactive until needed: search by keywords (e.g. what you are " +
59
33
  "trying to do), and matching inactive tools are activated and become callable from that point on " +
@@ -65,19 +39,18 @@ export function makeSearchToolsTool() {
65
39
  async execute(input, ctx) {
66
40
  if (!ctx.tools)
67
41
  return "tool activation is unavailable outside a conversation turn.";
68
- // Search the WHOLE registered catalog: the loader is the only discovery surface, and in a long
69
- // conversation the model does not remember what it activated — a "No tools matched" answer for
70
- // an ALREADY-ACTIVE tool would push it toward the exact wrong conclusion (capability missing).
71
- // ponytail: naive keyword match (any query token as a case-insensitive substring of
72
- // name+description) with a hard per-search activation cap above — the two named ceilings are
73
- // relevance and irreversibility; swap in scoring/embeddings if catalogs outgrow this.
42
+ // Search the WHOLE registered catalog: the loader is the only discovery surface, and in a long conversation the
43
+ // model does not remember what it activated.
44
+ // ponytail: naive keyword match (any query token as a case-insensitive substring of name+description) under a
45
+ // hard per-search activation cap — the two ceilings are relevance and irreversibility; swap in
46
+ // scoring/embeddings if catalogs outgrow this.
74
47
  const tokens = input.query
75
48
  .toLowerCase()
76
49
  .split(/[^a-z0-9]+/)
77
50
  .filter(Boolean);
78
- // AND semantics: EVERY token must hit — "adding a word narrows" must actually hold, or the
79
- // over-cap "narrow the query" instruction sends the model in circles (OR would widen with each
80
- // word, and a shared prefix like "fetch" could make a whole tool family permanently over-cap).
51
+ // AND semantics: EVERY token must hit — "adding a word narrows" must actually hold, or the over-cap "narrow the
52
+ // query" instruction sends the model in circles (OR would widen with each word, and a shared prefix like
53
+ // "fetch" could make a whole tool family permanently over-cap).
81
54
  const matchesQuery = (t) => {
82
55
  const haystack = `${t.name} ${t.description}`.toLowerCase();
83
56
  return tokens.every((token) => haystack.includes(token));
@@ -85,19 +58,16 @@ export function makeSearchToolsTool() {
85
58
  const describe = (t) => `${t.name} — ${t.description.split("\n")[0]}`;
86
59
  const active = new Set(ctx.tools.active());
87
60
  const registered = ctx.tools.registered();
88
- // A query with no searchable tokens (all punctuation/symbols) must not match: `every` over an
89
- // empty token list is vacuously true, and a noise query would otherwise activate the catalog.
61
+ // A query with no searchable tokens (all punctuation/symbols) must not match.
90
62
  if (tokens.length === 0)
91
63
  return `"${input.query}" contains no searchable keywords — describe the capability you need.`;
92
- // Exact-name shortcut: the guaranteed escape hatch from the cap — a query that IS an INACTIVE
93
- // registered tool's name addresses that one tool, no keyword scoring in the way. An exact match
94
- // on an ACTIVE tool falls through to keyword matching: it must not swallow the discovery of
95
- // other, still-inactive keyword matches.
64
+ // Exact-name shortcut: the guaranteed escape hatch from the cap — a query that IS an INACTIVE registered tool's
65
+ // name addresses that one tool, no keyword scoring in the way.
96
66
  const exact = registered.find((t) => t.name.toLowerCase() === input.query.trim().toLowerCase());
97
67
  const activeMatches = registered.filter((t) => active.has(t.name) && (t === exact || matchesQuery(t)));
98
68
  const inactiveMatches = exact && !active.has(exact.name) ? [exact] : registered.filter((t) => !active.has(t.name) && matchesQuery(t));
99
- // Same listing cap as every other branch — a wide token can match most of the ACTIVE set too
100
- // (in chat that includes pi's default tools), and no answer may pour a catalog into the context.
69
+ // Same listing cap as every other branch — a wide token can match most of the ACTIVE set too (in chat that
70
+ // includes pi's default tools), and no answer may pour a catalog into the context.
101
71
  const listedActive = activeMatches.slice(0, MAX_MISS_LISTING);
102
72
  const moreActive = activeMatches.length - listedActive.length;
103
73
  const activeNote = activeMatches.length > 0
@@ -109,15 +79,14 @@ export function makeSearchToolsTool() {
109
79
  const inactive = registered.filter((t) => !active.has(t.name));
110
80
  if (inactive.length === 0)
111
81
  return "All tools are already active — nothing to discover.";
112
- // Cap the miss listing like the activation cap — both guard the same semantic (don't pour the
113
- // catalog back into the context the deferral exists to protect).
82
+ // Cap the miss listing like the activation cap — both guard the same semantic (don't pour the catalog back
83
+ // into the context the deferral exists to protect).
114
84
  const listed = inactive.slice(0, MAX_MISS_LISTING);
115
85
  const more = inactive.length - listed.length;
116
86
  return `No tools matched "${input.query}". Inactive tools: ${listed.map(describe).join("; ")}${more > 0 ? ` … and ${more} more — search with different keywords.` : ""}`;
117
87
  }
118
88
  if (inactiveMatches.length > MAX_ACTIVATIONS_PER_SEARCH) {
119
- // Same listing cap as the miss path — an over-cap answer must not pour the catalog into the
120
- // context either. Names alone suffice: the exact-name escape only needs a name to query.
89
+ // Same listing cap as the miss path — an over-cap answer must not pour the catalog into the context either.
121
90
  const listed = inactiveMatches.slice(0, MAX_MISS_LISTING);
122
91
  const more = inactiveMatches.length - listed.length;
123
92
  return `${inactiveMatches.length} inactive tools matched "${input.query}" — too many to activate at once (activation is permanent for this conversation). Narrow the query (or query an exact name). Matches: ${listed
@@ -125,8 +94,8 @@ export function makeSearchToolsTool() {
125
94
  .join(", ")}${more > 0 ? ` … and ${more} more` : ""}.${activeNote ? ` ${activeNote}` : ""}`;
126
95
  }
127
96
  const activated = ctx.tools.activate(inactiveMatches.map((t) => t.name));
128
- // Report what actually happened, not what was attempted: a parallel sibling call may have
129
- // activated the same matches first, leaving nothing new here — an empty "Activated:" would lie.
97
+ // Report what actually happened, not what was attempted: a parallel sibling call may have activated the same
98
+ // matches first, leaving nothing new here.
130
99
  if (activated.length === 0) {
131
100
  return `Matched ${inactiveMatches.map((t) => t.name).join(", ")} — already active (possibly activated by a concurrent call). Call them directly.${activeNote ? ` ${activeNote}` : ""}`;
132
101
  }
@@ -1,23 +1,9 @@
1
- /**
2
- * `createAgentService` — the product as one call, with pi supplying the engine.
3
- *
4
- * The ASSEMBLY is engine-neutral and lives in `src/service.ts`: it takes a {@link MountableAgent}
5
- * (the SPEC contract plus three paths) and knows nothing about how that agent was built. What is
6
- * pi-specific is opening a DIRECTORY into one, which is why this shortcut lives here and the
7
- * assembly does not. A second engine ships its own opener and reuses `mountAgentService` unchanged.
8
- */
1
+ /** `createAgentService` — the product as one call, with pi supplying the engine. */
9
2
  import { type AgentService, type MountAgentServiceOptions } from "../../service.ts";
10
3
  export interface CreateAgentServiceOptions extends MountAgentServiceOptions {
11
4
  model?: string;
12
5
  authPath?: string;
13
6
  sessionsDir?: string;
14
7
  }
15
- /**
16
- * Open an agent directory as a live service: one handler, mounted wherever you serve.
17
- *
18
- * ```ts
19
- * const service = await createAgentService("./my-agent");
20
- * app.use("/agent", nodeListener(service.handler));
21
- * ```
22
- */
8
+ /** Open an agent directory as a live service: one handler, mounted wherever you serve. */
23
9
  export declare function createAgentService(dir: string, options?: CreateAgentServiceOptions): Promise<AgentService>;
@@ -1,21 +1,7 @@
1
- /**
2
- * `createAgentService` — the product as one call, with pi supplying the engine.
3
- *
4
- * The ASSEMBLY is engine-neutral and lives in `src/service.ts`: it takes a {@link MountableAgent}
5
- * (the SPEC contract plus three paths) and knows nothing about how that agent was built. What is
6
- * pi-specific is opening a DIRECTORY into one, which is why this shortcut lives here and the
7
- * assembly does not. A second engine ships its own opener and reuses `mountAgentService` unchanged.
8
- */
1
+ /** `createAgentService` — the product as one call, with pi supplying the engine. */
9
2
  import { mountAgentService } from "../../service.js";
10
3
  import { createPiAgentFromDir } from "./open.js";
11
- /**
12
- * Open an agent directory as a live service: one handler, mounted wherever you serve.
13
- *
14
- * ```ts
15
- * const service = await createAgentService("./my-agent");
16
- * app.use("/agent", nodeListener(service.handler));
17
- * ```
18
- */
4
+ /** Open an agent directory as a live service: one handler, mounted wherever you serve. */
19
5
  export async function createAgentService(dir, options = {}) {
20
6
  const opened = await createPiAgentFromDir(dir, {
21
7
  ...(options.model !== undefined ? { model: options.model } : {}),
@@ -1,16 +1,12 @@
1
1
  import { type AgentSessionRuntime, SessionManager } from "@earendil-works/pi-coding-agent";
2
2
  export interface BuildSessionRuntimeOptions {
3
- /** Model spec override (the CLI --model flag). Precedence: this > FASTAGENT_MODEL > config.model. */
3
+ /** Model spec override (the CLI --model flag). */
4
4
  model?: string;
5
- /** Credentials file override (the CLI --auth-path flag). Precedence: this > FASTAGENT_AUTH_PATH >
6
- * the agent's default `<agentDir>/.secrets/auth.json`. */
5
+ /** Credentials file override (the SDK `authPath` option; the CLI has only `FASTAGENT_AUTH_PATH`). */
7
6
  authPath?: string;
8
7
  }
9
8
  /**
10
- * Build pi's interactive runtime driven by fastagent's assembled agent (model, prompt, tools,
11
- * skills, and auth resolved exactly as the serving opener does). Split from the TUI launcher so the
12
- * assembly — the fidelity-critical part — is inspectable and reusable without launching a TUI.
9
+ * Build pi's interactive runtime driven by fastagent's assembled agent (model, prompt, tools, skills, and auth
10
+ * resolved exactly as the serving opener does).
13
11
  */
14
- export declare function buildAgentSessionRuntime(dir: string, options?: BuildSessionRuntimeOptions,
15
- /** Session backend. Defaults to pi's project-scoped store; tests inject SessionManager.inMemory(). */
16
- sessionManager?: SessionManager): Promise<AgentSessionRuntime>;
12
+ export declare function buildAgentSessionRuntime(dir: string, options?: BuildSessionRuntimeOptions, sessionManager?: SessionManager): Promise<AgentSessionRuntime>;