@fastagent-sh/fastagent 0.24.4 → 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 -535
  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 -92
  455. package/dist/engines/pi/turn-kit.js +0 -288
  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
@@ -1,13 +1,27 @@
1
1
  /**
2
- * `fastagent add <channel>`: drop a `channels/<kind>.ts` adapter-glue file (+ any companion tool, +
2
+ * `fastagent add <channel>`: drop a `src/channels/<kind>.ts` adapter-glue file (+ any companion tool, +
3
3
  * `.secrets/.env.example` vars) into an existing agent.
4
4
  */
5
5
  import { appendFile, mkdir, readFile, writeFile } from "node:fs/promises";
6
6
  import { dirname, join } from "node:path";
7
7
  import { detectRuntime } from "../runtime.js";
8
- import { SECRETS_DIRNAME, SECRET_FILE_MODE, assertInsideAgentDir, exists } from "../paths.js";
9
- import { baseTemplate, channelBundleFiles, channelTemplate } from "./templates.js";
10
- import { dotEnvPath, envExamplePath, parseEnvContent } from "../env.js";
8
+ import { assertInsideAgentDir, exists } from "../paths.js";
9
+ import { channelBundleFiles, channelTemplate } from "./templates.js";
10
+ import { writeValues } from "../value-file.js";
11
+ import { envExamplePath } from "../env.js";
12
+ import { FEISHU_AGENT_SCOPES } from "../channels/feishu/setup-mode.js";
13
+ import { slackBotScopes } from "../channels/slack/manifest.js";
14
+ const AGENT_SCOPE_LIST = FEISHU_AGENT_SCOPES.map((entry) => entry.request).join(", ");
15
+ const FEISHU_PERMISSION_STEP = `before publishing: the app needs ${AGENT_SCOPE_LIST} — \`add feishu\` requests them with the app and names any ` +
16
+ "your tenant withheld (approve those, then publish)";
17
+ /** The setting both clouds read, said the same way in `.env.example` and in `add`'s next steps. */
18
+ const ingressEnv = (prefix) => ({
19
+ name: `${prefix}_INGRESS`,
20
+ hint: "webhook | websocket — unset, `dev` connects by websocket, and `start` and every deployment receive by webhook",
21
+ required: false,
22
+ });
23
+ const LARK_PERMISSION_STEP = `before publishing: add ${AGENT_SCOPE_LIST} — \`add lark\` checks them and opens the permission page for any that ` +
24
+ "are missing";
11
25
  const CHANNEL_SCAFFOLDS = {
12
26
  telegram: {
13
27
  env: [
@@ -30,10 +44,11 @@ const CHANNEL_SCAFFOLDS = {
30
44
  { name: "SLACK_SIGNING_SECRET", hint: "Slack app → Basic Information → App Credentials", required: true },
31
45
  ],
32
46
  steps: [
33
- "Slack Bot Token Scopes: app_mentions:read, assistant:write, chat:write, im:history, files:read, files:write, channels:history, groups:history, mpim:history",
47
+ `Slack Bot Token Scopes: ${slackBotScopes().join(", ")}`,
34
48
  "enable Agents (agent_view) and leave token rotation OFF (it cannot be turned off again); subscribe app_home_opened, app_context_changed, app_mention, message.im, message.channels, message.groups, message.mpim; set Request URL to <public-url>/slack",
35
49
  "reinstall the app after changing scopes, then invite it to each channel it should read",
36
50
  "the agent can send messages or files by calling the scaffolded {tools}/slack-send.ts tool",
51
+ "the agent reads the threads of the channel it is asked in via the scaffolded {tools}/slack-threads.ts tool",
37
52
  ],
38
53
  },
39
54
  // Feishu is the canonical engine/cloud; Lark international reuses its protocol through a degraded compatibility
@@ -52,21 +67,23 @@ const CHANNEL_SCAFFOLDS = {
52
67
  },
53
68
  {
54
69
  name: "FEISHU_VERIFICATION_TOKEN",
55
- hint: "captured automatically (console → Events & Callbacks)",
70
+ hint: "webhook only (`start`, every deployment) — captured by `deploy --run` (console → Events & Callbacks)",
56
71
  required: true,
57
72
  },
58
73
  {
59
74
  name: "FEISHU_ENCRYPT_KEY",
60
- hint: "optional but recommended — set one in the console and copy it here",
75
+ hint: "webhook only; optional but recommended — set one in the console and copy it here",
61
76
  required: false,
62
77
  },
78
+ ingressEnv("FEISHU"),
63
79
  ],
64
80
  steps: [
65
- "before publishing: approve the sensitive im:message.group_msg permission for context-aware groups (the CLI adds it to the app draft when supported); it delivers all group messages so bare replies in the agent's own threads can invoke and other unsummoned discussion can buffer. im:message:readonly is requested alongside it so a thread's opening ask can carry the message it quotes; both need the same approval",
81
+ FEISHU_PERMISSION_STEP,
66
82
  "PUBLISH the app version in the developer console after permission approval — the switch to webhook mode takes effect on publish (one click, once ever; no API for it)",
67
83
  "edit {channel} — routing policy (the header walks through the console setup, for hand-made apps)",
68
- "the event Request URL is auto-registered by `dev --tunnel` / `deploy --run`",
84
+ "for webhook ingress, run `{serve} --tunnel` locally or `deploy --run` to register the selected app's event Request URL",
69
85
  "the agent can push messages from scheduled turns via the scaffolded {tools}/feishu-send.ts tool",
86
+ "the agent reads the threads of the group it is asked in via the scaffolded {tools}/feishu-threads.ts tool",
70
87
  ],
71
88
  },
72
89
  lark: {
@@ -75,79 +92,63 @@ const CHANNEL_SCAFFOLDS = {
75
92
  { name: "LARK_APP_SECRET", hint: "developer console → Credentials & Basic Info", required: true },
76
93
  {
77
94
  name: "LARK_VERIFICATION_TOKEN",
78
- hint: "console → Events & Callbacks; authenticates inbound events",
95
+ hint: "webhook only (`start`, every deployment) — console → Events & Callbacks; authenticates inbound events",
79
96
  required: true,
80
97
  },
81
98
  {
82
99
  name: "LARK_ENCRYPT_KEY",
83
- hint: "optional but recommended — set one in the console and copy it here",
100
+ hint: "webhook only; optional but recommended — set one in the console and copy it here",
84
101
  required: false,
85
102
  },
103
+ ingressEnv("LARK"),
86
104
  ],
87
105
  steps: [
88
106
  "finish the console setup: enable Bot and add the required permissions + im.message.receive_v1 event listed in {channel} (do not publish yet)",
89
- "before publishing: approve the sensitive im:message.group_msg permission for context-aware groups (add it manually if Lark's config API fallback was used); it delivers all group messages so bare replies in the agent's own threads can invoke and other unsummoned discussion can buffer. im:message:readonly is requested alongside it so a thread's opening ask can carry the message it quotes; both need the same approval",
90
- "run `fastagent dev --tunnel` and keep it running; if auto-registration reports a config-API 404, manually switch Subscription mode to webhook, set its printed https://…/lark Request URL, save, then create + publish a version",
107
+ LARK_PERMISSION_STEP,
108
+ "for webhook ingress, run `{serve} --tunnel` and keep it running; if auto-registration reports a config-API 404, manually switch Subscription mode to webhook, set its printed https://…/lark Request URL, save, then create + publish a version",
91
109
  "the agent can push messages from scheduled turns via the scaffolded {tools}/lark-send.ts tool",
110
+ "the agent reads the threads of the group it is asked in via the scaffolded {tools}/lark-threads.ts tool",
92
111
  ],
93
112
  },
94
113
  };
95
114
  /** The channel kinds `fastagent add <kind>` can scaffold. */
96
115
  export const CHANNEL_KINDS = Object.keys(CHANNEL_SCAFFOLDS);
97
- const WEBSOCKET_SETUPS = {
98
- feishu: {
99
- env: CHANNEL_SCAFFOLDS.feishu.env.filter((entry) => ["FEISHU_APP_ID", "FEISHU_APP_SECRET"].includes(entry.name)),
116
+ /** Local setup never changes the production app or ingress. */
117
+ const deploymentStep = (kind, pinned) => pinned
118
+ ? `run \`{serve}\` (no tunnel needed: it connects by WebSocket); ${kind.toUpperCase()}_INGRESS=websocket is ` +
119
+ "set for this environment; production has its own app and ingress setting"
120
+ : "run `{serve}` (no tunnel needed: it connects by WebSocket); " +
121
+ "`deploy --run` creates a separate production app for webhook";
122
+ const websocketSetup = (kind, pinned) => {
123
+ const prefix = kind.toUpperCase();
124
+ return {
125
+ env: CHANNEL_SCAFFOLDS[kind].env.filter((entry) => [`${prefix}_APP_ID`, `${prefix}_APP_SECRET`, `${prefix}_INGRESS`].includes(entry.name)),
100
126
  steps: [
101
- "before publishing: approve the sensitive im:message.group_msg permission for context-aware groups (the CLI adds it to the app draft when supported); it delivers all group messages so bare replies in the agent's own threads can invoke and other unsummoned discussion can buffer. im:message:readonly is requested alongside it so a thread's opening ask can carry the message it quotes; both need the same approval",
102
- "PUBLISH the app version in the developer console after permission approval — long-connection event subscriptions become active with the published version",
103
- "edit {channel} — routing policy (the scaffold is already set to WebSocket ingress)",
104
- "run `fastagent dev` without --tunnel; deployments must keep one process running (no scale-to-zero)",
105
- "the agent can push messages from scheduled turns via the scaffolded {tools}/feishu-send.ts tool",
106
- ],
107
- },
108
- lark: {
109
- env: CHANNEL_SCAFFOLDS.lark.env.filter((entry) => ["LARK_APP_ID", "LARK_APP_SECRET"].includes(entry.name)),
110
- steps: [
111
- "before publishing: approve the sensitive im:message.group_msg permission for context-aware groups (add it manually if Lark's config API fallback was used); it delivers all group messages so bare replies in the agent's own threads can invoke and other unsummoned discussion can buffer. im:message:readonly is requested alongside it so a thread's opening ask can carry the message it quotes; both need the same approval",
112
- "in Events & Callbacks choose long connection, subscribe im.message.receive_v1, then create + publish a version",
113
- "edit {channel} — routing policy (the scaffold is already set to WebSocket ingress)",
114
- "run `fastagent dev` without --tunnel; deployments must keep one process running (no scale-to-zero)",
115
- "the agent can push messages from scheduled turns via the scaffolded {tools}/lark-send.ts tool",
127
+ kind === "feishu" ? FEISHU_PERMISSION_STEP : LARK_PERMISSION_STEP,
128
+ kind === "feishu"
129
+ ? "PUBLISH the app version in the developer console after permission approval — long-connection event subscriptions become active with the published version"
130
+ : "in Events & Callbacks choose long connection, subscribe im.message.receive_v1, then create + publish a version",
131
+ "edit {channel} — routing policy",
132
+ deploymentStep(kind, pinned),
133
+ `the agent can push messages from scheduled turns via the scaffolded {tools}/${kind}-send.ts tool`,
134
+ `the agent reads the threads of the group it is asked in via the scaffolded {tools}/${kind}-threads.ts tool`,
116
135
  ],
117
- },
136
+ };
118
137
  };
119
138
  /** The mode-specific env vars + next-step lines a scaffolded channel needs. */
120
- export function channelSetup(kind, ingress = "webhook", groupBehavior) {
121
- const behavior = groupBehavior ?? "context";
122
- const setup = ingress === "websocket" && (kind === "feishu" || kind === "lark")
123
- ? WEBSOCKET_SETUPS[kind]
124
- : CHANNEL_SCAFFOLDS[kind];
125
- if ((kind === "feishu" || kind === "lark") && behavior === "mentions") {
126
- return {
127
- env: setup.env,
128
- steps: setup.steps.map((step) => step.includes("im:message.group_msg")
129
- ? "group behavior: mention-only — do not grant im:message.group_msg; bare thread replies and group context buffering remain disabled. im:message:readonly is independent of this choice: add it if you want an @mention to carry the message it quotes (without it that quote degrades to a marker)"
130
- : step),
131
- };
132
- }
133
- if (kind === "slack" && behavior === "mentions") {
134
- return {
135
- env: setup.env,
136
- steps: [
137
- "Slack Bot Token Scopes: app_mentions:read, assistant:write, chat:write, im:history, files:read, files:write (no channel/group/mpim history scopes)",
138
- "enable Agents (agent_view) and leave token rotation OFF (it cannot be turned off again); subscribe app_home_opened, app_context_changed, app_mention, and message.im; set Request URL to <public-url>/slack",
139
- "group behavior: mention-only — bare thread replies and unsummoned group context remain disabled",
140
- ...setup.steps.slice(2),
141
- ],
142
- };
143
- }
144
- return { env: setup.env, steps: setup.steps };
139
+ export function channelSetup(kind, ingress = "webhook",
140
+ /** Whether `<PREFIX>_INGRESS` is explicitly set in this environment. */
141
+ pinned = false) {
142
+ if (ingress !== "websocket" || (kind !== "feishu" && kind !== "lark"))
143
+ return CHANNEL_SCAFFOLDS[kind];
144
+ return websocketSetup(kind, pinned);
145
145
  }
146
146
  /**
147
147
  * Append a channel's env vars (commented placeholders + hints) to `.env.example`, so a developer who copies it to
148
- * `.env` finds the vars already there.
148
+ * `.env` finds the vars already there. Every var the channel can declare, whichever way this machine receives it:
149
+ * the file is what a collaborator or CI fills for a deployment, which receives Feishu/Lark by webhook.
149
150
  */
150
- export async function appendChannelEnv(dir, kind, ingress = "webhook") {
151
+ export async function appendChannelEnv(dir, kind) {
151
152
  const file = envExamplePath(dir);
152
153
  let current;
153
154
  try {
@@ -162,149 +163,50 @@ export async function appendChannelEnv(dir, kind, ingress = "webhook") {
162
163
  if (current.includes(marker))
163
164
  return false;
164
165
  // Hint on its OWN line above the placeholder (like the base env.example template).
165
- const block = `\n${marker}\n${channelSetup(kind, ingress)
166
- .env.map((e) => `# ${e.hint}\n# ${e.name}=`)
167
- .join("\n")}\n`;
166
+ const block = `\n${marker}\n${CHANNEL_SCAFFOLDS[kind].env.map((e) => `# ${e.hint}\n# ${e.name}=`).join("\n")}\n`;
168
167
  await appendFile(file, block);
169
168
  return true;
170
169
  }
171
- /** Whether `.env` content carries a non-empty ACTIVE value for `name`. */
172
- function hasActiveEnvValue(content, name) {
173
- return (parseEnvContent(content).get(name)?.trim() ?? "") !== "";
174
- }
175
- function mentionsEnvName(content, name) {
176
- return content.split("\n").some((line) => new RegExp(`^\\s*#?\\s*${name}\\s*=`).test(line));
177
- }
178
170
  /**
179
- * Append generated channel secrets to the agent's `.env` (`.secrets/.env` — never `.env.example`) Existing non-empty
180
- * values are kept.
171
+ * Append generated channel secrets to the selected environment's value file, and a placeholder for each the channel
172
+ * reads that nothing generated. Existing non-empty values are kept.
181
173
  */
182
- export async function appendChannelDotEnv(dir, kind, generated, overwrite = [], ingress = "webhook") {
183
- const file = dotEnvPath(dir);
184
- const secretsDir = dirname(file);
185
- await mkdir(secretsDir, { recursive: true });
186
- // THE one exception to "fastagent has no opinion about git": the directory it writes secrets into carries its own
187
- // `.gitignore`.
188
- const owned = secretsDir === join(dir, SECRETS_DIRNAME);
189
- let unprotectedSecretsDir;
190
- if (owned) {
191
- // Only EEXIST is tolerable (already protected, or a concurrent writer).
192
- await writeFile(join(secretsDir, ".gitignore"), baseTemplate("secrets.gitignore"), { flag: "wx" }).catch((e) => {
193
- if (e.code !== "EEXIST")
194
- throw e;
195
- });
196
- }
197
- else if (!(await exists(join(secretsDir, ".gitignore")))) {
198
- unprotectedSecretsDir = secretsDir;
199
- }
200
- let current = "";
201
- try {
202
- current = await readFile(file, "utf8");
203
- }
204
- catch (e) {
205
- if (e.code !== "ENOENT")
206
- throw e;
207
- }
208
- const env = channelSetup(kind, ingress).env;
209
- const alreadySet = env
210
- .filter((e) => !overwrite.includes(e.name) && hasActiveEnvValue(current, e.name))
211
- .map((e) => e.name);
212
- const lines = [];
213
- const written = [];
214
- const contentLines = current.split("\n");
215
- let replacedInPlace = false;
216
- for (const e of env) {
217
- if (alreadySet.includes(e.name))
218
- continue;
219
- const value = generated[e.name];
220
- if (value !== undefined) {
221
- // An ACTIVE but EMPTY assignment already in the file (an uncommented, unfilled placeholder from `cp
222
- // .env.example .env`) must be replaced IN PLACE.
223
- let idx = -1;
224
- for (let i = contentLines.length - 1; i >= 0; i--) {
225
- if (parseEnvContent(contentLines[i]).has(e.name)) {
226
- idx = i;
227
- break;
228
- }
229
- }
230
- if (idx >= 0) {
231
- contentLines[idx] = `${e.name}=${value}`;
232
- replacedInPlace = true;
233
- }
234
- else {
235
- lines.push(`${e.name}=${value}`);
236
- }
237
- written.push(e.name);
238
- }
239
- else if (!mentionsEnvName(current, e.name)) {
240
- // Hint above, never inline after `=` — see appendChannelEnv (this IS the file loadDotEnv reads).
241
- lines.push(`# ${e.hint}`, `# ${e.name}=`);
242
- }
243
- }
244
- // 0600 on the file this call CREATES, and nothing at all about one it did not: a `.env` the operator made with
245
- // `cp .secrets/.env.example .secrets/.env` carries the mode that command gave it, and re-deciding it would be
246
- // fastagent changing the permissions of a file it does not own.
247
- const secret = { mode: SECRET_FILE_MODE };
248
- if (replacedInPlace) {
249
- current = contentLines.join("\n");
250
- await writeFile(file, current, secret);
251
- }
252
- if (lines.length > 0) {
253
- const marker = `# --- ${kind} channel ---`;
254
- if (current.includes(marker)) {
255
- // A marker already present (e.g. a .env copied from .env.example) — slot the new lines under it instead of
256
- // orphaning them at the end of the file.
257
- await writeFile(file, current.replace(marker, `${marker}\n${lines.join("\n")}`), secret);
258
- }
259
- else {
260
- const prefix = current === "" ? "" : current.endsWith("\n") ? "\n" : "\n\n";
261
- await appendFile(file, `${prefix}${marker}\n${lines.join("\n")}\n`, secret);
262
- }
263
- }
264
- return { written, alreadySet, unprotectedSecretsDir };
174
+ export function appendChannelDotEnv(dir, kind, generated, overwrite = [], ingress = "webhook") {
175
+ const entries = channelSetup(kind, ingress).env.map(({ name, hint }) => ({
176
+ name,
177
+ hint,
178
+ ...(generated[name] === undefined ? {} : { value: generated[name] }),
179
+ }));
180
+ return writeValues(dir, `${kind} channel`, entries, overwrite);
265
181
  }
266
182
  /** The path `add <kind>` scaffolds to. */
267
183
  function channelPath(dir, kind) {
268
- return join(dir, "channels", `${kind}.ts`);
184
+ return join(dir, "src", "channels", `${kind}.ts`);
269
185
  }
270
186
  /** Whether a channel file already exists — checked before any mutation, so a no-clobber re-add is side-effect-free. */
271
187
  export async function channelExists(dir, kind) {
272
188
  return exists(channelPath(dir, kind));
273
189
  }
274
- /** Scaffold `channels/<kind>.ts` into {@link dir}. */
275
- export async function scaffoldChannel(dir, kind, options = {}) {
276
- const channelsDir = join(dir, "channels");
277
- // Don't write through a channels/ symlink that escapes the agent dir; one inside it is fine.
278
- await assertInsideAgentDir(dir, "channels");
190
+ /** Scaffold `src/channels/<kind>.ts` into {@link dir}. */
191
+ export async function scaffoldChannel(dir, kind) {
192
+ const channelsDir = join(dir, "src", "channels");
193
+ // Don't write through a src/ symlink that escapes the agent dir; one inside it is fine.
194
+ await assertInsideAgentDir(dir, "src");
279
195
  const file = channelPath(dir, kind);
280
196
  if (await exists(file)) {
281
197
  throw new Error(`${file} already exists — edit it, or remove it to re-scaffold`);
282
198
  }
283
199
  await mkdir(channelsDir, { recursive: true });
284
- // The long-connection variant is its own template FILE, not a transform of the webhook one: a transform
285
- // has to be re-taught every time the template changes shape, and a missed anchor scaffolds a channel
286
- // configured for the wrong ingress with nothing failing.
287
- // Which templates exist is the BUNDLE's fact, asked here rather than assumed from the kind: only
288
- // feishu/lark ship a long-connection variant today, and a caller asking for one that is missing
289
- // must get that sentence, not the ENOENT of a path it never named.
290
- const template = options.ingress === "websocket" ? "channel.websocket.ts" : "channel.ts";
291
- // CHANNEL templates only: the bundle also holds companion tools (`telegram-send.ts`), and listing
292
- // one as an available scaffold sends whoever reads this line looking for an ingress that is a tool.
293
- const available = channelBundleFiles(kind).filter((f) => f.startsWith("channel."));
294
- if (!available.includes(template)) {
295
- throw new Error(`${kind} has no ${options.ingress ?? "webhook"} scaffold (${template} is not in its bundle) — ` +
296
- `available: ${available.join(", ")}`);
297
- }
298
- await writeFile(file, channelTemplate(kind, template), { flag: "wx" });
200
+ await writeFile(file, channelTemplate(kind, "channel.ts"), { flag: "wx" });
299
201
  return file;
300
202
  }
301
- /** The bundle's companion tools (every `.ts` beside `channel.ts` → `tools/<name>`). */
203
+ /** The bundle's companion tools (every `.ts` beside `channel.ts` → `src/tools/<name>`). */
302
204
  export async function scaffoldCompanionTools(dir, kind) {
303
205
  const written = [];
304
206
  for (const name of channelBundleFiles(kind)) {
305
- if (name.startsWith("channel."))
306
- continue; // channel.ts / channel.websocket.ts are the channel, not tools
307
- const file = join(dir, "tools", name);
207
+ if (name === "channel.ts")
208
+ continue; // the channel, not a tool
209
+ const file = join(dir, "src", "tools", name);
308
210
  await mkdir(dirname(file), { recursive: true });
309
211
  await writeFile(file, channelTemplate(kind, name));
310
212
  written.push(file);
@@ -326,7 +228,7 @@ export async function assertChannelReady(dir) {
326
228
  // `dir` is the AGENT dir, so `fastagent init` here would nest a second agent inside it.
327
229
  throw new Error(`${dir}: no package.json — a channel adapter is code and needs the agent's own manifest. ` +
328
230
  `Add a package.json declaring @fastagent-sh/fastagent there (\`init\` scaffolds one), ` +
329
- `or run \`fastagent init\` in the workspace for a fresh agent`);
231
+ `or create a fresh agent with \`fastagent init <dir>\``);
330
232
  }
331
233
  throw e;
332
234
  }
@@ -1,23 +1,22 @@
1
- /** The agent directory name for a raw `--agent-dir` value: the default when unset, and `./bot` read as `bot`. */
2
- export declare function agentDirName(raw: string | undefined): string;
3
- /**
4
- * Why `name` cannot be an agent directory name, or undefined when it can.
5
- *
6
- * `"."` is refused along with every path: the definition always lands in a SUBDIRECTORY, and the directory around it
7
- * is the workspace. The subdirectory also carries its own `package.json`, so a `fastagent.config.ts` never loads under
8
- * the host repo's module settings.
9
- */
10
- export declare function agentDirNameError(name: string): string | undefined;
11
- export interface ScaffoldOptions {
12
- /** The agent directory's name inside `dir` — default {@link DEFAULT_AGENT_DIRNAME}. */
13
- agentDir?: string;
14
- }
15
1
  export interface ScaffoldResult {
2
+ /** The agent directory: `dir` itself. */
16
3
  dir: string;
17
- /** The agent dir relative to `dir`: the {@link ScaffoldOptions.agentDir} that was used. */
18
- agentDir: string;
19
4
  /** Files written by this run (relative to `dir`). */
20
5
  created: string[];
6
+ /** Remove what this run created, for a caller whose next step failed: the same rollback a failed write runs. */
7
+ undo(): Promise<void>;
21
8
  }
22
- /** Scaffold a runnable agent into `<dir>/<agentDir>/` (both created if missing). */
23
- export declare function scaffoldAgent(dir: string, options?: ScaffoldOptions): Promise<ScaffoldResult>;
9
+ /**
10
+ * Scaffold a runnable agent INTO `dir`, which must be new or empty. An agent is a directory of its own: what it works
11
+ * on is declared, never the directory around it.
12
+ */
13
+ export declare function scaffoldAgent(dir: string): Promise<ScaffoldResult>;
14
+ /**
15
+ * Make the new agent a git repository whose first commit is what creating it wrote (`createAgent`, which `init` and a
16
+ * client both run). An agent changes itself, and version control is how its author goes back to a version that worked;
17
+ * the scaffolded `.gitignore` already keeps its instance (`.state`, `.secrets`, `content`) out. Returns a sentence to
18
+ * show: when there is no repository of its own or no first commit, it says which and why. The catches are this
19
+ * step's boundary: the agent is created either way, so a git that is missing or refuses becomes that sentence rather
20
+ * than a failed create.
21
+ */
22
+ export declare function initRepository(dir: string): Promise<string>;
@@ -1,117 +1,78 @@
1
- /** Init: scaffold a runnable fastagent agent, offline. */
1
+ /** Init: scaffold a runnable fastagent agent, offline, and make it a git repository of its own. */
2
2
  import { lstat, mkdir, readdir, rm, rmdir, writeFile } from "node:fs/promises";
3
- import { basename, dirname, join, resolve, sep } from "node:path";
4
- import { AGENT_CONFIG_FILE, DEFAULT_AGENT_DIRNAME, SECRETS_DIRNAME, agentDefinitionOwner, agentsAt, displayPath, exists, } from "../paths.js";
3
+ import { dirname, join, sep } from "node:path";
4
+ import { AGENT_CONFIG_FILE, SECRETS_DIRNAME, displayPath, enclosingAgentDir } from "../paths.js";
5
5
  import { baseTemplate, packageJson, toPackageName } from "./templates.js";
6
- import { fastagentVersion } from "../version.js";
7
- /** The agent directory name for a raw `--agent-dir` value: the default when unset, and `./bot` read as `bot`. */
8
- export function agentDirName(raw) {
9
- if (raw === undefined)
10
- return DEFAULT_AGENT_DIRNAME;
11
- const trimmed = raw.replace(/^\.[/\\]/, "");
12
- return trimmed === "" ? raw : trimmed;
13
- }
6
+ import { fastagentBuild } from "../version.js";
7
+ import { GitNotInstalled, gitFor } from "../git.js";
8
+ /** Entries a directory may hold and still count as empty. */
9
+ const IGNORABLE = [".DS_Store", ".gitkeep", ".keep"];
14
10
  /**
15
- * Why `name` cannot be an agent directory name, or undefined when it can.
16
- *
17
- * `"."` is refused along with every path: the definition always lands in a SUBDIRECTORY, and the directory around it
18
- * is the workspace. The subdirectory also carries its own `package.json`, so a `fastagent.config.ts` never loads under
19
- * the host repo's module settings.
11
+ * Scaffold a runnable agent INTO `dir`, which must be new or empty. An agent is a directory of its own: what it works
12
+ * on is declared, never the directory around it.
20
13
  */
21
- export function agentDirNameError(name) {
22
- if (name !== "" && name !== "." && name !== ".." && name === basename(name))
23
- return undefined;
24
- return (`must be a single directory name — the definition lives in a subdirectory, and the directory around it is ` +
25
- `the workspace the agent works on (a path, or ".", would put it elsewhere)`);
26
- }
27
- /** Scaffold a runnable agent into `<dir>/<agentDir>/` (both created if missing). */
28
- export async function scaffoldAgent(dir, options = {}) {
29
- const root = agentDirName(options.agentDir);
30
- const invalid = agentDirNameError(root);
31
- if (invalid)
32
- throw new Error(`agentDir "${root}" ${invalid}`);
14
+ export async function scaffoldAgent(dir) {
33
15
  const skill = (name) => ({
34
- rel: join(root, "skills", "writing-great-skills", name),
16
+ rel: join("skills", "writing-great-skills", name),
35
17
  content: baseTemplate(`skills/writing-great-skills/${name}`),
36
18
  });
37
19
  const files = [
38
- // Standing instructions, added to pi's default prompt (SYSTEM.md would replace it).
39
- { rel: join(root, "APPEND_SYSTEM.md"), content: baseTemplate("APPEND_SYSTEM.md") },
20
+ // Standing instructions: the agent's system prompt.
21
+ { rel: "SYSTEM.md", content: baseTemplate("SYSTEM.md") },
40
22
  // The example skill: how to author skills well — the core of self-iteration.
41
23
  skill("SKILL.md"),
42
24
  skill("GLOSSARY.md"),
43
25
  skill("LICENSE"),
44
- { rel: join(root, AGENT_CONFIG_FILE), content: baseTemplate(AGENT_CONFIG_FILE) },
26
+ { rel: AGENT_CONFIG_FILE, content: baseTemplate(AGENT_CONFIG_FILE) },
45
27
  // Two ignore files, scaffolded ONCE and owned by the author from then on — no command rewrites, reads or verifies
46
28
  // them.
47
- { rel: join(root, ".gitignore"), content: baseTemplate("gitignore") },
48
- { rel: join(root, SECRETS_DIRNAME, ".gitignore"), content: baseTemplate("secrets.gitignore") },
49
- { rel: join(root, SECRETS_DIRNAME, ".env.example"), content: baseTemplate("env.example") },
50
- { rel: join(root, "tools", "fetch-url.ts"), content: baseTemplate("tools/fetch-url.ts") },
51
- // The agent's own manifest, named after the directory it serves. Its `type: module` is why the definition gets a
52
- // subdirectory of its own: a config file under a host repo's CommonJS manifest does not load.
53
- {
54
- rel: join(root, "package.json"),
55
- content: packageJson(`${toPackageName(dir)}-agent`, await fastagentVersion()),
56
- },
29
+ { rel: ".gitignore", content: baseTemplate("gitignore") },
30
+ { rel: join(SECRETS_DIRNAME, ".gitignore"), content: baseTemplate("secrets.gitignore") },
31
+ { rel: join(SECRETS_DIRNAME, ".env.example"), content: baseTemplate("env.example") },
32
+ // The agent's own manifest, named after its directory.
33
+ { rel: "package.json", content: packageJson(toPackageName(dir), await fastagentBuild()) },
57
34
  ];
58
- // Inside another agent's DEFINITION (its `skills/`, `tools/`, `channels/` or `routines/`): the outer agent would
59
- // load the new one as its own content.
60
- const owner = agentDefinitionOwner(dir);
35
+ const shown = displayPath(process.cwd(), dir) ?? dir;
36
+ // Inside another agent: that agent's directory is its own definition and working directory, so the new one would be
37
+ // read as part of it.
38
+ const owner = enclosingAgentDir(dir);
61
39
  if (owner) {
62
- throw new Error(`"${dir}" is inside the definition of the agent at ${owner} — an agent scaffolded here would be ` +
63
- `part of THAT agent's surface, not one of its own. Init outside it.`);
64
- }
65
- // Preflight scaffold parent dirs FIRST: a pre-existing non-directory there would make mkdir fail mid-loop AFTER the
66
- // first write, leaving a half-scaffold.
67
- const parents = new Set();
68
- for (const file of files) {
69
- let p = dirname(file.rel);
70
- while (p !== "." && p !== "") {
71
- parents.add(p);
72
- p = dirname(p);
73
- }
74
- }
75
- for (const rel of parents) {
76
- const st = await lstat(join(dir, rel)).catch(() => undefined);
77
- if (st && !st.isDirectory()) {
78
- throw new Error(`cannot scaffold: "${rel}" exists and is not a directory (a regular file or symlink) — remove it, or init elsewhere`);
79
- }
40
+ throw new Error(`"${shown}" is inside the agent ${owner} — an agent is a directory of its own; create it outside that one`);
80
41
  }
81
- // Refuse an occupied agent dir.
82
- const occupants = (await readdir(join(dir, root)).catch((e) => {
42
+ const st = await lstat(dir).catch((e) => {
83
43
  if (e.code === "ENOENT")
84
- return [];
44
+ return undefined;
85
45
  throw e;
86
- })).filter((f) => ![".DS_Store", ".gitkeep", ".keep"].includes(f));
87
- // Could `dir` still SELECT the agent this run creates?
88
- const existing = agentsAt(dir).filter((a) => a !== resolve(dir, root));
89
- const shadowed = existing.filter((a) => a === resolve(dir)); // an agent AT `dir` hides the new one inside it
90
- if (shadowed.length > 0) {
91
- throw new Error(`"${dir}" already resolves to ${shadowed.map((a) => displayPath(process.cwd(), a) ?? a).join(", ")} — ` +
92
- `an agent scaffolded in ./${root}/ would be hidden by it and never served ` +
93
- `from "${dir}" (an agent AT a directory wins over any inside it). Use that agent, move it away, ` +
94
- `or init in a different directory.`);
95
- }
96
- // ONE coordinate system for both refusals — `displayPath` is the shared policy (relative inside the cwd, absolute
97
- // when it climbs out).
98
- const target = displayPath(process.cwd(), join(dir, root)) ?? join(dir, root);
46
+ });
47
+ if (st && !st.isDirectory())
48
+ throw new Error(`"${shown}" exists and is not a directory`);
49
+ const occupants = st ? (await readdir(dir)).filter((name) => !IGNORABLE.includes(name)) : [];
99
50
  if (occupants.includes(AGENT_CONFIG_FILE)) {
100
- throw new Error(`"${target}" already has ${AGENT_CONFIG_FILE} — already a fastagent agent`);
51
+ throw new Error(`"${shown}" already has ${AGENT_CONFIG_FILE} — already a fastagent agent`);
101
52
  }
102
53
  if (occupants.length > 0) {
103
- throw new Error(`"${target}" already holds ${occupants.join(", ")} — move it away first, or run ` +
104
- `\`fastagent init\` in a different directory`);
54
+ throw new Error(`"${shown}" is not empty (it holds ${occupants.join(", ")}) — an agent lives in a directory of its own; to ` +
55
+ `have one work on this directory: \`fastagent init <new directory> --content ${displayPath(process.cwd(), dir) ?? "."}\``);
56
+ }
57
+ const parents = new Set();
58
+ for (const file of files) {
59
+ for (let p = dirname(file.rel); p !== "." && p !== ""; p = dirname(p))
60
+ parents.add(p);
105
61
  }
106
- // Which directories were OURS to create?
107
- const preexisting = new Set();
108
- for (const rel of parents)
109
- if (await exists(join(dir, rel)))
110
- preexisting.add(rel);
111
- const agentDirExisted = await exists(join(dir, root));
62
+ const dirExisted = st !== undefined;
112
63
  await mkdir(dir, { recursive: true });
113
64
  const created = [];
114
- // ONE rollback scope: any failure removes what THIS run created — files AND the directories it made for them.
65
+ // ONE rollback: it removes what THIS run created — files AND the directories it made for them — and leaves a
66
+ // directory the caller handed over empty. Best-effort, because it runs while another failure is being reported.
67
+ const undo = async () => {
68
+ for (const rel of [...created].reverse())
69
+ await rm(join(dir, rel), { force: true }).catch(() => { });
70
+ for (const rel of [...parents].sort((a, b) => b.split(sep).length - a.split(sep).length)) {
71
+ await rmdir(join(dir, rel)).catch(() => { });
72
+ }
73
+ if (!dirExisted)
74
+ await rmdir(dir).catch(() => { });
75
+ };
115
76
  // `wx` so a collision is a failure: the target was checked empty above, so anything here appeared mid-run.
116
77
  try {
117
78
  for (const file of files) {
@@ -122,16 +83,43 @@ export async function scaffoldAgent(dir, options = {}) {
122
83
  }
123
84
  }
124
85
  catch (error) {
125
- // Best-effort rollback of a partial scaffold.
126
- for (const rel of created.reverse())
127
- await rm(join(dir, rel), { force: true }).catch(() => { });
128
- for (const rel of [...parents].sort((a, b) => b.split(sep).length - a.split(sep).length)) {
129
- if (rel !== root && !preexisting.has(rel))
130
- await rmdir(join(dir, rel)).catch(() => { });
131
- }
132
- if (!agentDirExisted)
133
- await rmdir(join(dir, root)).catch(() => { });
86
+ await undo();
134
87
  throw error;
135
88
  }
136
- return { dir, agentDir: root, created };
89
+ return { dir, created, undo };
90
+ }
91
+ const git = gitFor("no repository was created");
92
+ /**
93
+ * Make the new agent a git repository whose first commit is what creating it wrote (`createAgent`, which `init` and a
94
+ * client both run). An agent changes itself, and version control is how its author goes back to a version that worked;
95
+ * the scaffolded `.gitignore` already keeps its instance (`.state`, `.secrets`, `content`) out. Returns a sentence to
96
+ * show: when there is no repository of its own or no first commit, it says which and why. The catches are this
97
+ * step's boundary: the agent is created either way, so a git that is missing or refuses becomes that sentence rather
98
+ * than a failed create.
99
+ */
100
+ export async function initRepository(dir) {
101
+ const reason = (error) => error.message.split("\n")[0];
102
+ try {
103
+ // A directory another repository TRACKS is that repository's: a second one inside it would hide the agent's files
104
+ // from the first. One it ignores (a home directory kept in git with `*` ignored) tracks nothing, so the agent gets
105
+ // its own.
106
+ const enclosing = git.answer(["rev-parse", "--show-toplevel"], dir);
107
+ if (enclosing !== undefined && git.answer(["check-ignore", "-q", "."], dir) === undefined) {
108
+ return `inside the git repository ${enclosing}: it tracks the agent, so none was created`;
109
+ }
110
+ await git.run(["init", "--quiet"], dir);
111
+ await git.run(["add", "--all"], dir);
112
+ }
113
+ catch (error) {
114
+ if (error instanceof GitNotInstalled)
115
+ return error.message;
116
+ return `git could not create a repository: ${reason(error)}`;
117
+ }
118
+ try {
119
+ await git.run(["commit", "--quiet", "--message", "Create the agent with fastagent"], dir);
120
+ }
121
+ catch (error) {
122
+ return `created a git repository, but its first commit failed (${reason(error)}): commit the scaffold yourself`;
123
+ }
124
+ return "created a git repository, with the scaffold as its first commit";
137
125
  }