@cursor/july 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (623) hide show
  1. package/AGENTS.md +290 -0
  2. package/LICENSE.md +3 -0
  3. package/README.md +1233 -0
  4. package/dist/ab.d.ts +290 -0
  5. package/dist/ab.d.ts.map +1 -0
  6. package/dist/ab.js +386 -0
  7. package/dist/bin/agent-serve.d.ts +15 -0
  8. package/dist/bin/agent-serve.d.ts.map +1 -0
  9. package/dist/bin/agent-serve.js +754 -0
  10. package/dist/channels/github/api.d.ts +101 -0
  11. package/dist/channels/github/api.d.ts.map +1 -0
  12. package/dist/channels/github/api.js +229 -0
  13. package/dist/channels/github/auth.d.ts +67 -0
  14. package/dist/channels/github/auth.d.ts.map +1 -0
  15. package/dist/channels/github/auth.js +239 -0
  16. package/dist/channels/github/binding.d.ts +73 -0
  17. package/dist/channels/github/binding.d.ts.map +1 -0
  18. package/dist/channels/github/binding.js +114 -0
  19. package/dist/channels/github/defaults.d.ts +24 -0
  20. package/dist/channels/github/defaults.d.ts.map +1 -0
  21. package/dist/channels/github/defaults.js +100 -0
  22. package/dist/channels/github/events.d.ts +18 -0
  23. package/dist/channels/github/events.d.ts.map +1 -0
  24. package/dist/channels/github/events.js +59 -0
  25. package/dist/channels/github/forward.d.ts +137 -0
  26. package/dist/channels/github/forward.d.ts.map +1 -0
  27. package/dist/channels/github/forward.js +271 -0
  28. package/dist/channels/github/github-channel.d.ts +127 -0
  29. package/dist/channels/github/github-channel.d.ts.map +1 -0
  30. package/dist/channels/github/github-channel.js +1688 -0
  31. package/dist/channels/github/index.d.ts +52 -0
  32. package/dist/channels/github/index.d.ts.map +1 -0
  33. package/dist/channels/github/index.js +51 -0
  34. package/dist/channels/github/replay.d.ts +115 -0
  35. package/dist/channels/github/replay.d.ts.map +1 -0
  36. package/dist/channels/github/replay.js +282 -0
  37. package/dist/channels/github/state.d.ts +26 -0
  38. package/dist/channels/github/state.d.ts.map +1 -0
  39. package/dist/channels/github/state.js +50 -0
  40. package/dist/channels/github/types.d.ts +385 -0
  41. package/dist/channels/github/types.d.ts.map +1 -0
  42. package/dist/channels/github/types.js +1 -0
  43. package/dist/channels/slack/api.d.ts +117 -0
  44. package/dist/channels/slack/api.d.ts.map +1 -0
  45. package/dist/channels/slack/api.js +437 -0
  46. package/dist/channels/slack/approvals.d.ts +32 -0
  47. package/dist/channels/slack/approvals.d.ts.map +1 -0
  48. package/dist/channels/slack/approvals.js +157 -0
  49. package/dist/channels/slack/assistant.d.ts +23 -0
  50. package/dist/channels/slack/assistant.d.ts.map +1 -0
  51. package/dist/channels/slack/assistant.js +67 -0
  52. package/dist/channels/slack/auth.d.ts +40 -0
  53. package/dist/channels/slack/auth.d.ts.map +1 -0
  54. package/dist/channels/slack/auth.js +71 -0
  55. package/dist/channels/slack/channel-watch.d.ts +46 -0
  56. package/dist/channels/slack/channel-watch.d.ts.map +1 -0
  57. package/dist/channels/slack/channel-watch.js +180 -0
  58. package/dist/channels/slack/constants.d.ts +17 -0
  59. package/dist/channels/slack/constants.d.ts.map +1 -0
  60. package/dist/channels/slack/constants.js +27 -0
  61. package/dist/channels/slack/credentials.d.ts +35 -0
  62. package/dist/channels/slack/credentials.d.ts.map +1 -0
  63. package/dist/channels/slack/credentials.js +88 -0
  64. package/dist/channels/slack/cursor-account.d.ts +76 -0
  65. package/dist/channels/slack/cursor-account.d.ts.map +1 -0
  66. package/dist/channels/slack/cursor-account.js +88 -0
  67. package/dist/channels/slack/defaults.d.ts +32 -0
  68. package/dist/channels/slack/defaults.d.ts.map +1 -0
  69. package/dist/channels/slack/defaults.js +380 -0
  70. package/dist/channels/slack/dispatch.d.ts +43 -0
  71. package/dist/channels/slack/dispatch.d.ts.map +1 -0
  72. package/dist/channels/slack/dispatch.js +185 -0
  73. package/dist/channels/slack/doctor.d.ts +24 -0
  74. package/dist/channels/slack/doctor.d.ts.map +1 -0
  75. package/dist/channels/slack/doctor.js +115 -0
  76. package/dist/channels/slack/event-id-dedupe.d.ts +10 -0
  77. package/dist/channels/slack/event-id-dedupe.d.ts.map +1 -0
  78. package/dist/channels/slack/event-id-dedupe.js +23 -0
  79. package/dist/channels/slack/inbound.d.ts +41 -0
  80. package/dist/channels/slack/inbound.d.ts.map +1 -0
  81. package/dist/channels/slack/inbound.js +166 -0
  82. package/dist/channels/slack/index.d.ts +45 -0
  83. package/dist/channels/slack/index.d.ts.map +1 -0
  84. package/dist/channels/slack/index.js +44 -0
  85. package/dist/channels/slack/init.d.ts +32 -0
  86. package/dist/channels/slack/init.d.ts.map +1 -0
  87. package/dist/channels/slack/init.js +82 -0
  88. package/dist/channels/slack/interactive.d.ts +19 -0
  89. package/dist/channels/slack/interactive.d.ts.map +1 -0
  90. package/dist/channels/slack/interactive.js +142 -0
  91. package/dist/channels/slack/links.d.ts +10 -0
  92. package/dist/channels/slack/links.d.ts.map +1 -0
  93. package/dist/channels/slack/links.js +19 -0
  94. package/dist/channels/slack/live-delivery.d.ts +75 -0
  95. package/dist/channels/slack/live-delivery.d.ts.map +1 -0
  96. package/dist/channels/slack/live-delivery.js +143 -0
  97. package/dist/channels/slack/log.d.ts +25 -0
  98. package/dist/channels/slack/log.d.ts.map +1 -0
  99. package/dist/channels/slack/log.js +47 -0
  100. package/dist/channels/slack/manifest.d.ts +57 -0
  101. package/dist/channels/slack/manifest.d.ts.map +1 -0
  102. package/dist/channels/slack/manifest.js +110 -0
  103. package/dist/channels/slack/pr-ref.d.ts +18 -0
  104. package/dist/channels/slack/pr-ref.d.ts.map +1 -0
  105. package/dist/channels/slack/pr-ref.js +36 -0
  106. package/dist/channels/slack/setup.d.ts +14 -0
  107. package/dist/channels/slack/setup.d.ts.map +1 -0
  108. package/dist/channels/slack/setup.js +86 -0
  109. package/dist/channels/slack/slack-channel.d.ts +15 -0
  110. package/dist/channels/slack/slack-channel.d.ts.map +1 -0
  111. package/dist/channels/slack/slack-channel.js +281 -0
  112. package/dist/channels/slack/socket-mode.d.ts +26 -0
  113. package/dist/channels/slack/socket-mode.d.ts.map +1 -0
  114. package/dist/channels/slack/socket-mode.js +116 -0
  115. package/dist/channels/slack/stream.d.ts +26 -0
  116. package/dist/channels/slack/stream.d.ts.map +1 -0
  117. package/dist/channels/slack/stream.js +39 -0
  118. package/dist/channels/slack/tool-approval-events.d.ts +33 -0
  119. package/dist/channels/slack/tool-approval-events.d.ts.map +1 -0
  120. package/dist/channels/slack/tool-approval-events.js +129 -0
  121. package/dist/channels/slack/types.d.ts +326 -0
  122. package/dist/channels/slack/types.d.ts.map +1 -0
  123. package/dist/channels/slack/types.js +4 -0
  124. package/dist/channels.d.ts +116 -0
  125. package/dist/channels.d.ts.map +1 -0
  126. package/dist/channels.js +294 -0
  127. package/dist/connections.d.ts +75 -0
  128. package/dist/connections.d.ts.map +1 -0
  129. package/dist/connections.js +75 -0
  130. package/dist/docs/404.html +22 -0
  131. package/dist/docs/ab.html +87 -0
  132. package/dist/docs/assets/ab.md.COdXkces.js +63 -0
  133. package/dist/docs/assets/ab.md.COdXkces.lean.js +1 -0
  134. package/dist/docs/assets/app.DqfFEmJd.js +1 -0
  135. package/dist/docs/assets/building-with-agents.md.CJCtZCyi.js +13 -0
  136. package/dist/docs/assets/building-with-agents.md.CJCtZCyi.lean.js +1 -0
  137. package/dist/docs/assets/chunks/@localSearchIndexroot.CcVk1uKq.js +1 -0
  138. package/dist/docs/assets/chunks/VPLocalSearchBox.BaLEdS15.js +9 -0
  139. package/dist/docs/assets/chunks/framework.CAZyNGu9.js +19 -0
  140. package/dist/docs/assets/chunks/theme.CZRvu_0q.js +2 -0
  141. package/dist/docs/assets/concepts.md.Cfb9b-k1.js +4 -0
  142. package/dist/docs/assets/concepts.md.Cfb9b-k1.lean.js +1 -0
  143. package/dist/docs/assets/deployment.md.Dx1TYNk5.js +14 -0
  144. package/dist/docs/assets/deployment.md.Dx1TYNk5.lean.js +1 -0
  145. package/dist/docs/assets/evals.md.DPZ_MAnI.js +85 -0
  146. package/dist/docs/assets/evals.md.DPZ_MAnI.lean.js +1 -0
  147. package/dist/docs/assets/guides_agent-to-agent.md.CrtrsySy.js +10 -0
  148. package/dist/docs/assets/guides_agent-to-agent.md.CrtrsySy.lean.js +1 -0
  149. package/dist/docs/assets/guides_cloud-runtime.md.CYlNMTNp.js +9 -0
  150. package/dist/docs/assets/guides_cloud-runtime.md.CYlNMTNp.lean.js +1 -0
  151. package/dist/docs/assets/guides_github.md.DwbKhCeS.js +20 -0
  152. package/dist/docs/assets/guides_github.md.DwbKhCeS.lean.js +1 -0
  153. package/dist/docs/assets/guides_human-in-the-loop.md.Dvuctx7s.js +28 -0
  154. package/dist/docs/assets/guides_human-in-the-loop.md.Dvuctx7s.lean.js +1 -0
  155. package/dist/docs/assets/guides_slack.md.bv41fHfW.js +37 -0
  156. package/dist/docs/assets/guides_slack.md.bv41fHfW.lean.js +1 -0
  157. package/dist/docs/assets/guides_webhooks.md.hFTik3lf.js +50 -0
  158. package/dist/docs/assets/guides_webhooks.md.hFTik3lf.lean.js +1 -0
  159. package/dist/docs/assets/hillclimbing.md.DHNast08.js +4 -0
  160. package/dist/docs/assets/hillclimbing.md.DHNast08.lean.js +1 -0
  161. package/dist/docs/assets/index.md.BPKcj5AI.js +20 -0
  162. package/dist/docs/assets/index.md.BPKcj5AI.lean.js +1 -0
  163. package/dist/docs/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 +0 -0
  164. package/dist/docs/assets/inter-italic-cyrillic.By2_1cv3.woff2 +0 -0
  165. package/dist/docs/assets/inter-italic-greek-ext.1u6EdAuj.woff2 +0 -0
  166. package/dist/docs/assets/inter-italic-greek.DJ8dCoTZ.woff2 +0 -0
  167. package/dist/docs/assets/inter-italic-latin-ext.CN1xVJS-.woff2 +0 -0
  168. package/dist/docs/assets/inter-italic-latin.C2AdPX0b.woff2 +0 -0
  169. package/dist/docs/assets/inter-italic-vietnamese.BSbpV94h.woff2 +0 -0
  170. package/dist/docs/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 +0 -0
  171. package/dist/docs/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 +0 -0
  172. package/dist/docs/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 +0 -0
  173. package/dist/docs/assets/inter-roman-greek.BBVDIX6e.woff2 +0 -0
  174. package/dist/docs/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 +0 -0
  175. package/dist/docs/assets/inter-roman-latin.Di8DUHzh.woff2 +0 -0
  176. package/dist/docs/assets/inter-roman-vietnamese.BjW4sHH5.woff2 +0 -0
  177. package/dist/docs/assets/quickstart.md.tVPiGK_L.js +35 -0
  178. package/dist/docs/assets/quickstart.md.tVPiGK_L.lean.js +1 -0
  179. package/dist/docs/assets/reference_agent-config.md.Bpd7HQwf.js +34 -0
  180. package/dist/docs/assets/reference_agent-config.md.Bpd7HQwf.lean.js +1 -0
  181. package/dist/docs/assets/reference_channels.md.D7JTR03W.js +53 -0
  182. package/dist/docs/assets/reference_channels.md.D7JTR03W.lean.js +1 -0
  183. package/dist/docs/assets/reference_cli.md.DA730zCu.js +33 -0
  184. package/dist/docs/assets/reference_cli.md.DA730zCu.lean.js +1 -0
  185. package/dist/docs/assets/reference_connections.md.C3vNH_DE.js +26 -0
  186. package/dist/docs/assets/reference_connections.md.C3vNH_DE.lean.js +1 -0
  187. package/dist/docs/assets/reference_hooks.md.BCEc3MyM.js +12 -0
  188. package/dist/docs/assets/reference_hooks.md.BCEc3MyM.lean.js +1 -0
  189. package/dist/docs/assets/reference_http-api.md.DBAahtdz.js +11 -0
  190. package/dist/docs/assets/reference_http-api.md.DBAahtdz.lean.js +1 -0
  191. package/dist/docs/assets/reference_instructions.md.BC05LEQ8.js +12 -0
  192. package/dist/docs/assets/reference_instructions.md.BC05LEQ8.lean.js +1 -0
  193. package/dist/docs/assets/reference_playground.md.CPZhfYaO.js +1 -0
  194. package/dist/docs/assets/reference_playground.md.CPZhfYaO.lean.js +1 -0
  195. package/dist/docs/assets/reference_project-layout.md.Bd_CKtNS.js +31 -0
  196. package/dist/docs/assets/reference_project-layout.md.Bd_CKtNS.lean.js +1 -0
  197. package/dist/docs/assets/reference_schedules.md.D7qijxLk.js +82 -0
  198. package/dist/docs/assets/reference_schedules.md.D7qijxLk.lean.js +1 -0
  199. package/dist/docs/assets/reference_sessions.md.DdjWabnN.js +8 -0
  200. package/dist/docs/assets/reference_sessions.md.DdjWabnN.lean.js +1 -0
  201. package/dist/docs/assets/reference_skills.md.VQnlBT3Q.js +15 -0
  202. package/dist/docs/assets/reference_skills.md.VQnlBT3Q.lean.js +1 -0
  203. package/dist/docs/assets/reference_subagents.md.CIRAVcPK.js +10 -0
  204. package/dist/docs/assets/reference_subagents.md.CIRAVcPK.lean.js +1 -0
  205. package/dist/docs/assets/reference_tools.md.DF5kwlt0.js +46 -0
  206. package/dist/docs/assets/reference_tools.md.DF5kwlt0.lean.js +1 -0
  207. package/dist/docs/assets/scaffolding-agents.md.CyYfWGdc.js +1 -0
  208. package/dist/docs/assets/scaffolding-agents.md.CyYfWGdc.lean.js +1 -0
  209. package/dist/docs/assets/style.Bj-IXa5W.css +1 -0
  210. package/dist/docs/assets/troubleshooting.md.DkovjS7h.js +1 -0
  211. package/dist/docs/assets/troubleshooting.md.DkovjS7h.lean.js +1 -0
  212. package/dist/docs/building-with-agents.html +37 -0
  213. package/dist/docs/concepts.html +28 -0
  214. package/dist/docs/deployment.html +38 -0
  215. package/dist/docs/evals.html +109 -0
  216. package/dist/docs/guides/agent-to-agent.html +34 -0
  217. package/dist/docs/guides/cloud-runtime.html +33 -0
  218. package/dist/docs/guides/github.html +44 -0
  219. package/dist/docs/guides/human-in-the-loop.html +52 -0
  220. package/dist/docs/guides/slack.html +61 -0
  221. package/dist/docs/guides/webhooks.html +74 -0
  222. package/dist/docs/hashmap.json +1 -0
  223. package/dist/docs/hillclimbing.html +28 -0
  224. package/dist/docs/index.html +44 -0
  225. package/dist/docs/quickstart.html +59 -0
  226. package/dist/docs/reference/agent-config.html +58 -0
  227. package/dist/docs/reference/channels.html +77 -0
  228. package/dist/docs/reference/cli.html +57 -0
  229. package/dist/docs/reference/connections.html +50 -0
  230. package/dist/docs/reference/hooks.html +36 -0
  231. package/dist/docs/reference/http-api.html +35 -0
  232. package/dist/docs/reference/instructions.html +36 -0
  233. package/dist/docs/reference/playground.html +25 -0
  234. package/dist/docs/reference/project-layout.html +55 -0
  235. package/dist/docs/reference/schedules.html +106 -0
  236. package/dist/docs/reference/sessions.html +32 -0
  237. package/dist/docs/reference/skills.html +39 -0
  238. package/dist/docs/reference/subagents.html +34 -0
  239. package/dist/docs/reference/tools.html +70 -0
  240. package/dist/docs/scaffolding-agents.html +25 -0
  241. package/dist/docs/troubleshooting.html +25 -0
  242. package/dist/docs/vp-icons.css +0 -0
  243. package/dist/evals.d.ts +312 -0
  244. package/dist/evals.d.ts.map +1 -0
  245. package/dist/evals.js +254 -0
  246. package/dist/hooks.d.ts +23 -0
  247. package/dist/hooks.d.ts.map +1 -0
  248. package/dist/hooks.js +23 -0
  249. package/dist/index.d.ts +71 -0
  250. package/dist/index.d.ts.map +1 -0
  251. package/dist/index.js +101 -0
  252. package/dist/internal/ab-collector.d.ts +42 -0
  253. package/dist/internal/ab-collector.d.ts.map +1 -0
  254. package/dist/internal/ab-collector.js +153 -0
  255. package/dist/internal/ab-fold.d.ts +36 -0
  256. package/dist/internal/ab-fold.d.ts.map +1 -0
  257. package/dist/internal/ab-fold.js +171 -0
  258. package/dist/internal/ab-snapshot.d.ts +70 -0
  259. package/dist/internal/ab-snapshot.d.ts.map +1 -0
  260. package/dist/internal/ab-snapshot.js +208 -0
  261. package/dist/internal/approval-gate.d.ts +34 -0
  262. package/dist/internal/approval-gate.d.ts.map +1 -0
  263. package/dist/internal/approval-gate.js +85 -0
  264. package/dist/internal/auth.d.ts +8 -0
  265. package/dist/internal/auth.d.ts.map +1 -0
  266. package/dist/internal/auth.js +27 -0
  267. package/dist/internal/authored-alias-hooks.d.ts +31 -0
  268. package/dist/internal/authored-alias-hooks.d.ts.map +1 -0
  269. package/dist/internal/authored-alias-hooks.js +43 -0
  270. package/dist/internal/authored-loaders.d.ts +25 -0
  271. package/dist/internal/authored-loaders.d.ts.map +1 -0
  272. package/dist/internal/authored-loaders.js +129 -0
  273. package/dist/internal/bounded-int.d.ts +11 -0
  274. package/dist/internal/bounded-int.d.ts.map +1 -0
  275. package/dist/internal/bounded-int.js +13 -0
  276. package/dist/internal/brand.d.ts +6 -0
  277. package/dist/internal/brand.d.ts.map +1 -0
  278. package/dist/internal/brand.js +17 -0
  279. package/dist/internal/chat-client.d.ts +19 -0
  280. package/dist/internal/chat-client.d.ts.map +1 -0
  281. package/dist/internal/chat-client.js +327 -0
  282. package/dist/internal/cli-ax.d.ts +58 -0
  283. package/dist/internal/cli-ax.d.ts.map +1 -0
  284. package/dist/internal/cli-ax.js +345 -0
  285. package/dist/internal/cli-cursor.d.ts +20 -0
  286. package/dist/internal/cli-cursor.d.ts.map +1 -0
  287. package/dist/internal/cli-cursor.js +227 -0
  288. package/dist/internal/cli-github.d.ts +47 -0
  289. package/dist/internal/cli-github.d.ts.map +1 -0
  290. package/dist/internal/cli-github.js +644 -0
  291. package/dist/internal/cli-slack.d.ts +18 -0
  292. package/dist/internal/cli-slack.d.ts.map +1 -0
  293. package/dist/internal/cli-slack.js +144 -0
  294. package/dist/internal/cloud-merge.d.ts +23 -0
  295. package/dist/internal/cloud-merge.d.ts.map +1 -0
  296. package/dist/internal/cloud-merge.js +28 -0
  297. package/dist/internal/cron.d.ts +17 -0
  298. package/dist/internal/cron.d.ts.map +1 -0
  299. package/dist/internal/cron.js +60 -0
  300. package/dist/internal/cursor/account-mcp.d.ts +95 -0
  301. package/dist/internal/cursor/account-mcp.d.ts.map +1 -0
  302. package/dist/internal/cursor/account-mcp.js +342 -0
  303. package/dist/internal/cursor/backend-client.d.ts +118 -0
  304. package/dist/internal/cursor/backend-client.d.ts.map +1 -0
  305. package/dist/internal/cursor/backend-client.js +477 -0
  306. package/dist/internal/cursor/credentials.d.ts +112 -0
  307. package/dist/internal/cursor/credentials.d.ts.map +1 -0
  308. package/dist/internal/cursor/credentials.js +329 -0
  309. package/dist/internal/cursor-event-relay.d.ts +111 -0
  310. package/dist/internal/cursor-event-relay.d.ts.map +1 -0
  311. package/dist/internal/cursor-event-relay.js +181 -0
  312. package/dist/internal/cursor-relay-core.d.ts +113 -0
  313. package/dist/internal/cursor-relay-core.d.ts.map +1 -0
  314. package/dist/internal/cursor-relay-core.js +364 -0
  315. package/dist/internal/cursor-slack-relay.d.ts +94 -0
  316. package/dist/internal/cursor-slack-relay.d.ts.map +1 -0
  317. package/dist/internal/cursor-slack-relay.js +171 -0
  318. package/dist/internal/describe-error.d.ts +3 -0
  319. package/dist/internal/describe-error.d.ts.map +1 -0
  320. package/dist/internal/describe-error.js +4 -0
  321. package/dist/internal/dev-mode.d.ts +13 -0
  322. package/dist/internal/dev-mode.d.ts.map +1 -0
  323. package/dist/internal/dev-mode.js +17 -0
  324. package/dist/internal/discovery.d.ts +55 -0
  325. package/dist/internal/discovery.d.ts.map +1 -0
  326. package/dist/internal/discovery.js +1427 -0
  327. package/dist/internal/distribution.d.ts +47 -0
  328. package/dist/internal/distribution.d.ts.map +1 -0
  329. package/dist/internal/distribution.js +82 -0
  330. package/dist/internal/docs-site.d.ts +28 -0
  331. package/dist/internal/docs-site.d.ts.map +1 -0
  332. package/dist/internal/docs-site.js +169 -0
  333. package/dist/internal/duration.d.ts +8 -0
  334. package/dist/internal/duration.d.ts.map +1 -0
  335. package/dist/internal/duration.js +35 -0
  336. package/dist/internal/eval-run-store.d.ts +81 -0
  337. package/dist/internal/eval-run-store.d.ts.map +1 -0
  338. package/dist/internal/eval-run-store.js +396 -0
  339. package/dist/internal/eval-runner.d.ts +76 -0
  340. package/dist/internal/eval-runner.d.ts.map +1 -0
  341. package/dist/internal/eval-runner.js +336 -0
  342. package/dist/internal/event-log.d.ts +51 -0
  343. package/dist/internal/event-log.d.ts.map +1 -0
  344. package/dist/internal/event-log.js +167 -0
  345. package/dist/internal/event-mapper.d.ts +46 -0
  346. package/dist/internal/event-mapper.d.ts.map +1 -0
  347. package/dist/internal/event-mapper.js +226 -0
  348. package/dist/internal/frontmatter.d.ts +12 -0
  349. package/dist/internal/frontmatter.d.ts.map +1 -0
  350. package/dist/internal/frontmatter.js +61 -0
  351. package/dist/internal/github-fanout.d.ts +32 -0
  352. package/dist/internal/github-fanout.d.ts.map +1 -0
  353. package/dist/internal/github-fanout.js +122 -0
  354. package/dist/internal/handleAgentServeTrigger.d.ts +59 -0
  355. package/dist/internal/handleAgentServeTrigger.d.ts.map +1 -0
  356. package/dist/internal/handleAgentServeTrigger.js +115 -0
  357. package/dist/internal/host-platforms.d.ts +40 -0
  358. package/dist/internal/host-platforms.d.ts.map +1 -0
  359. package/dist/internal/host-platforms.js +113 -0
  360. package/dist/internal/http-channel.d.ts +54 -0
  361. package/dist/internal/http-channel.d.ts.map +1 -0
  362. package/dist/internal/http-channel.js +382 -0
  363. package/dist/internal/ids.d.ts +19 -0
  364. package/dist/internal/ids.d.ts.map +1 -0
  365. package/dist/internal/ids.js +34 -0
  366. package/dist/internal/init-project.d.ts +22 -0
  367. package/dist/internal/init-project.d.ts.map +1 -0
  368. package/dist/internal/init-project.js +125 -0
  369. package/dist/internal/json-dir-store.d.ts +32 -0
  370. package/dist/internal/json-dir-store.d.ts.map +1 -0
  371. package/dist/internal/json-dir-store.js +100 -0
  372. package/dist/internal/json-schema.d.ts +18 -0
  373. package/dist/internal/json-schema.d.ts.map +1 -0
  374. package/dist/internal/json-schema.js +53 -0
  375. package/dist/internal/log-ring.d.ts +23 -0
  376. package/dist/internal/log-ring.d.ts.map +1 -0
  377. package/dist/internal/log-ring.js +42 -0
  378. package/dist/internal/map-pool.d.ts +6 -0
  379. package/dist/internal/map-pool.d.ts.map +1 -0
  380. package/dist/internal/map-pool.js +41 -0
  381. package/dist/internal/mcp-endpoint.d.ts +59 -0
  382. package/dist/internal/mcp-endpoint.d.ts.map +1 -0
  383. package/dist/internal/mcp-endpoint.js +355 -0
  384. package/dist/internal/mcp-host.d.ts +28 -0
  385. package/dist/internal/mcp-host.d.ts.map +1 -0
  386. package/dist/internal/mcp-host.js +162 -0
  387. package/dist/internal/net.d.ts +5 -0
  388. package/dist/internal/net.d.ts.map +1 -0
  389. package/dist/internal/net.js +30 -0
  390. package/dist/internal/peer-connections.d.ts +44 -0
  391. package/dist/internal/peer-connections.d.ts.map +1 -0
  392. package/dist/internal/peer-connections.js +67 -0
  393. package/dist/internal/playground/static.d.ts +35 -0
  394. package/dist/internal/playground/static.d.ts.map +1 -0
  395. package/dist/internal/playground/static.js +162 -0
  396. package/dist/internal/playground/toolchain.d.ts +57 -0
  397. package/dist/internal/playground/toolchain.d.ts.map +1 -0
  398. package/dist/internal/playground/toolchain.js +226 -0
  399. package/dist/internal/reminder-runner.d.ts +62 -0
  400. package/dist/internal/reminder-runner.d.ts.map +1 -0
  401. package/dist/internal/reminder-runner.js +582 -0
  402. package/dist/internal/reminder-schedule.d.ts +22 -0
  403. package/dist/internal/reminder-schedule.d.ts.map +1 -0
  404. package/dist/internal/reminder-schedule.js +82 -0
  405. package/dist/internal/reminder-store.d.ts +58 -0
  406. package/dist/internal/reminder-store.d.ts.map +1 -0
  407. package/dist/internal/reminder-store.js +106 -0
  408. package/dist/internal/resolved-connections.d.ts +41 -0
  409. package/dist/internal/resolved-connections.d.ts.map +1 -0
  410. package/dist/internal/resolved-connections.js +73 -0
  411. package/dist/internal/review-comments.d.ts +50 -0
  412. package/dist/internal/review-comments.d.ts.map +1 -0
  413. package/dist/internal/review-comments.js +194 -0
  414. package/dist/internal/router.d.ts +33 -0
  415. package/dist/internal/router.d.ts.map +1 -0
  416. package/dist/internal/router.js +93 -0
  417. package/dist/internal/run-client.d.ts +49 -0
  418. package/dist/internal/run-client.d.ts.map +1 -0
  419. package/dist/internal/run-client.js +208 -0
  420. package/dist/internal/schedule-runner.d.ts +31 -0
  421. package/dist/internal/schedule-runner.d.ts.map +1 -0
  422. package/dist/internal/schedule-runner.js +126 -0
  423. package/dist/internal/sdk-runner.d.ts +41 -0
  424. package/dist/internal/sdk-runner.d.ts.map +1 -0
  425. package/dist/internal/sdk-runner.js +221 -0
  426. package/dist/internal/server.d.ts +25 -0
  427. package/dist/internal/server.d.ts.map +1 -0
  428. package/dist/internal/server.js +1374 -0
  429. package/dist/internal/session-engine.d.ts +287 -0
  430. package/dist/internal/session-engine.d.ts.map +1 -0
  431. package/dist/internal/session-engine.js +1377 -0
  432. package/dist/internal/session-store.d.ts +29 -0
  433. package/dist/internal/session-store.d.ts.map +1 -0
  434. package/dist/internal/session-store.js +140 -0
  435. package/dist/internal/session-urls.d.ts +10 -0
  436. package/dist/internal/session-urls.d.ts.map +1 -0
  437. package/dist/internal/session-urls.js +25 -0
  438. package/dist/internal/stream-progress.d.ts +14 -0
  439. package/dist/internal/stream-progress.d.ts.map +1 -0
  440. package/dist/internal/stream-progress.js +195 -0
  441. package/dist/internal/tool-result.d.ts +20 -0
  442. package/dist/internal/tool-result.d.ts.map +1 -0
  443. package/dist/internal/tool-result.js +45 -0
  444. package/dist/internal/trajectory.d.ts +56 -0
  445. package/dist/internal/trajectory.d.ts.map +1 -0
  446. package/dist/internal/trajectory.js +248 -0
  447. package/dist/internal/workspace.d.ts +42 -0
  448. package/dist/internal/workspace.d.ts.map +1 -0
  449. package/dist/internal/workspace.js +199 -0
  450. package/dist/playground/assets/cursor-icons-outline-oY2V_mvK.woff2 +0 -0
  451. package/dist/playground/assets/index-1K-hG-7p.css +1 -0
  452. package/dist/playground/assets/index-FlWjhg3x.js +79 -0
  453. package/dist/playground/index.html +17 -0
  454. package/dist/reminders.d.ts +23 -0
  455. package/dist/reminders.d.ts.map +1 -0
  456. package/dist/reminders.js +22 -0
  457. package/dist/schedules.d.ts +23 -0
  458. package/dist/schedules.d.ts.map +1 -0
  459. package/dist/schedules.js +26 -0
  460. package/dist/skills.d.ts +22 -0
  461. package/dist/skills.d.ts.map +1 -0
  462. package/dist/skills.js +22 -0
  463. package/dist/tools.d.ts +92 -0
  464. package/dist/tools.d.ts.map +1 -0
  465. package/dist/tools.js +68 -0
  466. package/dist/types.d.ts +1609 -0
  467. package/dist/types.d.ts.map +1 -0
  468. package/dist/types.js +80 -0
  469. package/docs/README.md +174 -0
  470. package/docs/ab.md +355 -0
  471. package/docs/building-with-agents.md +129 -0
  472. package/docs/concepts.md +177 -0
  473. package/docs/deployment.md +218 -0
  474. package/docs/evals.md +383 -0
  475. package/docs/guides/agent-to-agent.md +118 -0
  476. package/docs/guides/cloud-runtime.md +122 -0
  477. package/docs/guides/github.md +172 -0
  478. package/docs/guides/human-in-the-loop.md +131 -0
  479. package/docs/guides/slack.md +245 -0
  480. package/docs/guides/webhooks.md +201 -0
  481. package/docs/hillclimbing.md +93 -0
  482. package/docs/quickstart.md +183 -0
  483. package/docs/reference/agent-config.md +154 -0
  484. package/docs/reference/channels.md +206 -0
  485. package/docs/reference/cli.md +257 -0
  486. package/docs/reference/connections.md +137 -0
  487. package/docs/reference/hooks.md +74 -0
  488. package/docs/reference/http-api.md +219 -0
  489. package/docs/reference/instructions.md +77 -0
  490. package/docs/reference/playground.md +67 -0
  491. package/docs/reference/project-layout.md +119 -0
  492. package/docs/reference/schedules.md +220 -0
  493. package/docs/reference/sessions.md +165 -0
  494. package/docs/reference/skills.md +79 -0
  495. package/docs/reference/subagents.md +74 -0
  496. package/docs/reference/tools.md +203 -0
  497. package/docs/scaffolding-agents.md +113 -0
  498. package/docs/troubleshooting.md +95 -0
  499. package/package.json +185 -0
  500. package/skills/ab/SKILL.md +139 -0
  501. package/skills/create-agent/SKILL.md +219 -0
  502. package/skills/debug/SKILL.md +71 -0
  503. package/skills/evals/SKILL.md +217 -0
  504. package/skills/framework-map/SKILL.md +142 -0
  505. package/skills/github/SKILL.md +144 -0
  506. package/skills/hillclimb/SKILL.md +162 -0
  507. package/skills/setup-slack/SKILL.md +180 -0
  508. package/src/ab.ts +675 -0
  509. package/src/bin/agent-serve.ts +949 -0
  510. package/src/channels/github/api.ts +374 -0
  511. package/src/channels/github/auth.ts +275 -0
  512. package/src/channels/github/binding.ts +223 -0
  513. package/src/channels/github/defaults.ts +111 -0
  514. package/src/channels/github/events.ts +72 -0
  515. package/src/channels/github/forward.ts +381 -0
  516. package/src/channels/github/github-channel.ts +2270 -0
  517. package/src/channels/github/index.ts +52 -0
  518. package/src/channels/github/replay.ts +387 -0
  519. package/src/channels/github/state.ts +95 -0
  520. package/src/channels/github/types.ts +490 -0
  521. package/src/channels/slack/api.ts +580 -0
  522. package/src/channels/slack/approvals.ts +198 -0
  523. package/src/channels/slack/assistant.ts +76 -0
  524. package/src/channels/slack/auth.ts +112 -0
  525. package/src/channels/slack/channel-watch.ts +219 -0
  526. package/src/channels/slack/constants.ts +30 -0
  527. package/src/channels/slack/credentials.ts +90 -0
  528. package/src/channels/slack/cursor-account.ts +166 -0
  529. package/src/channels/slack/defaults.ts +450 -0
  530. package/src/channels/slack/dispatch.ts +293 -0
  531. package/src/channels/slack/doctor.ts +135 -0
  532. package/src/channels/slack/event-id-dedupe.ts +30 -0
  533. package/src/channels/slack/inbound.ts +219 -0
  534. package/src/channels/slack/index.ts +45 -0
  535. package/src/channels/slack/init.ts +133 -0
  536. package/src/channels/slack/interactive.ts +194 -0
  537. package/src/channels/slack/links.ts +23 -0
  538. package/src/channels/slack/live-delivery.ts +201 -0
  539. package/src/channels/slack/log.ts +83 -0
  540. package/src/channels/slack/manifest.ts +179 -0
  541. package/src/channels/slack/pr-ref.ts +50 -0
  542. package/src/channels/slack/setup.ts +97 -0
  543. package/src/channels/slack/slack-channel.ts +374 -0
  544. package/src/channels/slack/socket-mode.ts +154 -0
  545. package/src/channels/slack/stream.ts +63 -0
  546. package/src/channels/slack/tool-approval-events.ts +136 -0
  547. package/src/channels/slack/types.ts +349 -0
  548. package/src/channels.ts +385 -0
  549. package/src/connections.ts +89 -0
  550. package/src/evals.ts +501 -0
  551. package/src/hooks.ts +33 -0
  552. package/src/index.ts +184 -0
  553. package/src/internal/ab-collector.ts +210 -0
  554. package/src/internal/ab-fold.ts +228 -0
  555. package/src/internal/ab-snapshot.ts +335 -0
  556. package/src/internal/approval-gate.ts +115 -0
  557. package/src/internal/auth.ts +39 -0
  558. package/src/internal/authored-alias-hooks.ts +69 -0
  559. package/src/internal/authored-loaders.ts +138 -0
  560. package/src/internal/bounded-int.ts +27 -0
  561. package/src/internal/brand.ts +29 -0
  562. package/src/internal/chat-client.ts +407 -0
  563. package/src/internal/cli-ax.ts +474 -0
  564. package/src/internal/cli-cursor.ts +275 -0
  565. package/src/internal/cli-github.ts +849 -0
  566. package/src/internal/cli-slack.ts +175 -0
  567. package/src/internal/cloud-merge.ts +41 -0
  568. package/src/internal/cron.ts +78 -0
  569. package/src/internal/cursor/account-mcp.ts +472 -0
  570. package/src/internal/cursor/backend-client.ts +614 -0
  571. package/src/internal/cursor/credentials.ts +401 -0
  572. package/src/internal/cursor-event-relay.ts +281 -0
  573. package/src/internal/cursor-relay-core.ts +444 -0
  574. package/src/internal/cursor-slack-relay.ts +242 -0
  575. package/src/internal/describe-error.ts +4 -0
  576. package/src/internal/dev-mode.ts +20 -0
  577. package/src/internal/discovery.ts +1709 -0
  578. package/src/internal/distribution.ts +97 -0
  579. package/src/internal/docs-site.ts +191 -0
  580. package/src/internal/duration.ts +39 -0
  581. package/src/internal/eval-run-store.ts +519 -0
  582. package/src/internal/eval-runner.ts +522 -0
  583. package/src/internal/event-log.ts +175 -0
  584. package/src/internal/event-mapper.ts +272 -0
  585. package/src/internal/frontmatter.ts +77 -0
  586. package/src/internal/github-fanout.ts +159 -0
  587. package/src/internal/handleAgentServeTrigger.ts +156 -0
  588. package/src/internal/host-platforms.ts +144 -0
  589. package/src/internal/http-channel.ts +546 -0
  590. package/src/internal/ids.ts +44 -0
  591. package/src/internal/init-project.ts +165 -0
  592. package/src/internal/json-dir-store.ts +109 -0
  593. package/src/internal/json-schema.ts +69 -0
  594. package/src/internal/log-ring.ts +59 -0
  595. package/src/internal/map-pool.ts +32 -0
  596. package/src/internal/mcp-endpoint.ts +518 -0
  597. package/src/internal/mcp-host.ts +211 -0
  598. package/src/internal/net.ts +31 -0
  599. package/src/internal/peer-connections.ts +100 -0
  600. package/src/internal/playground/static.ts +189 -0
  601. package/src/internal/playground/toolchain.ts +277 -0
  602. package/src/internal/reminder-runner.ts +756 -0
  603. package/src/internal/reminder-schedule.ts +106 -0
  604. package/src/internal/reminder-store.ts +145 -0
  605. package/src/internal/resolved-connections.ts +145 -0
  606. package/src/internal/review-comments.ts +268 -0
  607. package/src/internal/router.ts +145 -0
  608. package/src/internal/run-client.ts +320 -0
  609. package/src/internal/schedule-runner.ts +161 -0
  610. package/src/internal/sdk-runner.ts +315 -0
  611. package/src/internal/server.ts +1737 -0
  612. package/src/internal/session-engine.ts +1810 -0
  613. package/src/internal/session-store.ts +147 -0
  614. package/src/internal/session-urls.ts +40 -0
  615. package/src/internal/stream-progress.ts +254 -0
  616. package/src/internal/tool-result.ts +54 -0
  617. package/src/internal/trajectory.ts +306 -0
  618. package/src/internal/workspace.ts +223 -0
  619. package/src/reminders.ts +35 -0
  620. package/src/schedules.ts +37 -0
  621. package/src/skills.ts +27 -0
  622. package/src/tools.ts +127 -0
  623. package/src/types.ts +1848 -0
@@ -0,0 +1,1709 @@
1
+ /**
2
+ * Filesystem discovery: walks a project's `agent/` directory and compiles
3
+ * the authored files (markdown and TypeScript) into an {@link AgentProject}
4
+ * manifest. Identity comes from the path — a tool at `agent/tools/foo.ts`
5
+ * is named `foo`, a subagent at `agent/subagents/researcher/` is named
6
+ * `researcher` — so no definition carries a `name` field.
7
+ */
8
+
9
+ import type { Dirent } from "node:fs";
10
+ import { readdir, readFile, stat } from "node:fs/promises";
11
+ import {
12
+ basename,
13
+ extname,
14
+ isAbsolute,
15
+ join,
16
+ relative,
17
+ resolve,
18
+ } from "node:path";
19
+ import { pathToFileURL } from "node:url";
20
+ import { type ABConfigFile, resolveABMaxPlaygroundSessions } from "../ab.js";
21
+ import { httpChannel } from "../channels.js";
22
+ import {
23
+ type AgentConfigDefinition,
24
+ type AgentProject,
25
+ type AgentProjectInfo,
26
+ type ChannelDefinition,
27
+ type ConnectionDefinition,
28
+ type ConnectionTransport,
29
+ DEFAULT_MODEL,
30
+ type Diagnostic,
31
+ type DiscoveredAB,
32
+ type DiscoveredChannel,
33
+ type DiscoveredConnection,
34
+ type DiscoveredHook,
35
+ type DiscoveredSchedule,
36
+ type DiscoveredSkill,
37
+ type DiscoveredTool,
38
+ type HookDefinition,
39
+ type HttpChannelDefinition,
40
+ type InstructionsDefinition,
41
+ type ModelSetting,
42
+ type ResolvedAgent,
43
+ type ScheduleDefinition,
44
+ type SeedFile,
45
+ type SkillDefinition,
46
+ type SkillFileSource,
47
+ type ToolDefinition,
48
+ toPublicCloudInfo,
49
+ } from "../types.js";
50
+ import { registerAuthoredModuleLoaders } from "./authored-loaders.js";
51
+ import { getDefinitionKind } from "./brand.js";
52
+ import { isValidCron } from "./cron.js";
53
+ import { parseFrontmatter } from "./frontmatter.js";
54
+ import { toolInputJsonSchema } from "./json-schema.js";
55
+ import { MCP_ENDPOINT_PATH, mcpEndpointToolNames } from "./mcp-endpoint.js";
56
+
57
+ const MODULE_EXTENSIONS = new Set([".ts", ".mts", ".js", ".mjs"]);
58
+ const NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
59
+ const SKILL_DESCRIPTION_FALLBACK = (name: string): string =>
60
+ `Instructions for the ${name} skill.`;
61
+
62
+ export interface LoadAgentProjectOptions {
63
+ /** Override the module loader (embedders / tests). */
64
+ importModule?: (absolutePath: string) => Promise<Record<string, unknown>>;
65
+ }
66
+
67
+ type ImportModule = (absolutePath: string) => Promise<Record<string, unknown>>;
68
+
69
+ async function defaultImportModule(
70
+ absolutePath: string
71
+ ): Promise<Record<string, unknown>> {
72
+ return (await import(pathToFileURL(absolutePath).href)) as Record<
73
+ string,
74
+ unknown
75
+ >;
76
+ }
77
+
78
+ interface DiscoveryContext {
79
+ rootDir: string;
80
+ diagnostics: Diagnostic[];
81
+ importModule: ImportModule;
82
+ }
83
+
84
+ /**
85
+ * True when `dir` looks like an agent project root: it has a nested `agent/`
86
+ * directory, or flat-layout markers (`agent.ts` / `instructions.md`) directly
87
+ * inside it.
88
+ */
89
+ export async function isAgentProjectDir(dir: string): Promise<boolean> {
90
+ const resolved = resolve(dir);
91
+ if (!(await isDirectory(resolved))) {
92
+ return false;
93
+ }
94
+ if (await isDirectory(join(resolved, "agent"))) {
95
+ return true;
96
+ }
97
+ return hasFlatLayoutMarkers(resolved);
98
+ }
99
+
100
+ /**
101
+ * Discover the agent projects directly under `rootDir` (one level deep):
102
+ * each immediate child directory that is itself an agent project becomes a
103
+ * mounted agent, keyed by a slug derived from its directory name. Used for
104
+ * multi-agent serving, where one server hosts many agents under
105
+ * `/<slug>/...`. Throws when no child projects are found.
106
+ */
107
+ export async function loadAgentProjects(
108
+ rootDir: string,
109
+ options: LoadAgentProjectOptions = {}
110
+ ): Promise<Array<{ slug: string; project: AgentProject }>> {
111
+ const resolved = resolve(rootDir);
112
+ if (!(await isDirectory(resolved))) {
113
+ throw new Error(`Agent projects directory does not exist: ${resolved}`);
114
+ }
115
+
116
+ const results: Array<{ slug: string; project: AgentProject }> = [];
117
+ const seenSlugs = new Set<string>();
118
+ for (const entry of await sortedEntries(resolved)) {
119
+ if (!entry.isDirectory() || entry.name.startsWith(".")) {
120
+ continue;
121
+ }
122
+ if (entry.name === "node_modules") {
123
+ continue;
124
+ }
125
+ const childDir = join(resolved, entry.name);
126
+ if (!(await isAgentProjectDir(childDir))) {
127
+ continue;
128
+ }
129
+ const slug = assertSlug(entry.name, resolved);
130
+ if (seenSlugs.has(slug)) {
131
+ throw new Error(`Duplicate agent slug "${slug}" under ${resolved}.`);
132
+ }
133
+ seenSlugs.add(slug);
134
+ results.push({ slug, project: await loadAgentProject(childDir, options) });
135
+ }
136
+
137
+ if (results.length === 0) {
138
+ throw new Error(
139
+ `No agent projects found directly under ${resolved}. Each agent should be a subdirectory with its own agent/ directory (or flat-layout markers).`
140
+ );
141
+ }
142
+ return results;
143
+ }
144
+
145
+ /**
146
+ * Resolve mounts for `serve` / CLI. Defaults to multi-agent layout:
147
+ * - `mode: "single"` — one agent at `/v1/*` (empty slug).
148
+ * - otherwise, if `rootDir` is itself an agent project — one agent under
149
+ * `/<dirname>/...`.
150
+ * - otherwise, every immediate child agent project under `/<slug>/...`.
151
+ */
152
+ export async function resolveAgentMounts(
153
+ rootDir: string,
154
+ options: LoadAgentProjectOptions & {
155
+ mode?: "single" | "multi";
156
+ } = {}
157
+ ): Promise<Array<{ slug: string; project: AgentProject }>> {
158
+ const { mode = "multi", ...discovery } = options;
159
+ if (mode === "single") {
160
+ return [{ slug: "", project: await loadAgentProject(rootDir, discovery) }];
161
+ }
162
+ if (await isAgentProjectDir(rootDir)) {
163
+ const resolved = resolve(rootDir);
164
+ const slug = assertSlug(basename(resolved), resolved);
165
+ return [
166
+ {
167
+ slug,
168
+ project: await loadAgentProject(rootDir, discovery),
169
+ },
170
+ ];
171
+ }
172
+ return loadAgentProjects(rootDir, discovery);
173
+ }
174
+
175
+ function assertSlug(name: string, parentDir: string): string {
176
+ if (!NAME_PATTERN.test(name) || RESERVED_SLUGS.has(name)) {
177
+ throw new Error(
178
+ `Cannot mount agent directory "${name}" under ${parentDir}: its name must match ${NAME_PATTERN} and not be one of ${[...RESERVED_SLUGS].join(", ")}.`
179
+ );
180
+ }
181
+ return name;
182
+ }
183
+
184
+ /** URL path segments reserved by the server; agents cannot use them as slugs. */
185
+ export const RESERVED_SLUGS: Set<string> = new Set([
186
+ "v1",
187
+ "playground",
188
+ "docs",
189
+ ]);
190
+
191
+ export async function loadAgentProject(
192
+ dir: string,
193
+ options: LoadAgentProjectOptions = {}
194
+ ): Promise<AgentProject> {
195
+ // Public API: embedders can call discovery directly (without `serve()` or
196
+ // the CLI), and it dynamically imports authored TypeScript.
197
+ await registerAuthoredModuleLoaders();
198
+ const rootDir = resolve(dir);
199
+ if (!(await isDirectory(rootDir))) {
200
+ throw new Error(`Agent project directory does not exist: ${rootDir}`);
201
+ }
202
+
203
+ const nestedAgentDir = join(rootDir, "agent");
204
+ let agentDir: string;
205
+ if (await isDirectory(nestedAgentDir)) {
206
+ agentDir = nestedAgentDir;
207
+ } else if (await hasFlatLayoutMarkers(rootDir)) {
208
+ agentDir = rootDir;
209
+ } else {
210
+ throw new Error(
211
+ `No agent found under ${rootDir}: expected an agent/ directory (or a flat layout with agent.ts / instructions.md at the root)`
212
+ );
213
+ }
214
+
215
+ const diagnostics: Diagnostic[] = [];
216
+ const ctx: DiscoveryContext = {
217
+ rootDir,
218
+ diagnostics,
219
+ importModule: options.importModule ?? defaultImportModule,
220
+ };
221
+
222
+ const name = (await readPackageName(rootDir)) ?? basename(rootDir);
223
+ const agent = await loadAgent(ctx, agentDir, {
224
+ name,
225
+ isRoot: true,
226
+ });
227
+
228
+ const { channels, httpChannelDefinition } = await loadChannels(
229
+ ctx,
230
+ join(agentDir, "channels")
231
+ );
232
+ const schedules = await loadSchedules(ctx, join(agentDir, "schedules"));
233
+ const hooks = await loadHooks(ctx, join(agentDir, "hooks"));
234
+ const abs = await loadABs(ctx, agentDir);
235
+ const abConfig = await loadABConfig(ctx, agentDir);
236
+
237
+ if (agentDir !== rootDir) {
238
+ await warnUnknownSlots(ctx, agentDir);
239
+ }
240
+
241
+ return {
242
+ rootDir,
243
+ agentDir,
244
+ name: agent.name,
245
+ agent,
246
+ httpChannel: httpChannelDefinition ?? httpChannel(),
247
+ channels,
248
+ schedules,
249
+ hooks,
250
+ abs,
251
+ ...(abConfig === undefined ? {} : { abConfig }),
252
+ diagnostics,
253
+ };
254
+ }
255
+
256
+ /** True when the project has diagnostics that must block serving. */
257
+ export function hasErrorDiagnostics(project: AgentProject): boolean {
258
+ return project.diagnostics.some((d) => d.severity === "error");
259
+ }
260
+
261
+ export function formatDiagnostics(diagnostics: Diagnostic[]): string {
262
+ return diagnostics
263
+ .map((d) => {
264
+ const location = d.path === undefined ? "" : ` (${d.path})`;
265
+ return `${d.severity}: ${d.message}${location}`;
266
+ })
267
+ .join("\n");
268
+ }
269
+
270
+ // ============================================================================
271
+ // Agents (root + subagents)
272
+ // ============================================================================
273
+
274
+ async function loadAgent(
275
+ ctx: DiscoveryContext,
276
+ agentDir: string,
277
+ options: { name: string; isRoot: boolean }
278
+ ): Promise<ResolvedAgent> {
279
+ const config = await loadAgentConfig(ctx, agentDir, options.isRoot);
280
+ const instructions = await loadInstructions(
281
+ ctx,
282
+ agentDir,
283
+ config,
284
+ options.isRoot
285
+ );
286
+
287
+ // Subagents run as Cursor SDK custom subagents (prompt + description +
288
+ // model); their other slots are inert today, so don't load them — just
289
+ // warn that they exist.
290
+ if (!options.isRoot) {
291
+ await warnUnsupportedSubagentSlots(ctx, agentDir);
292
+ return {
293
+ name: config?.name ?? options.name,
294
+ ...(config?.description === undefined
295
+ ? {}
296
+ : { description: config.description }),
297
+ ...(config?.model === undefined ? {} : { model: config.model }),
298
+ ...(instructions === undefined ? {} : { instructions }),
299
+ runtime: "local",
300
+ tools: [],
301
+ skills: [],
302
+ connections: [],
303
+ subagents: [],
304
+ seedFiles: [],
305
+ };
306
+ }
307
+
308
+ const tools = await loadTools(ctx, join(agentDir, "tools"));
309
+ const skills = await loadSkills(ctx, join(agentDir, "skills"));
310
+ const connections = await loadConnections(
311
+ ctx,
312
+ await resolveMcpConnectionsDir(ctx, agentDir)
313
+ );
314
+ const seedFiles = await loadSeedFiles(ctx, agentDir);
315
+ const subagents = await loadSubagents(ctx, join(agentDir, "subagents"));
316
+ const runtime = config?.runtime === "cloud" ? "cloud" : "local";
317
+ // Keep authored `cloud` even when default runtime is local — per-session
318
+ // `send({ cloud })` merges it under cloudOverride for handoff sessions.
319
+ const cloud = config?.cloud;
320
+ const localCwd = resolveAgentCwd(ctx, config?.local?.cwd);
321
+ const local =
322
+ localCwd === undefined
323
+ ? undefined
324
+ : {
325
+ cwd: localCwd,
326
+ };
327
+
328
+ if (runtime === "cloud") {
329
+ warnCloudRuntimeLimitations(ctx, agentDir, {
330
+ serverTools: tools.filter((tool) => tool.execution === "server").length,
331
+ agentTools: tools.filter((tool) => tool.execution === "agent").length,
332
+ skills: skills.length,
333
+ seedFiles: seedFiles.length,
334
+ hasCloudOptions: cloud !== undefined,
335
+ });
336
+ }
337
+
338
+ return {
339
+ name: config?.name ?? options.name,
340
+ description: config?.description,
341
+ // Root agents always get a concrete model: authored value or DEFAULT_MODEL.
342
+ model: isValidModelSetting(config?.model) ? config.model : DEFAULT_MODEL,
343
+ instructions,
344
+ runtime,
345
+ cloud,
346
+ local,
347
+ tools,
348
+ skills,
349
+ connections,
350
+ subagents,
351
+ seedFiles,
352
+ };
353
+ }
354
+
355
+ /** Absolute harness cwd parent, or undefined when unset / blank. */
356
+ function resolveAgentCwd(
357
+ ctx: DiscoveryContext,
358
+ authored: string | undefined
359
+ ): string | undefined {
360
+ if (authored === undefined) {
361
+ return undefined;
362
+ }
363
+ const trimmed = authored.trim();
364
+ if (trimmed === "") {
365
+ return undefined;
366
+ }
367
+ return isAbsolute(trimmed) ? trimmed : resolve(ctx.rootDir, trimmed);
368
+ }
369
+
370
+ async function loadAgentConfig(
371
+ ctx: DiscoveryContext,
372
+ agentDir: string,
373
+ isRoot: boolean
374
+ ): Promise<AgentConfigDefinition | undefined> {
375
+ const configPath = await findModuleFile(agentDir, "agent");
376
+ if (configPath === undefined) {
377
+ ctx.diagnostics.push({
378
+ severity: "error",
379
+ path: displayPath(ctx, join(agentDir, "agent.ts")),
380
+ message: isRoot
381
+ ? "agent.ts is required on the root agent: default-export defineAgent({ ... })"
382
+ : "agent.ts is required on a subagent: default-export defineAgent({ description: ... })",
383
+ });
384
+ return undefined;
385
+ }
386
+
387
+ const definition = await importDefault(ctx, configPath, "agent");
388
+ if (definition === undefined) {
389
+ return undefined;
390
+ }
391
+ const config = definition as AgentConfigDefinition;
392
+
393
+ if (!isRoot && isBlank(config.description)) {
394
+ ctx.diagnostics.push({
395
+ severity: "error",
396
+ path: displayPath(ctx, configPath),
397
+ message:
398
+ "Subagents require a description; the parent model reads it to decide when to delegate.",
399
+ });
400
+ }
401
+ return config;
402
+ }
403
+
404
+ async function loadInstructions(
405
+ ctx: DiscoveryContext,
406
+ agentDir: string,
407
+ config: AgentConfigDefinition | undefined,
408
+ isRoot: boolean
409
+ ): Promise<string | undefined> {
410
+ const parts: string[] = [];
411
+
412
+ const markdownPath = join(agentDir, "instructions.md");
413
+ if (await isFile(markdownPath)) {
414
+ parts.push((await readFile(markdownPath, "utf8")).trim());
415
+ }
416
+
417
+ const modulePath = await findModuleFile(agentDir, "instructions");
418
+ if (modulePath !== undefined) {
419
+ const loaded = await loadInstructionsModule(ctx, modulePath);
420
+ if (loaded !== undefined) {
421
+ parts.push(loaded);
422
+ }
423
+ }
424
+
425
+ const instructionsDir = join(agentDir, "instructions");
426
+ if (await isDirectory(instructionsDir)) {
427
+ for (const entry of await sortedEntries(instructionsDir)) {
428
+ if (!entry.isFile()) {
429
+ continue;
430
+ }
431
+ const entryPath = join(instructionsDir, entry.name);
432
+ if (entry.name.endsWith(".md")) {
433
+ parts.push((await readFile(entryPath, "utf8")).trim());
434
+ } else if (isModuleFile(entry.name)) {
435
+ const loaded = await loadInstructionsModule(ctx, entryPath);
436
+ if (loaded !== undefined) {
437
+ parts.push(loaded);
438
+ }
439
+ }
440
+ }
441
+ }
442
+
443
+ if (config?.instructions !== undefined && !isBlank(config.instructions)) {
444
+ parts.push(config.instructions.trim());
445
+ }
446
+
447
+ const combined = parts.filter((part) => part !== "").join("\n\n");
448
+ if (combined === "") {
449
+ if (isRoot) {
450
+ ctx.diagnostics.push({
451
+ severity: "error",
452
+ path: displayPath(ctx, markdownPath),
453
+ message:
454
+ "instructions.md is required on the root agent (the always-on system prompt).",
455
+ });
456
+ }
457
+ return undefined;
458
+ }
459
+ return combined;
460
+ }
461
+
462
+ async function loadInstructionsModule(
463
+ ctx: DiscoveryContext,
464
+ modulePath: string
465
+ ): Promise<string | undefined> {
466
+ let mod: Record<string, unknown>;
467
+ try {
468
+ mod = await ctx.importModule(modulePath);
469
+ } catch (error) {
470
+ ctx.diagnostics.push({
471
+ severity: "error",
472
+ path: displayPath(ctx, modulePath),
473
+ message: `Failed to load module: ${errorMessage(error)}`,
474
+ });
475
+ return undefined;
476
+ }
477
+ const value = mod.default;
478
+ if (typeof value === "string") {
479
+ return value.trim();
480
+ }
481
+ if (getDefinitionKind(value) === "instructions") {
482
+ return (value as InstructionsDefinition).markdown.trim();
483
+ }
484
+ ctx.diagnostics.push({
485
+ severity: "error",
486
+ path: displayPath(ctx, modulePath),
487
+ message:
488
+ "Instructions modules must default-export a string or defineInstructions({ markdown }).",
489
+ });
490
+ return undefined;
491
+ }
492
+
493
+ // ============================================================================
494
+ // Tools
495
+ // ============================================================================
496
+
497
+ async function loadTools(
498
+ ctx: DiscoveryContext,
499
+ toolsDir: string
500
+ ): Promise<DiscoveredTool[]> {
501
+ if (!(await isDirectory(toolsDir))) {
502
+ return [];
503
+ }
504
+ const tools: DiscoveredTool[] = [];
505
+ for (const entry of await sortedEntries(toolsDir)) {
506
+ const entryPath = join(toolsDir, entry.name);
507
+ if (entry.isDirectory()) {
508
+ ctx.diagnostics.push({
509
+ severity: "warning",
510
+ path: displayPath(ctx, entryPath),
511
+ message: "Nested tool directories are not supported; ignoring.",
512
+ });
513
+ continue;
514
+ }
515
+ if (!entry.isFile() || !isModuleFile(entry.name)) {
516
+ continue;
517
+ }
518
+ const name = moduleStem(entry.name);
519
+ if (!NAME_PATTERN.test(name)) {
520
+ ctx.diagnostics.push({
521
+ severity: "error",
522
+ path: displayPath(ctx, entryPath),
523
+ message: `Tool name "${name}" must match ${NAME_PATTERN} (the filename becomes the tool name the model sees).`,
524
+ });
525
+ continue;
526
+ }
527
+ const definition = await importDefault(ctx, entryPath, "tool");
528
+ if (definition === undefined) {
529
+ continue;
530
+ }
531
+ const tool = definition as ToolDefinition;
532
+ if (isBlank(tool.description)) {
533
+ ctx.diagnostics.push({
534
+ severity: "error",
535
+ path: displayPath(ctx, entryPath),
536
+ message: "Tools require a non-empty description.",
537
+ });
538
+ continue;
539
+ }
540
+ const execution = tool.execution === "agent" ? "agent" : "server";
541
+ const needsApprovalConfigured =
542
+ tool.needsApproval === true || typeof tool.needsApproval === "function";
543
+ if (execution === "agent") {
544
+ if (isBlank(tool.script)) {
545
+ ctx.diagnostics.push({
546
+ severity: "error",
547
+ path: displayPath(ctx, entryPath),
548
+ message:
549
+ 'Tools with execution: "agent" require a non-empty `script` (shell body; JSON on stdin, result on stdout).',
550
+ });
551
+ continue;
552
+ }
553
+ if (typeof tool.execute === "function") {
554
+ ctx.diagnostics.push({
555
+ severity: "warning",
556
+ path: displayPath(ctx, entryPath),
557
+ message:
558
+ '`execute` is ignored when execution is "agent"; the Cursor agent runs `script` instead.',
559
+ });
560
+ }
561
+ if (needsApprovalConfigured) {
562
+ ctx.diagnostics.push({
563
+ severity: "error",
564
+ path: displayPath(ctx, entryPath),
565
+ message:
566
+ '`needsApproval` is only supported for execution: "server" tools (in-process).',
567
+ });
568
+ continue;
569
+ }
570
+ } else if (typeof tool.execute !== "function") {
571
+ ctx.diagnostics.push({
572
+ severity: "error",
573
+ path: displayPath(ctx, entryPath),
574
+ message:
575
+ 'Tools require an execute(input, ctx) function (or set execution: "agent" with a script).',
576
+ });
577
+ continue;
578
+ }
579
+ let inputSchema: DiscoveredTool["inputSchema"];
580
+ try {
581
+ inputSchema = toolInputJsonSchema(tool.inputSchema);
582
+ } catch (error) {
583
+ ctx.diagnostics.push({
584
+ severity: "error",
585
+ path: displayPath(ctx, entryPath),
586
+ message: `Failed to convert inputSchema to JSON Schema: ${errorMessage(error)}`,
587
+ });
588
+ continue;
589
+ }
590
+ tools.push({
591
+ name,
592
+ description: tool.description,
593
+ inputSchema,
594
+ execution,
595
+ needsApproval: needsApprovalConfigured,
596
+ ...(execution === "agent" && tool.script !== undefined
597
+ ? { script: tool.script }
598
+ : {}),
599
+ definition: tool,
600
+ });
601
+ }
602
+ return tools;
603
+ }
604
+
605
+ // ============================================================================
606
+ // Skills
607
+ // ============================================================================
608
+
609
+ async function loadSkills(
610
+ ctx: DiscoveryContext,
611
+ skillsDir: string
612
+ ): Promise<DiscoveredSkill[]> {
613
+ if (!(await isDirectory(skillsDir))) {
614
+ return [];
615
+ }
616
+ const skills: DiscoveredSkill[] = [];
617
+ for (const entry of await sortedEntries(skillsDir)) {
618
+ const entryPath = join(skillsDir, entry.name);
619
+ if (entry.isDirectory()) {
620
+ const skill = await loadPackagedSkill(ctx, entryPath, entry.name);
621
+ if (skill !== undefined) {
622
+ skills.push(skill);
623
+ }
624
+ continue;
625
+ }
626
+ if (!entry.isFile()) {
627
+ continue;
628
+ }
629
+ if (entry.name.endsWith(".md")) {
630
+ const name = entry.name.slice(0, -".md".length);
631
+ if (!validateSkillName(ctx, name, entryPath)) {
632
+ continue;
633
+ }
634
+ const { data, body } = parseFrontmatter(
635
+ await readFile(entryPath, "utf8")
636
+ );
637
+ skills.push({
638
+ name,
639
+ description:
640
+ data.description ??
641
+ deriveSkillDescription(body) ??
642
+ SKILL_DESCRIPTION_FALLBACK(name),
643
+ markdown: body.trim(),
644
+ files: {},
645
+ });
646
+ continue;
647
+ }
648
+ if (isModuleFile(entry.name)) {
649
+ const name = moduleStem(entry.name);
650
+ if (!validateSkillName(ctx, name, entryPath)) {
651
+ continue;
652
+ }
653
+ const definition = await importDefault(ctx, entryPath, "skill");
654
+ if (definition === undefined) {
655
+ continue;
656
+ }
657
+ const skill = definition as SkillDefinition;
658
+ const files: Record<string, SkillFileSource> = {};
659
+ for (const [rel, contents] of Object.entries(skill.files ?? {})) {
660
+ files[rel] = { contents };
661
+ }
662
+ skills.push({
663
+ name,
664
+ description:
665
+ skill.description ??
666
+ deriveSkillDescription(skill.markdown) ??
667
+ SKILL_DESCRIPTION_FALLBACK(name),
668
+ markdown: skill.markdown.trim(),
669
+ files,
670
+ });
671
+ }
672
+ }
673
+ return skills;
674
+ }
675
+
676
+ async function loadPackagedSkill(
677
+ ctx: DiscoveryContext,
678
+ skillDir: string,
679
+ name: string
680
+ ): Promise<DiscoveredSkill | undefined> {
681
+ const skillMdPath = join(skillDir, "SKILL.md");
682
+ if (!(await isFile(skillMdPath))) {
683
+ ctx.diagnostics.push({
684
+ severity: "warning",
685
+ path: displayPath(ctx, skillDir),
686
+ message: "Skill directories must contain a SKILL.md; ignoring.",
687
+ });
688
+ return undefined;
689
+ }
690
+ if (!validateSkillName(ctx, name, skillDir)) {
691
+ return undefined;
692
+ }
693
+ const { data, body } = parseFrontmatter(await readFile(skillMdPath, "utf8"));
694
+ if (data.description === undefined || isBlank(data.description)) {
695
+ ctx.diagnostics.push({
696
+ severity: "error",
697
+ path: displayPath(ctx, skillMdPath),
698
+ message:
699
+ "Packaged skills require `description` frontmatter in SKILL.md (there is no filename slug to fall back on).",
700
+ });
701
+ return undefined;
702
+ }
703
+
704
+ const files: Record<string, SkillFileSource> = {};
705
+ for (const filePath of await walkFiles(skillDir)) {
706
+ const rel = toPosix(relative(skillDir, filePath));
707
+ if (rel === "SKILL.md") {
708
+ continue;
709
+ }
710
+ files[rel] = { sourcePath: filePath };
711
+ }
712
+
713
+ return {
714
+ name,
715
+ description: data.description,
716
+ markdown: body.trim(),
717
+ files,
718
+ };
719
+ }
720
+
721
+ /** One-line `/v1/info` label for a connection transport. */
722
+ function connectionTransportLabel(transport: ConnectionTransport): string {
723
+ if ("cursorAccount" in transport) {
724
+ return transport.servers === undefined
725
+ ? "cursor-account"
726
+ : `cursor-account:${transport.servers.join(",")}`;
727
+ }
728
+ if ("agent" in transport) {
729
+ return `agent:${transport.agent}`;
730
+ }
731
+ if ("url" in transport) {
732
+ return transport.url;
733
+ }
734
+ return transport.command;
735
+ }
736
+
737
+ function connectionTransport(
738
+ connection: ConnectionDefinition
739
+ ): ConnectionTransport | undefined {
740
+ if ("cursorAccount" in connection && connection.cursorAccount === true) {
741
+ return {
742
+ cursorAccount: true,
743
+ ...(connection.servers === undefined
744
+ ? {}
745
+ : { servers: connection.servers }),
746
+ };
747
+ }
748
+ if ("agent" in connection && !isBlank(connection.agent)) {
749
+ return { agent: connection.agent };
750
+ }
751
+ if ("url" in connection && !isBlank(connection.url)) {
752
+ return {
753
+ ...(connection.type === undefined ? {} : { type: connection.type }),
754
+ url: connection.url,
755
+ ...(connection.headers === undefined
756
+ ? {}
757
+ : { headers: connection.headers }),
758
+ };
759
+ }
760
+ if ("command" in connection && !isBlank(connection.command)) {
761
+ return {
762
+ ...(connection.type === undefined ? {} : { type: connection.type }),
763
+ command: connection.command,
764
+ ...(connection.args === undefined ? {} : { args: connection.args }),
765
+ ...(connection.env === undefined ? {} : { env: connection.env }),
766
+ ...(connection.cwd === undefined ? {} : { cwd: connection.cwd }),
767
+ };
768
+ }
769
+ return undefined;
770
+ }
771
+
772
+ function validateSkillName(
773
+ ctx: DiscoveryContext,
774
+ name: string,
775
+ entryPath: string
776
+ ): boolean {
777
+ if (NAME_PATTERN.test(name)) {
778
+ return true;
779
+ }
780
+ ctx.diagnostics.push({
781
+ severity: "error",
782
+ path: displayPath(ctx, entryPath),
783
+ message: `Skill name "${name}" must match ${NAME_PATTERN}.`,
784
+ });
785
+ return false;
786
+ }
787
+
788
+ /**
789
+ * Advertise the first non-empty, non-code-fence line of the body, with any
790
+ * leading `#`, `>`, `*`, or `-` markers stripped.
791
+ */
792
+ export function deriveSkillDescription(markdown: string): string | undefined {
793
+ let inFence = false;
794
+ for (const rawLine of markdown.split(/\r?\n/)) {
795
+ const line = rawLine.trim();
796
+ if (line.startsWith("```")) {
797
+ inFence = !inFence;
798
+ continue;
799
+ }
800
+ if (inFence || line === "") {
801
+ continue;
802
+ }
803
+ const stripped = line.replace(/^[#>*\s-]+/, "").trim();
804
+ if (stripped !== "") {
805
+ return stripped.length > 200 ? `${stripped.slice(0, 197)}...` : stripped;
806
+ }
807
+ }
808
+ return undefined;
809
+ }
810
+
811
+ // ============================================================================
812
+ // MCP connections
813
+ // ============================================================================
814
+
815
+ /**
816
+ * Prefer `agent/mcp-connections/`. Keep loading `agent/connections/` with a
817
+ * deprecation warning so existing agents keep working during the rename.
818
+ */
819
+ async function resolveMcpConnectionsDir(
820
+ ctx: DiscoveryContext,
821
+ agentDir: string
822
+ ): Promise<string> {
823
+ const preferred = join(agentDir, "mcp-connections");
824
+ const legacy = join(agentDir, "connections");
825
+ const hasPreferred = await isDirectory(preferred);
826
+ const hasLegacy = await isDirectory(legacy);
827
+ if (hasPreferred && hasLegacy) {
828
+ ctx.diagnostics.push({
829
+ severity: "warning",
830
+ path: displayPath(ctx, legacy),
831
+ message:
832
+ "Both agent/mcp-connections/ and agent/connections/ exist; using mcp-connections/ and ignoring connections/. Rename or remove the legacy directory.",
833
+ });
834
+ return preferred;
835
+ }
836
+ if (hasLegacy) {
837
+ ctx.diagnostics.push({
838
+ severity: "warning",
839
+ path: displayPath(ctx, legacy),
840
+ message:
841
+ "agent/connections/ is deprecated; rename it to agent/mcp-connections/. Loading from the legacy path for now.",
842
+ });
843
+ return legacy;
844
+ }
845
+ return preferred;
846
+ }
847
+
848
+ async function loadConnections(
849
+ ctx: DiscoveryContext,
850
+ connectionsDir: string
851
+ ): Promise<DiscoveredConnection[]> {
852
+ if (!(await isDirectory(connectionsDir))) {
853
+ return [];
854
+ }
855
+ const connections: DiscoveredConnection[] = [];
856
+ for (const entry of await sortedEntries(connectionsDir)) {
857
+ const entryPath = join(connectionsDir, entry.name);
858
+ if (!entry.isFile() || !isModuleFile(entry.name)) {
859
+ continue;
860
+ }
861
+ const name = moduleStem(entry.name);
862
+ if (!NAME_PATTERN.test(name)) {
863
+ ctx.diagnostics.push({
864
+ severity: "error",
865
+ path: displayPath(ctx, entryPath),
866
+ message: `MCP connection name "${name}" must match ${NAME_PATTERN}.`,
867
+ });
868
+ continue;
869
+ }
870
+ const definition = await importDefault(ctx, entryPath, "connection");
871
+ if (definition === undefined) {
872
+ continue;
873
+ }
874
+ const connection = definition as ConnectionDefinition;
875
+ const transport = connectionTransport(connection);
876
+ if (transport === undefined) {
877
+ ctx.diagnostics.push({
878
+ severity: "error",
879
+ path: displayPath(ctx, entryPath),
880
+ message:
881
+ "MCP connections require a `url` (http/sse), a `command` (stdio), an `agent` (peer agent slug on the same serve host), or `cursorAccount: true` (the Cursor account's MCP connectors).",
882
+ });
883
+ continue;
884
+ }
885
+ if (
886
+ "cursorAccount" in transport &&
887
+ transport.servers !== undefined &&
888
+ (transport.servers.length === 0 ||
889
+ transport.servers.some((server) => isBlank(server)))
890
+ ) {
891
+ ctx.diagnostics.push({
892
+ severity: "error",
893
+ path: displayPath(ctx, entryPath),
894
+ message:
895
+ "Cursor account MCP connection `servers` must be a non-empty list of connector names (or omitted to expose every connected server).",
896
+ });
897
+ continue;
898
+ }
899
+ if ("agent" in transport && !NAME_PATTERN.test(transport.agent)) {
900
+ ctx.diagnostics.push({
901
+ severity: "error",
902
+ path: displayPath(ctx, entryPath),
903
+ message: `Peer MCP connection agent "${transport.agent}" must be a mount slug matching ${NAME_PATTERN}.`,
904
+ });
905
+ continue;
906
+ }
907
+ connections.push({
908
+ name,
909
+ transport,
910
+ ...(connection.description === undefined
911
+ ? {}
912
+ : { description: connection.description }),
913
+ });
914
+ }
915
+ return connections;
916
+ }
917
+
918
+ // ============================================================================
919
+ // Subagents
920
+ // ============================================================================
921
+
922
+ async function loadSubagents(
923
+ ctx: DiscoveryContext,
924
+ subagentsDir: string
925
+ ): Promise<ResolvedAgent[]> {
926
+ if (!(await isDirectory(subagentsDir))) {
927
+ return [];
928
+ }
929
+ const subagents: ResolvedAgent[] = [];
930
+ for (const entry of await sortedEntries(subagentsDir)) {
931
+ if (!entry.isDirectory()) {
932
+ continue;
933
+ }
934
+ const subagentDir = join(subagentsDir, entry.name);
935
+ if (!NAME_PATTERN.test(entry.name)) {
936
+ ctx.diagnostics.push({
937
+ severity: "error",
938
+ path: displayPath(ctx, subagentDir),
939
+ message: `Subagent name "${entry.name}" must match ${NAME_PATTERN}.`,
940
+ });
941
+ continue;
942
+ }
943
+ subagents.push(
944
+ await loadAgent(ctx, subagentDir, { name: entry.name, isRoot: false })
945
+ );
946
+ }
947
+ return subagents;
948
+ }
949
+
950
+ async function warnUnsupportedSubagentSlots(
951
+ ctx: DiscoveryContext,
952
+ subagentDir: string
953
+ ): Promise<void> {
954
+ const unsupported = [
955
+ ["tools", "tools"],
956
+ ["skills", "skills"],
957
+ ["mcp-connections", "MCP connections"],
958
+ ["connections", "MCP connections"],
959
+ ["subagents", "nested subagents"],
960
+ ["channels", "channels"],
961
+ ["schedules", "schedules"],
962
+ ["hooks", "hooks"],
963
+ ["sandbox", "sandbox files"],
964
+ ] as const;
965
+ for (const [slot, label] of unsupported) {
966
+ if (await isDirectory(join(subagentDir, slot))) {
967
+ ctx.diagnostics.push({
968
+ severity: "warning",
969
+ path: displayPath(ctx, join(subagentDir, slot)),
970
+ message: `Subagent ${label} are not supported on the Cursor harness yet; the subagent runs with the parent's execution surface. Ignoring.`,
971
+ });
972
+ }
973
+ }
974
+ }
975
+
976
+ function warnCloudRuntimeLimitations(
977
+ ctx: DiscoveryContext,
978
+ agentDir: string,
979
+ counts: {
980
+ serverTools: number;
981
+ agentTools: number;
982
+ skills: number;
983
+ seedFiles: number;
984
+ hasCloudOptions: boolean;
985
+ }
986
+ ): void {
987
+ const agentPath = displayPath(ctx, join(agentDir, "agent.ts"));
988
+ if (!counts.hasCloudOptions) {
989
+ ctx.diagnostics.push({
990
+ severity: "warning",
991
+ path: agentPath,
992
+ message:
993
+ 'runtime: "cloud" usually needs a `cloud` block (e.g. repos / env). Without it the Cursor cloud API may reject create.',
994
+ });
995
+ }
996
+ if (counts.serverTools > 0) {
997
+ ctx.diagnostics.push({
998
+ severity: "warning",
999
+ path: displayPath(ctx, join(agentDir, "tools")),
1000
+ message:
1001
+ 'In-process tools (execution: "server", the default) are not available on cloud agents. Use execution: "agent", MCP connections, or tools that already live in the cloud workspace.',
1002
+ });
1003
+ }
1004
+ if (counts.agentTools > 0) {
1005
+ ctx.diagnostics.push({
1006
+ severity: "warning",
1007
+ path: displayPath(ctx, join(agentDir, "tools")),
1008
+ message:
1009
+ 'Agent tools (execution: "agent") are described on the first cloud prompt (with script bodies); they are not written into the cloud VM filesystem automatically.',
1010
+ });
1011
+ }
1012
+ if (counts.skills > 0) {
1013
+ ctx.diagnostics.push({
1014
+ severity: "warning",
1015
+ path: displayPath(ctx, join(agentDir, "skills")),
1016
+ message:
1017
+ "Authored skills are materialized into the local session workspace only; cloud agents see skills that already exist in the cloud repo.",
1018
+ });
1019
+ }
1020
+ if (counts.seedFiles > 0) {
1021
+ ctx.diagnostics.push({
1022
+ severity: "warning",
1023
+ path: displayPath(ctx, join(agentDir, "sandbox")),
1024
+ message:
1025
+ "sandbox/workspace seed files apply to the local session workspace only; cloud agents use the configured cloud repos.",
1026
+ });
1027
+ }
1028
+ }
1029
+
1030
+ // ============================================================================
1031
+ // Channels
1032
+ // ============================================================================
1033
+
1034
+ async function loadChannels(
1035
+ ctx: DiscoveryContext,
1036
+ channelsDir: string
1037
+ ): Promise<{
1038
+ channels: DiscoveredChannel[];
1039
+ httpChannelDefinition: HttpChannelDefinition | undefined;
1040
+ }> {
1041
+ const channels: DiscoveredChannel[] = [];
1042
+ let httpChannelDefinition: HttpChannelDefinition | undefined;
1043
+ if (!(await isDirectory(channelsDir))) {
1044
+ return { channels, httpChannelDefinition };
1045
+ }
1046
+
1047
+ for (const entry of await sortedEntries(channelsDir)) {
1048
+ const entryPath = join(channelsDir, entry.name);
1049
+ if (!entry.isFile() || !isModuleFile(entry.name)) {
1050
+ continue;
1051
+ }
1052
+ const id = moduleStem(entry.name);
1053
+ if (!NAME_PATTERN.test(id)) {
1054
+ ctx.diagnostics.push({
1055
+ severity: "error",
1056
+ path: displayPath(ctx, entryPath),
1057
+ message: `Channel id "${id}" must match ${NAME_PATTERN}.`,
1058
+ });
1059
+ continue;
1060
+ }
1061
+
1062
+ let mod: Record<string, unknown>;
1063
+ try {
1064
+ mod = await ctx.importModule(entryPath);
1065
+ } catch (error) {
1066
+ ctx.diagnostics.push({
1067
+ severity: "error",
1068
+ path: displayPath(ctx, entryPath),
1069
+ message: `Failed to load module: ${errorMessage(error)}`,
1070
+ });
1071
+ continue;
1072
+ }
1073
+ const kind = getDefinitionKind(mod.default);
1074
+
1075
+ if (kind === "http-channel") {
1076
+ if (id !== "http") {
1077
+ ctx.diagnostics.push({
1078
+ severity: "error",
1079
+ path: displayPath(ctx, entryPath),
1080
+ message:
1081
+ "httpChannel() configures the built-in HTTP channel and must be authored at channels/http.ts.",
1082
+ });
1083
+ continue;
1084
+ }
1085
+ httpChannelDefinition = mod.default as HttpChannelDefinition;
1086
+ continue;
1087
+ }
1088
+
1089
+ if (kind !== "channel") {
1090
+ ctx.diagnostics.push({
1091
+ severity: "error",
1092
+ path: displayPath(ctx, entryPath),
1093
+ message:
1094
+ id === "http"
1095
+ ? "channels/http.ts must default-export httpChannel({ ... })."
1096
+ : "Channel modules must default-export defineChannel({ ... }).",
1097
+ });
1098
+ continue;
1099
+ }
1100
+ if (id === "http") {
1101
+ ctx.diagnostics.push({
1102
+ severity: "error",
1103
+ path: displayPath(ctx, entryPath),
1104
+ message:
1105
+ "The channel id `http` is reserved for the built-in HTTP channel; use httpChannel() to configure it.",
1106
+ });
1107
+ continue;
1108
+ }
1109
+ channels.push({ id, definition: mod.default as ChannelDefinition });
1110
+ }
1111
+ return { channels, httpChannelDefinition };
1112
+ }
1113
+
1114
+ // ============================================================================
1115
+ // Schedules
1116
+ // ============================================================================
1117
+
1118
+ async function loadSchedules(
1119
+ ctx: DiscoveryContext,
1120
+ schedulesDir: string
1121
+ ): Promise<DiscoveredSchedule[]> {
1122
+ if (!(await isDirectory(schedulesDir))) {
1123
+ return [];
1124
+ }
1125
+ const schedules: DiscoveredSchedule[] = [];
1126
+ for (const filePath of await walkFiles(schedulesDir)) {
1127
+ const rel = toPosix(relative(schedulesDir, filePath));
1128
+ if (rel.endsWith(".md")) {
1129
+ const name = rel.slice(0, -".md".length);
1130
+ const { data, body } = parseFrontmatter(await readFile(filePath, "utf8"));
1131
+ const cron = data.cron;
1132
+ if (cron === undefined || !isValidCron(cron)) {
1133
+ ctx.diagnostics.push({
1134
+ severity: "error",
1135
+ path: displayPath(ctx, filePath),
1136
+ message:
1137
+ cron === undefined
1138
+ ? "Markdown schedules require `cron` frontmatter."
1139
+ : `Invalid cron expression: "${cron}".`,
1140
+ });
1141
+ continue;
1142
+ }
1143
+ const markdown = body.trim();
1144
+ if (markdown === "") {
1145
+ ctx.diagnostics.push({
1146
+ severity: "error",
1147
+ path: displayPath(ctx, filePath),
1148
+ message: "Markdown schedules require a non-empty prompt body.",
1149
+ });
1150
+ continue;
1151
+ }
1152
+ schedules.push({
1153
+ name,
1154
+ cron,
1155
+ kind: "markdown",
1156
+ definition: {
1157
+ __agentServe: "schedule",
1158
+ cron,
1159
+ markdown,
1160
+ },
1161
+ });
1162
+ continue;
1163
+ }
1164
+
1165
+ if (!isModuleFile(basename(filePath))) {
1166
+ continue;
1167
+ }
1168
+ const name = rel.slice(0, -extname(rel).length);
1169
+ const definition = await importDefault(ctx, filePath, "schedule");
1170
+ if (definition === undefined) {
1171
+ continue;
1172
+ }
1173
+ const schedule = definition as ScheduleDefinition;
1174
+ if ((schedule.markdown === undefined) === (schedule.run === undefined)) {
1175
+ ctx.diagnostics.push({
1176
+ severity: "error",
1177
+ path: displayPath(ctx, filePath),
1178
+ message: "Schedules require exactly one of `markdown` or `run`.",
1179
+ });
1180
+ continue;
1181
+ }
1182
+ if (!isValidCron(schedule.cron)) {
1183
+ ctx.diagnostics.push({
1184
+ severity: "error",
1185
+ path: displayPath(ctx, filePath),
1186
+ message: `Invalid cron expression: "${schedule.cron}".`,
1187
+ });
1188
+ continue;
1189
+ }
1190
+ schedules.push({
1191
+ name,
1192
+ cron: schedule.cron,
1193
+ kind: schedule.run === undefined ? "markdown" : "run",
1194
+ definition: schedule,
1195
+ });
1196
+ }
1197
+ return schedules;
1198
+ }
1199
+
1200
+ // ============================================================================
1201
+ // Hooks
1202
+ // ============================================================================
1203
+
1204
+ async function loadHooks(
1205
+ ctx: DiscoveryContext,
1206
+ hooksDir: string
1207
+ ): Promise<DiscoveredHook[]> {
1208
+ if (!(await isDirectory(hooksDir))) {
1209
+ return [];
1210
+ }
1211
+ const hooks: DiscoveredHook[] = [];
1212
+ for (const filePath of await walkFiles(hooksDir)) {
1213
+ if (!isModuleFile(basename(filePath))) {
1214
+ continue;
1215
+ }
1216
+ const rel = toPosix(relative(hooksDir, filePath));
1217
+ const name = rel.slice(0, -extname(rel).length);
1218
+ const definition = await importDefault(ctx, filePath, "hook");
1219
+ if (definition === undefined) {
1220
+ continue;
1221
+ }
1222
+ const hook = definition as HookDefinition;
1223
+ if (
1224
+ typeof hook.events !== "object" ||
1225
+ hook.events === null ||
1226
+ Object.keys(hook.events).length === 0
1227
+ ) {
1228
+ ctx.diagnostics.push({
1229
+ severity: "warning",
1230
+ path: displayPath(ctx, filePath),
1231
+ message: "Hook declares no event handlers.",
1232
+ });
1233
+ continue;
1234
+ }
1235
+ hooks.push({ name, definition: hook });
1236
+ }
1237
+ return hooks;
1238
+ }
1239
+
1240
+ // ============================================================================
1241
+ // A/B metrics (agent/ab.ts or agent/ab/*.ts) + agent/ab.config.ts
1242
+ // ============================================================================
1243
+
1244
+ async function loadABs(
1245
+ ctx: DiscoveryContext,
1246
+ agentDir: string
1247
+ ): Promise<DiscoveredAB[]> {
1248
+ const abs: DiscoveredAB[] = [];
1249
+ const seen = new Set<string>();
1250
+
1251
+ const single = await findModuleFile(agentDir, "ab");
1252
+ if (single !== undefined) {
1253
+ const definition = await importDefault(ctx, single, "ab");
1254
+ if (definition !== undefined) {
1255
+ const name = resolveABName(definition, "ab");
1256
+ seen.add(name);
1257
+ abs.push({
1258
+ name,
1259
+ definition: definition as DiscoveredAB["definition"],
1260
+ });
1261
+ }
1262
+ }
1263
+
1264
+ const absDir = join(agentDir, "ab");
1265
+ if (await isDirectory(absDir)) {
1266
+ for (const filePath of await walkFiles(absDir)) {
1267
+ if (!isModuleFile(basename(filePath))) {
1268
+ continue;
1269
+ }
1270
+ const rel = toPosix(relative(absDir, filePath));
1271
+ const stem = rel.slice(0, -extname(rel).length);
1272
+ const definition = await importDefault(ctx, filePath, "ab");
1273
+ if (definition === undefined) {
1274
+ continue;
1275
+ }
1276
+ const name = resolveABName(definition, stem);
1277
+ if (seen.has(name)) {
1278
+ ctx.diagnostics.push({
1279
+ severity: "error",
1280
+ path: displayPath(ctx, filePath),
1281
+ message: `Duplicate A/B experiment name ${JSON.stringify(name)}.`,
1282
+ });
1283
+ continue;
1284
+ }
1285
+ seen.add(name);
1286
+ abs.push({
1287
+ name,
1288
+ definition: definition as DiscoveredAB["definition"],
1289
+ });
1290
+ }
1291
+ }
1292
+
1293
+ return abs;
1294
+ }
1295
+
1296
+ async function loadABConfig(
1297
+ ctx: DiscoveryContext,
1298
+ agentDir: string
1299
+ ): Promise<ABConfigFile | undefined> {
1300
+ const path = await findModuleFile(agentDir, "ab.config");
1301
+ if (path === undefined) {
1302
+ return undefined;
1303
+ }
1304
+ try {
1305
+ const mod = await ctx.importModule(path);
1306
+ if (mod.default === null || typeof mod.default !== "object") {
1307
+ ctx.diagnostics.push({
1308
+ severity: "error",
1309
+ path: displayPath(ctx, path),
1310
+ message:
1311
+ "ab.config.ts must default-export defineABConfig({ ... }) from @anysphere/agent-serve/ab.",
1312
+ });
1313
+ return undefined;
1314
+ }
1315
+ return mod.default as ABConfigFile;
1316
+ } catch (error) {
1317
+ ctx.diagnostics.push({
1318
+ severity: "error",
1319
+ path: displayPath(ctx, path),
1320
+ message: `Failed to load ab.config: ${error instanceof Error ? error.message : String(error)}`,
1321
+ });
1322
+ return undefined;
1323
+ }
1324
+ }
1325
+
1326
+ function resolveABName(definition: unknown, fallback: string): string {
1327
+ if (
1328
+ typeof definition === "object" &&
1329
+ definition !== null &&
1330
+ "name" in definition &&
1331
+ typeof (definition as { name: unknown }).name === "string" &&
1332
+ (definition as { name: string }).name.length > 0
1333
+ ) {
1334
+ return (definition as { name: string }).name;
1335
+ }
1336
+ return fallback;
1337
+ }
1338
+
1339
+ // ============================================================================
1340
+ // Sandbox seed files
1341
+ // ============================================================================
1342
+
1343
+ async function loadSeedFiles(
1344
+ ctx: DiscoveryContext,
1345
+ agentDir: string
1346
+ ): Promise<SeedFile[]> {
1347
+ const sandboxDir = join(agentDir, "sandbox");
1348
+ for (const candidate of ["sandbox.ts", join("sandbox", "sandbox.ts")]) {
1349
+ if (await isFile(join(agentDir, candidate))) {
1350
+ ctx.diagnostics.push({
1351
+ severity: "warning",
1352
+ path: displayPath(ctx, join(agentDir, candidate)),
1353
+ message:
1354
+ "Custom sandbox backends are not supported; the agent runs in the session workspace directory. Ignoring.",
1355
+ });
1356
+ }
1357
+ }
1358
+ const workspaceDir = join(sandboxDir, "workspace");
1359
+ if (!(await isDirectory(workspaceDir))) {
1360
+ return [];
1361
+ }
1362
+ const seedFiles: SeedFile[] = [];
1363
+ for (const filePath of await walkFiles(workspaceDir)) {
1364
+ seedFiles.push({
1365
+ relativePath: toPosix(relative(workspaceDir, filePath)),
1366
+ sourcePath: filePath,
1367
+ });
1368
+ }
1369
+ return seedFiles;
1370
+ }
1371
+
1372
+ // ============================================================================
1373
+ // Info projection
1374
+ // ============================================================================
1375
+
1376
+ export function projectInfo(project: AgentProject): AgentProjectInfo {
1377
+ const agent = project.agent;
1378
+ const httpRoutes: AgentProjectInfo["channels"][number]["routes"] = [
1379
+ { method: "POST", path: "/v1/session", url: "/v1/session" },
1380
+ {
1381
+ method: "POST",
1382
+ path: "/v1/session/:sessionId",
1383
+ url: "/v1/session/:sessionId",
1384
+ },
1385
+ {
1386
+ method: "POST",
1387
+ path: "/v1/session/:sessionId/stop",
1388
+ url: "/v1/session/:sessionId/stop",
1389
+ },
1390
+ {
1391
+ method: "GET",
1392
+ path: "/v1/session/:sessionId/stream",
1393
+ url: "/v1/session/:sessionId/stream",
1394
+ },
1395
+ {
1396
+ method: "GET",
1397
+ path: "/v1/session/:sessionId/approvals",
1398
+ url: "/v1/session/:sessionId/approvals",
1399
+ },
1400
+ {
1401
+ method: "POST",
1402
+ path: "/v1/session/:sessionId/approvals/:callId",
1403
+ url: "/v1/session/:sessionId/approvals/:callId",
1404
+ },
1405
+ ];
1406
+ return {
1407
+ name: project.name,
1408
+ model: agent.model,
1409
+ runtime: agent.runtime,
1410
+ cloud:
1411
+ agent.cloud === undefined ? undefined : toPublicCloudInfo(agent.cloud),
1412
+ local: agent.local,
1413
+ mcp: { path: MCP_ENDPOINT_PATH, tools: mcpEndpointToolNames(project) },
1414
+ instructions:
1415
+ agent.instructions === undefined
1416
+ ? undefined
1417
+ : {
1418
+ chars: agent.instructions.length,
1419
+ preview:
1420
+ agent.instructions.length > 280
1421
+ ? `${agent.instructions.slice(0, 277)}...`
1422
+ : agent.instructions,
1423
+ },
1424
+ tools: agent.tools.map((tool) => ({
1425
+ name: tool.name,
1426
+ description: tool.description,
1427
+ execution: tool.execution,
1428
+ needsApproval: tool.needsApproval,
1429
+ inputSchema: tool.inputSchema,
1430
+ })),
1431
+ skills: agent.skills.map((skill) => ({
1432
+ name: skill.name,
1433
+ description: skill.description,
1434
+ })),
1435
+ connections: agent.connections.map((connection) => ({
1436
+ name: connection.name,
1437
+ transport: connectionTransportLabel(connection.transport),
1438
+ })),
1439
+ subagents: agent.subagents.map((subagent) => ({
1440
+ name: subagent.name,
1441
+ description: subagent.description,
1442
+ model: subagent.model,
1443
+ })),
1444
+ channels: [
1445
+ { id: "http", routes: httpRoutes },
1446
+ ...project.channels.map((channel) => {
1447
+ const info: AgentProjectInfo["channels"][number] = {
1448
+ id: channel.id,
1449
+ routes: (channel.definition.routes ?? []).map((r) => {
1450
+ const routeInfo: AgentProjectInfo["channels"][number]["routes"][number] =
1451
+ {
1452
+ method: r.method,
1453
+ path: r.path,
1454
+ url: `/v1/channels/${channel.id}${r.path}`,
1455
+ };
1456
+ if (r.description !== undefined) {
1457
+ routeInfo.description = r.description;
1458
+ }
1459
+ if (r.bodySchema !== undefined) {
1460
+ routeInfo.bodySchema = toolInputJsonSchema(r.bodySchema);
1461
+ }
1462
+ if (r.querySchema !== undefined) {
1463
+ routeInfo.querySchema = toolInputJsonSchema(r.querySchema);
1464
+ }
1465
+ return routeInfo;
1466
+ }),
1467
+ };
1468
+ const meta = channel.definition.meta;
1469
+ if (meta?.kind !== undefined) {
1470
+ info.kind = meta.kind;
1471
+ }
1472
+ if (meta?.githubWebhookEvents !== undefined) {
1473
+ info.githubWebhookEvents = meta.githubWebhookEvents;
1474
+ }
1475
+ if (meta?.slackChannelPosts !== undefined) {
1476
+ info.slackChannelPosts = meta.slackChannelPosts;
1477
+ }
1478
+ return info;
1479
+ }),
1480
+ ],
1481
+ schedules: project.schedules.map((schedule) => ({
1482
+ name: schedule.name,
1483
+ cron: schedule.cron,
1484
+ kind: schedule.kind,
1485
+ })),
1486
+ hooks: project.hooks.map((hook) => hook.name),
1487
+ abs: project.abs.map((experiment) => experiment.name),
1488
+ ...(project.abConfig === undefined && project.abs.length === 0
1489
+ ? {}
1490
+ : {
1491
+ abConfig: {
1492
+ maxPlaygroundSessions: resolveABMaxPlaygroundSessions(
1493
+ project.abConfig?.maxPlaygroundSessions
1494
+ ),
1495
+ durableSamples: project.abConfig?.persistSamples !== undefined,
1496
+ durableSnapshots: project.abConfig?.persistSnapshots !== undefined,
1497
+ },
1498
+ }),
1499
+ diagnostics: project.diagnostics,
1500
+ };
1501
+ }
1502
+
1503
+ // ============================================================================
1504
+ // Shared helpers
1505
+ // ============================================================================
1506
+
1507
+ async function importDefault(
1508
+ ctx: DiscoveryContext,
1509
+ modulePath: string,
1510
+ expectedKind:
1511
+ | "agent"
1512
+ | "tool"
1513
+ | "skill"
1514
+ | "connection"
1515
+ | "schedule"
1516
+ | "hook"
1517
+ | "ab"
1518
+ ): Promise<unknown> {
1519
+ let mod: Record<string, unknown>;
1520
+ try {
1521
+ mod = await ctx.importModule(modulePath);
1522
+ } catch (error) {
1523
+ ctx.diagnostics.push({
1524
+ severity: "error",
1525
+ path: displayPath(ctx, modulePath),
1526
+ message: `Failed to load module: ${errorMessage(error)}`,
1527
+ });
1528
+ return undefined;
1529
+ }
1530
+ const kind = getDefinitionKind(mod.default);
1531
+ if (kind !== expectedKind) {
1532
+ const helper =
1533
+ expectedKind === "ab"
1534
+ ? "defineAB"
1535
+ : `define${expectedKind[0]?.toUpperCase()}${expectedKind.slice(1)}`;
1536
+ ctx.diagnostics.push({
1537
+ severity: "error",
1538
+ path: displayPath(ctx, modulePath),
1539
+ message:
1540
+ kind === undefined
1541
+ ? `Module must default-export ${helper}({ ... }).`
1542
+ : `Expected a ${expectedKind} definition but found "${kind}" (wrong authored slot?).`,
1543
+ });
1544
+ return undefined;
1545
+ }
1546
+ return mod.default;
1547
+ }
1548
+
1549
+ async function warnUnknownSlots(
1550
+ ctx: DiscoveryContext,
1551
+ agentDir: string
1552
+ ): Promise<void> {
1553
+ const known = new Set([
1554
+ "agent",
1555
+ "instructions",
1556
+ "tools",
1557
+ "skills",
1558
+ "mcp-connections",
1559
+ // Legacy slot name; resolveMcpConnectionsDir warns and still loads it.
1560
+ "connections",
1561
+ "subagents",
1562
+ "channels",
1563
+ "schedules",
1564
+ "hooks",
1565
+ "ab",
1566
+ "sandbox",
1567
+ "lib",
1568
+ // Vite playground custom tool chips (examples/*/agent/playground/tools).
1569
+ "playground",
1570
+ ]);
1571
+ for (const entry of await sortedEntries(agentDir)) {
1572
+ if (!entry.isDirectory() || entry.name.startsWith(".")) {
1573
+ continue;
1574
+ }
1575
+ if (entry.name === "node_modules" || known.has(entry.name)) {
1576
+ continue;
1577
+ }
1578
+ // Footgun: evals live at project-root `evals/`, not under `agent/`.
1579
+ if (entry.name === "evals") {
1580
+ ctx.diagnostics.push({
1581
+ severity: "warning",
1582
+ path: displayPath(ctx, join(agentDir, entry.name)),
1583
+ message:
1584
+ 'Directory "evals" under agent/ is ignored — put defineEval files at project-root evals/ instead.',
1585
+ });
1586
+ continue;
1587
+ }
1588
+ ctx.diagnostics.push({
1589
+ severity: "warning",
1590
+ path: displayPath(ctx, join(agentDir, entry.name)),
1591
+ message: `Unrecognized directory "${entry.name}" under agent/ (not an authored slot; use lib/ for shared code).`,
1592
+ });
1593
+ }
1594
+ }
1595
+
1596
+ async function hasFlatLayoutMarkers(rootDir: string): Promise<boolean> {
1597
+ if (await isFile(join(rootDir, "instructions.md"))) {
1598
+ return true;
1599
+ }
1600
+ return (await findModuleFile(rootDir, "agent")) !== undefined;
1601
+ }
1602
+
1603
+ async function readPackageName(rootDir: string): Promise<string | undefined> {
1604
+ const packageJsonPath = join(rootDir, "package.json");
1605
+ if (!(await isFile(packageJsonPath))) {
1606
+ return undefined;
1607
+ }
1608
+ try {
1609
+ const parsed = JSON.parse(await readFile(packageJsonPath, "utf8")) as {
1610
+ name?: unknown;
1611
+ };
1612
+ if (typeof parsed.name === "string" && parsed.name !== "") {
1613
+ return parsed.name;
1614
+ }
1615
+ } catch {
1616
+ // Malformed package.json falls back to the directory name.
1617
+ }
1618
+ return undefined;
1619
+ }
1620
+
1621
+ async function findModuleFile(
1622
+ dir: string,
1623
+ stem: string
1624
+ ): Promise<string | undefined> {
1625
+ for (const extension of [".ts", ".mts", ".js", ".mjs"]) {
1626
+ const candidate = join(dir, `${stem}${extension}`);
1627
+ if (await isFile(candidate)) {
1628
+ return candidate;
1629
+ }
1630
+ }
1631
+ return undefined;
1632
+ }
1633
+
1634
+ function isModuleFile(fileName: string): boolean {
1635
+ if (fileName.endsWith(".d.ts") || fileName.startsWith(".")) {
1636
+ return false;
1637
+ }
1638
+ return MODULE_EXTENSIONS.has(extname(fileName));
1639
+ }
1640
+
1641
+ function moduleStem(fileName: string): string {
1642
+ return fileName.slice(0, -extname(fileName).length);
1643
+ }
1644
+
1645
+ async function sortedEntries(dir: string): Promise<Dirent[]> {
1646
+ const entries = await readdir(dir, { withFileTypes: true });
1647
+ return entries.sort((a, b) => a.name.localeCompare(b.name));
1648
+ }
1649
+
1650
+ async function walkFiles(dir: string): Promise<string[]> {
1651
+ const files: string[] = [];
1652
+ for (const entry of await sortedEntries(dir)) {
1653
+ if (entry.name.startsWith(".")) {
1654
+ continue;
1655
+ }
1656
+ const entryPath = join(dir, entry.name);
1657
+ if (entry.isDirectory()) {
1658
+ files.push(...(await walkFiles(entryPath)));
1659
+ } else if (entry.isFile()) {
1660
+ files.push(entryPath);
1661
+ }
1662
+ }
1663
+ return files;
1664
+ }
1665
+
1666
+ async function isDirectory(path: string): Promise<boolean> {
1667
+ try {
1668
+ return (await stat(path)).isDirectory();
1669
+ } catch {
1670
+ return false;
1671
+ }
1672
+ }
1673
+
1674
+ async function isFile(path: string): Promise<boolean> {
1675
+ try {
1676
+ return (await stat(path)).isFile();
1677
+ } catch {
1678
+ return false;
1679
+ }
1680
+ }
1681
+
1682
+ function isValidModelSetting(model: unknown): model is ModelSetting {
1683
+ if (typeof model === "string") {
1684
+ return model.trim() !== "";
1685
+ }
1686
+ return (
1687
+ typeof model === "object" &&
1688
+ model !== null &&
1689
+ typeof (model as { id?: unknown }).id === "string" &&
1690
+ (model as { id: string }).id.trim() !== ""
1691
+ );
1692
+ }
1693
+
1694
+ function isBlank(value: unknown): boolean {
1695
+ return typeof value !== "string" || value.trim() === "";
1696
+ }
1697
+
1698
+ function toPosix(path: string): string {
1699
+ return path.split("\\").join("/");
1700
+ }
1701
+
1702
+ function displayPath(ctx: DiscoveryContext, absolutePath: string): string {
1703
+ const rel = relative(ctx.rootDir, absolutePath);
1704
+ return rel.startsWith("..") ? absolutePath : toPosix(rel);
1705
+ }
1706
+
1707
+ function errorMessage(error: unknown): string {
1708
+ return error instanceof Error ? error.message : String(error);
1709
+ }