@cursor/july 0.1.100 → 0.1.102

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 (484) hide show
  1. package/README.md +27 -0
  2. package/dist/bin/agent-serve.js +3 -2
  3. package/dist/channels/deployments/deployments-channel.d.ts +41 -5
  4. package/dist/channels/deployments/deployments-channel.d.ts.map +1 -1
  5. package/dist/channels/deployments/deployments-channel.js +88 -8
  6. package/dist/channels/deployments/types.d.ts +4 -3
  7. package/dist/channels/deployments/types.d.ts.map +1 -1
  8. package/dist/channels/origin/defaults.d.ts +43 -0
  9. package/dist/channels/origin/defaults.d.ts.map +1 -0
  10. package/dist/channels/origin/defaults.js +111 -0
  11. package/dist/channels/origin/index.d.ts +2 -0
  12. package/dist/channels/origin/index.d.ts.map +1 -1
  13. package/dist/channels/origin/index.js +2 -0
  14. package/dist/channels/origin/origin-channel.d.ts +2 -1
  15. package/dist/channels/origin/origin-channel.d.ts.map +1 -1
  16. package/dist/channels/origin/origin-channel.js +24 -3
  17. package/dist/channels/origin/state.d.ts +32 -0
  18. package/dist/channels/origin/state.d.ts.map +1 -0
  19. package/dist/channels/origin/state.js +23 -0
  20. package/dist/channels/origin/types.d.ts +23 -1
  21. package/dist/channels/origin/types.d.ts.map +1 -1
  22. package/dist/channels/slack/api.d.ts +1 -1
  23. package/dist/channels/slack/api.d.ts.map +1 -1
  24. package/dist/channels/slack/api.js +5 -2
  25. package/dist/channels/slack/assistant.d.ts +1 -1
  26. package/dist/channels/slack/assistant.d.ts.map +1 -1
  27. package/dist/channels/slack/code-fences.d.ts +17 -0
  28. package/dist/channels/slack/code-fences.d.ts.map +1 -0
  29. package/dist/channels/slack/code-fences.js +168 -0
  30. package/dist/channels/slack/dispatch.d.ts +3 -2
  31. package/dist/channels/slack/dispatch.d.ts.map +1 -1
  32. package/dist/channels/slack/dispatch.js +28 -8
  33. package/dist/channels/slack/inbound.d.ts +8 -0
  34. package/dist/channels/slack/inbound.d.ts.map +1 -1
  35. package/dist/channels/slack/inbound.js +28 -0
  36. package/dist/channels/slack/index.d.ts +1 -0
  37. package/dist/channels/slack/index.d.ts.map +1 -1
  38. package/dist/channels/slack/index.js +1 -0
  39. package/dist/channels/slack/live-delivery.d.ts.map +1 -1
  40. package/dist/channels/slack/live-delivery.js +11 -4
  41. package/dist/channels/slack/manifest.d.ts.map +1 -1
  42. package/dist/channels/slack/manifest.js +2 -1
  43. package/dist/connections.d.ts +18 -2
  44. package/dist/connections.d.ts.map +1 -1
  45. package/dist/connections.js +18 -2
  46. package/dist/docs/404.html +2 -2
  47. package/dist/docs/ab.html +4 -4
  48. package/dist/docs/assets/{app.DZ1e0Ycq.js → app.CLA03RCz.js} +1 -1
  49. package/dist/docs/assets/chunks/@localSearchIndexroot.FAqdWIXo.js +1 -0
  50. package/dist/docs/assets/chunks/{VPLocalSearchBox.l8omwc6D.js → VPLocalSearchBox.ifxv-cJV.js} +1 -1
  51. package/dist/docs/assets/chunks/{theme.JVcD6gel.js → theme.DWCwM6p-.js} +2 -2
  52. package/dist/docs/assets/concepts.md.rPzW2HZN.js +1 -0
  53. package/dist/docs/assets/guides_cloud-agents.md.DumwWXba.js +9 -0
  54. package/dist/docs/assets/guides_cloud-agents.md.DumwWXba.lean.js +1 -0
  55. package/dist/docs/assets/{guides_cloud-runtime.md.CkYbjnAX.js → guides_cloud-runtime.md.BRirbRAN.js} +1 -1
  56. package/dist/docs/assets/index.md.Kiht7ell.js +5 -0
  57. package/dist/docs/assets/{index.md.Bb4k8kUm.lean.js → index.md.Kiht7ell.lean.js} +1 -1
  58. package/dist/docs/assets/{reference_connections.md.BiGoBAk2.js → reference_connections.md.Di6jJAXF.js} +12 -2
  59. package/dist/docs/assets/{reference_connections.md.BiGoBAk2.lean.js → reference_connections.md.Di6jJAXF.lean.js} +1 -1
  60. package/dist/docs/assets/reference_extensions.md.9WQMZe1l.js +56 -0
  61. package/dist/docs/assets/reference_extensions.md.9WQMZe1l.lean.js +1 -0
  62. package/dist/docs/assets/{reference_http-api.md.BEJx9XVj.js → reference_http-api.md.oySXBO8o.js} +1 -1
  63. package/dist/docs/assets/{reference_project-layout.md.Bv4KOtlB.js → reference_project-layout.md.CA6QP6Lw.js} +2 -2
  64. package/dist/docs/assets/{reference_project-layout.md.Bv4KOtlB.lean.js → reference_project-layout.md.CA6QP6Lw.lean.js} +1 -1
  65. package/dist/docs/assets/{reference_skills.md.BFW9retM.js → reference_skills.md.l2ezAcaN.js} +1 -1
  66. package/dist/docs/assets/{reference_tools.md.CvAHsdSp.js → reference_tools.md.B1up2r9W.js} +1 -1
  67. package/dist/docs/assets/skills_framework-map.md.DDa4MeEW.js +1 -0
  68. package/dist/docs/assets/{skills_framework-map.md.BTi817yv.lean.js → skills_framework-map.md.DDa4MeEW.lean.js} +1 -1
  69. package/dist/docs/building-with-agents.html +4 -4
  70. package/dist/docs/concepts.html +5 -5
  71. package/dist/docs/concepts.md +3 -3
  72. package/dist/docs/deployment.html +4 -4
  73. package/dist/docs/evals.html +4 -4
  74. package/dist/docs/guides/agent-to-agent.html +4 -4
  75. package/dist/docs/guides/cloud-agents.html +35 -0
  76. package/dist/docs/guides/cloud-agents.md +93 -0
  77. package/dist/docs/guides/cloud-runtime.html +6 -6
  78. package/dist/docs/guides/cloud-runtime.md +2 -0
  79. package/dist/docs/guides/convert-automation.html +4 -4
  80. package/dist/docs/guides/github.html +4 -4
  81. package/dist/docs/guides/human-in-the-loop.html +4 -4
  82. package/dist/docs/guides/mcp-oauth.html +4 -4
  83. package/dist/docs/guides/opentelemetry.html +5 -5
  84. package/dist/docs/guides/slack.html +4 -4
  85. package/dist/docs/guides/webhooks.html +4 -4
  86. package/dist/docs/hashmap.json +1 -1
  87. package/dist/docs/hillclimbing.html +4 -4
  88. package/dist/docs/index.html +6 -6
  89. package/dist/docs/index.md +5 -1
  90. package/dist/docs/llms-full.txt +353 -5
  91. package/dist/docs/llms.txt +2 -0
  92. package/dist/docs/quickstart.html +4 -4
  93. package/dist/docs/reference/agent-config.html +4 -4
  94. package/dist/docs/reference/artifacts.html +4 -4
  95. package/dist/docs/reference/channels.html +5 -5
  96. package/dist/docs/reference/cli.html +4 -4
  97. package/dist/docs/reference/connections.html +16 -6
  98. package/dist/docs/reference/connections.md +31 -0
  99. package/dist/docs/reference/extensions.html +82 -0
  100. package/dist/docs/reference/extensions.md +200 -0
  101. package/dist/docs/reference/hooks.html +4 -4
  102. package/dist/docs/reference/http-api.html +6 -6
  103. package/dist/docs/reference/http-api.md +1 -1
  104. package/dist/docs/reference/instructions.html +4 -4
  105. package/dist/docs/reference/playground.html +4 -4
  106. package/dist/docs/reference/project-layout.html +6 -6
  107. package/dist/docs/reference/project-layout.md +3 -0
  108. package/dist/docs/reference/prompt.html +4 -4
  109. package/dist/docs/reference/schedules.html +4 -4
  110. package/dist/docs/reference/sessions.html +4 -4
  111. package/dist/docs/reference/skills.html +6 -6
  112. package/dist/docs/reference/skills.md +2 -0
  113. package/dist/docs/reference/subagents.html +5 -5
  114. package/dist/docs/reference/tools.html +6 -6
  115. package/dist/docs/reference/tools.md +2 -0
  116. package/dist/docs/scaffolding-agents.html +4 -4
  117. package/dist/docs/skills/ab.html +4 -4
  118. package/dist/docs/skills/create-agent.html +4 -4
  119. package/dist/docs/skills/debug.html +4 -4
  120. package/dist/docs/skills/evals.html +4 -4
  121. package/dist/docs/skills/framework-map.html +5 -5
  122. package/dist/docs/skills/framework-map.md +1 -0
  123. package/dist/docs/skills/github.html +4 -4
  124. package/dist/docs/skills/hillclimb.html +4 -4
  125. package/dist/docs/skills/index.html +4 -4
  126. package/dist/docs/skills/mcp-auth.html +4 -4
  127. package/dist/docs/skills/otel.html +4 -4
  128. package/dist/docs/skills/setup-slack.html +4 -4
  129. package/dist/docs/storage.html +4 -4
  130. package/dist/docs/templates/agentic-owners.html +4 -4
  131. package/dist/docs/templates/agents-md.html +4 -4
  132. package/dist/docs/templates/code-wiki.html +4 -4
  133. package/dist/docs/templates/demo.html +4 -4
  134. package/dist/docs/templates/pr-autofixer.html +4 -4
  135. package/dist/docs/templates/security-help.html +4 -4
  136. package/dist/docs/templates/security-reviewer.html +4 -4
  137. package/dist/docs/templates/triage.html +4 -4
  138. package/dist/docs/troubleshooting.html +4 -4
  139. package/dist/extensions/cursor-cloud-agents/extension.d.ts +57 -0
  140. package/dist/extensions/cursor-cloud-agents/extension.d.ts.map +1 -0
  141. package/dist/extensions/cursor-cloud-agents/extension.js +66 -0
  142. package/dist/extensions/cursor-cloud-agents/lib/backend.d.ts +78 -0
  143. package/dist/extensions/cursor-cloud-agents/lib/backend.d.ts.map +1 -0
  144. package/dist/extensions/cursor-cloud-agents/lib/backend.js +286 -0
  145. package/dist/extensions/cursor-cloud-agents/lib/fleet.d.ts +25 -0
  146. package/dist/extensions/cursor-cloud-agents/lib/fleet.d.ts.map +1 -0
  147. package/dist/extensions/cursor-cloud-agents/lib/fleet.js +85 -0
  148. package/dist/extensions/cursor-cloud-agents/lib/inputs.d.ts +5 -0
  149. package/dist/extensions/cursor-cloud-agents/lib/inputs.d.ts.map +1 -0
  150. package/dist/extensions/cursor-cloud-agents/lib/inputs.js +4 -0
  151. package/dist/extensions/cursor-cloud-agents/lib/repos.d.ts +15 -0
  152. package/dist/extensions/cursor-cloud-agents/lib/repos.d.ts.map +1 -0
  153. package/dist/extensions/cursor-cloud-agents/lib/repos.js +55 -0
  154. package/dist/extensions/cursor-cloud-agents/lib/transcript.d.ts +8 -0
  155. package/dist/extensions/cursor-cloud-agents/lib/transcript.d.ts.map +1 -0
  156. package/dist/extensions/cursor-cloud-agents/lib/transcript.js +15 -0
  157. package/dist/extensions/cursor-cloud-agents/skills/handoff.md +41 -0
  158. package/dist/extensions/cursor-cloud-agents/tools/cancel.d.ts +4 -0
  159. package/dist/extensions/cursor-cloud-agents/tools/cancel.d.ts.map +1 -0
  160. package/dist/extensions/cursor-cloud-agents/tools/cancel.js +26 -0
  161. package/dist/extensions/cursor-cloud-agents/tools/dump.d.ts +4 -0
  162. package/dist/extensions/cursor-cloud-agents/tools/dump.d.ts.map +1 -0
  163. package/dist/extensions/cursor-cloud-agents/tools/dump.js +54 -0
  164. package/dist/extensions/cursor-cloud-agents/tools/get.d.ts +4 -0
  165. package/dist/extensions/cursor-cloud-agents/tools/get.d.ts.map +1 -0
  166. package/dist/extensions/cursor-cloud-agents/tools/get.js +25 -0
  167. package/dist/extensions/cursor-cloud-agents/tools/launch.d.ts +4 -0
  168. package/dist/extensions/cursor-cloud-agents/tools/launch.d.ts.map +1 -0
  169. package/dist/extensions/cursor-cloud-agents/tools/launch.js +59 -0
  170. package/dist/extensions/cursor-cloud-agents/tools/list.d.ts +4 -0
  171. package/dist/extensions/cursor-cloud-agents/tools/list.d.ts.map +1 -0
  172. package/dist/extensions/cursor-cloud-agents/tools/list.js +36 -0
  173. package/dist/extensions/cursor-cloud-agents/tools/reply.d.ts +4 -0
  174. package/dist/extensions/cursor-cloud-agents/tools/reply.d.ts.map +1 -0
  175. package/dist/extensions/cursor-cloud-agents/tools/reply.js +35 -0
  176. package/dist/extensions.d.ts +73 -0
  177. package/dist/extensions.d.ts.map +1 -0
  178. package/dist/extensions.js +112 -0
  179. package/dist/files-backends/cursor-hosted.d.ts +0 -8
  180. package/dist/files-backends/cursor-hosted.d.ts.map +1 -1
  181. package/dist/files-backends/cursor-hosted.js +1 -11
  182. package/dist/files.d.ts +5 -1
  183. package/dist/files.d.ts.map +1 -1
  184. package/dist/files.js +22 -18
  185. package/dist/index.d.ts +4 -3
  186. package/dist/index.d.ts.map +1 -1
  187. package/dist/index.js +3 -2
  188. package/dist/internal/advertise-tools.d.ts +4 -1
  189. package/dist/internal/advertise-tools.d.ts.map +1 -1
  190. package/dist/internal/advertise-tools.js +20 -1
  191. package/dist/internal/as-of.d.ts +18 -0
  192. package/dist/internal/as-of.d.ts.map +1 -1
  193. package/dist/internal/as-of.js +131 -0
  194. package/dist/internal/cli-deploy.d.ts.map +1 -1
  195. package/dist/internal/cli-deploy.js +64 -19
  196. package/dist/internal/continuation-identity.d.ts.map +1 -1
  197. package/dist/internal/continuation-identity.js +3 -1
  198. package/dist/internal/conversation-mirror.d.ts.map +1 -1
  199. package/dist/internal/conversation-mirror.js +13 -15
  200. package/dist/internal/cursor/backend-client.d.ts +8 -1
  201. package/dist/internal/cursor/backend-client.d.ts.map +1 -1
  202. package/dist/internal/cursor/backend-client.js +25 -9
  203. package/dist/internal/cursor/hosted-deployment-secrets.d.ts +16 -0
  204. package/dist/internal/cursor/hosted-deployment-secrets.d.ts.map +1 -0
  205. package/dist/internal/cursor/hosted-deployment-secrets.js +61 -0
  206. package/dist/internal/cursor/hosted-store-secrets.d.ts +11 -0
  207. package/dist/internal/cursor/hosted-store-secrets.d.ts.map +1 -1
  208. package/dist/internal/cursor/hosted-store-secrets.js +15 -0
  209. package/dist/internal/cursor-deployment-relay.d.ts +15 -1
  210. package/dist/internal/cursor-deployment-relay.d.ts.map +1 -1
  211. package/dist/internal/cursor-deployment-relay.js +46 -7
  212. package/dist/internal/deploy-client.d.ts +23 -0
  213. package/dist/internal/deploy-client.d.ts.map +1 -1
  214. package/dist/internal/deploy-client.js +54 -12
  215. package/dist/internal/discovery/ab.d.ts +9 -0
  216. package/dist/internal/discovery/ab.d.ts.map +1 -0
  217. package/dist/internal/discovery/ab.js +101 -0
  218. package/dist/internal/discovery/agent-config.d.ts +35 -0
  219. package/dist/internal/discovery/agent-config.d.ts.map +1 -0
  220. package/dist/internal/discovery/agent-config.js +173 -0
  221. package/dist/internal/discovery/agent.d.ts +11 -0
  222. package/dist/internal/discovery/agent.d.ts.map +1 -0
  223. package/dist/internal/discovery/agent.js +306 -0
  224. package/dist/internal/discovery/channels.d.ts +10 -0
  225. package/dist/internal/discovery/channels.d.ts.map +1 -0
  226. package/dist/internal/discovery/channels.js +97 -0
  227. package/dist/internal/discovery/connections.d.ts +17 -0
  228. package/dist/internal/discovery/connections.d.ts.map +1 -0
  229. package/dist/internal/discovery/connections.js +436 -0
  230. package/dist/internal/discovery/context.d.ts +20 -0
  231. package/dist/internal/discovery/context.d.ts.map +1 -0
  232. package/dist/internal/discovery/context.js +30 -0
  233. package/dist/internal/discovery/cursor-plugin.d.ts +25 -0
  234. package/dist/internal/discovery/cursor-plugin.d.ts.map +1 -0
  235. package/dist/internal/discovery/cursor-plugin.js +570 -0
  236. package/dist/internal/discovery/extensions.d.ts +19 -0
  237. package/dist/internal/discovery/extensions.d.ts.map +1 -0
  238. package/dist/internal/discovery/extensions.js +238 -0
  239. package/dist/internal/discovery/fs.d.ts +15 -0
  240. package/dist/internal/discovery/fs.d.ts.map +1 -0
  241. package/dist/internal/discovery/fs.js +115 -0
  242. package/dist/internal/discovery/hooks.d.ts +7 -0
  243. package/dist/internal/discovery/hooks.d.ts.map +1 -0
  244. package/dist/internal/discovery/hooks.js +48 -0
  245. package/dist/internal/discovery/info.d.ts +6 -0
  246. package/dist/internal/discovery/info.d.ts.map +1 -0
  247. package/dist/internal/discovery/info.js +141 -0
  248. package/dist/internal/discovery/instructions.d.ts +7 -0
  249. package/dist/internal/discovery/instructions.d.ts.map +1 -0
  250. package/dist/internal/discovery/instructions.js +95 -0
  251. package/dist/internal/discovery/modules.d.ts +20 -0
  252. package/dist/internal/discovery/modules.d.ts.map +1 -0
  253. package/dist/internal/discovery/modules.js +101 -0
  254. package/dist/internal/discovery/names.d.ts +12 -0
  255. package/dist/internal/discovery/names.d.ts.map +1 -0
  256. package/dist/internal/discovery/names.js +36 -0
  257. package/dist/internal/discovery/project.d.ts +53 -0
  258. package/dist/internal/discovery/project.d.ts.map +1 -0
  259. package/dist/internal/discovery/project.js +239 -0
  260. package/dist/internal/discovery/runtime.d.ts +34 -0
  261. package/dist/internal/discovery/runtime.d.ts.map +1 -0
  262. package/dist/internal/discovery/runtime.js +98 -0
  263. package/dist/internal/discovery/schedules.d.ts +7 -0
  264. package/dist/internal/discovery/schedules.d.ts.map +1 -0
  265. package/dist/internal/discovery/schedules.js +97 -0
  266. package/dist/internal/discovery/seed-files.d.ts +7 -0
  267. package/dist/internal/discovery/seed-files.d.ts.map +1 -0
  268. package/dist/internal/discovery/seed-files.js +41 -0
  269. package/dist/internal/discovery/services.d.ts +17 -0
  270. package/dist/internal/discovery/services.d.ts.map +1 -0
  271. package/dist/internal/discovery/services.js +104 -0
  272. package/dist/internal/discovery/skills.d.ts +16 -0
  273. package/dist/internal/discovery/skills.d.ts.map +1 -0
  274. package/dist/internal/discovery/skills.js +151 -0
  275. package/dist/internal/discovery/subagent.d.ts +13 -0
  276. package/dist/internal/discovery/subagent.d.ts.map +1 -0
  277. package/dist/internal/discovery/subagent.js +19 -0
  278. package/dist/internal/discovery/tools.d.ts +18 -0
  279. package/dist/internal/discovery/tools.d.ts.map +1 -0
  280. package/dist/internal/discovery/tools.js +159 -0
  281. package/dist/internal/discovery.d.ts +8 -62
  282. package/dist/internal/discovery.d.ts.map +1 -1
  283. package/dist/internal/discovery.js +8 -2210
  284. package/dist/internal/distribution.d.ts.map +1 -1
  285. package/dist/internal/distribution.js +5 -0
  286. package/dist/internal/framework-file-storage.d.ts +3 -3
  287. package/dist/internal/framework-file-storage.d.ts.map +1 -1
  288. package/dist/internal/framework-file-storage.js +38 -9
  289. package/dist/internal/framework-storage-selection.d.ts.map +1 -1
  290. package/dist/internal/framework-storage-selection.js +8 -7
  291. package/dist/internal/frontmatter.d.ts +11 -3
  292. package/dist/internal/frontmatter.d.ts.map +1 -1
  293. package/dist/internal/frontmatter.js +29 -3
  294. package/dist/internal/hosted-delivery.d.ts +3 -1
  295. package/dist/internal/hosted-delivery.d.ts.map +1 -1
  296. package/dist/internal/hosted-delivery.js +19 -32
  297. package/dist/internal/hosted-execution-diag.d.ts +5 -0
  298. package/dist/internal/hosted-execution-diag.d.ts.map +1 -1
  299. package/dist/internal/hosted-execution-diag.js +5 -0
  300. package/dist/internal/hosted-execution-flush.d.ts +46 -0
  301. package/dist/internal/hosted-execution-flush.d.ts.map +1 -0
  302. package/dist/internal/hosted-execution-flush.js +81 -0
  303. package/dist/internal/mcp-endpoint.js +1 -0
  304. package/dist/internal/mcp-host.d.ts +2 -1
  305. package/dist/internal/mcp-host.d.ts.map +1 -1
  306. package/dist/internal/mcp-host.js +66 -29
  307. package/dist/internal/otel/collector.d.ts +13 -0
  308. package/dist/internal/otel/collector.d.ts.map +1 -1
  309. package/dist/internal/otel/collector.js +84 -17
  310. package/dist/internal/otel/http.d.ts.map +1 -1
  311. package/dist/internal/otel/http.js +2 -10
  312. package/dist/internal/otel/mcp.d.ts +16 -0
  313. package/dist/internal/otel/mcp.d.ts.map +1 -0
  314. package/dist/internal/otel/mcp.js +68 -0
  315. package/dist/internal/otel/propagation.d.ts +6 -0
  316. package/dist/internal/otel/propagation.d.ts.map +1 -0
  317. package/dist/internal/otel/propagation.js +14 -0
  318. package/dist/internal/otel/runtime.d.ts.map +1 -1
  319. package/dist/internal/otel/runtime.js +2 -0
  320. package/dist/internal/review-comments.d.ts +1 -1
  321. package/dist/internal/review-comments.d.ts.map +1 -1
  322. package/dist/internal/review-comments.js +4 -4
  323. package/dist/internal/server.d.ts +3 -0
  324. package/dist/internal/server.d.ts.map +1 -1
  325. package/dist/internal/server.js +79 -104
  326. package/dist/internal/session-engine.d.ts +14 -7
  327. package/dist/internal/session-engine.d.ts.map +1 -1
  328. package/dist/internal/session-engine.js +113 -66
  329. package/dist/internal/session-run-log.d.ts +3 -1
  330. package/dist/internal/session-run-log.d.ts.map +1 -1
  331. package/dist/internal/session-run-log.js +14 -14
  332. package/dist/internal/start-log-store.d.ts +60 -0
  333. package/dist/internal/start-log-store.d.ts.map +1 -0
  334. package/dist/internal/start-log-store.js +149 -0
  335. package/dist/internal/storage-coordinator.d.ts +4 -1
  336. package/dist/internal/storage-coordinator.d.ts.map +1 -1
  337. package/dist/internal/storage-coordinator.js +15 -13
  338. package/dist/internal/tool-catalog.d.ts.map +1 -1
  339. package/dist/internal/tool-catalog.js +9 -6
  340. package/dist/internal/tool-policy.d.ts +8 -0
  341. package/dist/internal/tool-policy.d.ts.map +1 -1
  342. package/dist/internal/tool-policy.js +19 -7
  343. package/dist/internal/turn-cost.d.ts +14 -0
  344. package/dist/internal/turn-cost.d.ts.map +1 -1
  345. package/dist/internal/turn-cost.js +41 -9
  346. package/dist/internal/v2-hosting.d.ts +22 -0
  347. package/dist/internal/v2-hosting.d.ts.map +1 -0
  348. package/dist/internal/v2-hosting.js +23 -0
  349. package/dist/otel.d.ts +16 -0
  350. package/dist/otel.d.ts.map +1 -1
  351. package/dist/otel.js +26 -0
  352. package/dist/playground/assets/index-B6C9S9nG.css +1 -0
  353. package/dist/playground/assets/{index-DVs98vPL.js → index-ZqNoT8Ie.js} +45 -45
  354. package/dist/playground/index.html +2 -2
  355. package/dist/storage-protocol.d.ts +47 -22
  356. package/dist/storage-protocol.d.ts.map +1 -1
  357. package/dist/storage-protocol.js +90 -54
  358. package/dist/storage.d.ts +2 -2
  359. package/dist/storage.d.ts.map +1 -1
  360. package/dist/storage.js +1 -1
  361. package/dist/tools.d.ts +9 -0
  362. package/dist/tools.d.ts.map +1 -1
  363. package/dist/tools.js +9 -0
  364. package/dist/types.d.ts +154 -6
  365. package/dist/types.d.ts.map +1 -1
  366. package/dist/types.js +29 -1
  367. package/docs/README.md +5 -1
  368. package/docs/concepts.md +3 -3
  369. package/docs/guides/cloud-agents.md +98 -0
  370. package/docs/guides/cloud-runtime.md +2 -0
  371. package/docs/reference/connections.md +31 -0
  372. package/docs/reference/extensions.md +205 -0
  373. package/docs/reference/http-api.md +1 -1
  374. package/docs/reference/project-layout.md +3 -0
  375. package/docs/reference/skills.md +2 -0
  376. package/docs/reference/tools.md +2 -0
  377. package/package.json +18 -4
  378. package/skills/framework-map/SKILL.md +1 -0
  379. package/src/bin/agent-serve.ts +11 -2
  380. package/src/channels/deployments/deployments-channel.ts +117 -8
  381. package/src/channels/deployments/types.ts +4 -3
  382. package/src/channels/origin/defaults.ts +143 -0
  383. package/src/channels/origin/index.ts +2 -0
  384. package/src/channels/origin/origin-channel.ts +37 -1
  385. package/src/channels/origin/state.ts +54 -0
  386. package/src/channels/origin/types.ts +24 -0
  387. package/src/channels/slack/api.ts +6 -3
  388. package/src/channels/slack/assistant.ts +1 -1
  389. package/src/channels/slack/code-fences.ts +183 -0
  390. package/src/channels/slack/dispatch.ts +27 -2
  391. package/src/channels/slack/inbound.ts +37 -0
  392. package/src/channels/slack/index.ts +1 -0
  393. package/src/channels/slack/live-delivery.ts +16 -13
  394. package/src/channels/slack/manifest.ts +2 -1
  395. package/src/connections.ts +18 -1
  396. package/src/extensions/cursor-cloud-agents/extension.ts +120 -0
  397. package/src/extensions/cursor-cloud-agents/lib/backend.ts +395 -0
  398. package/src/extensions/cursor-cloud-agents/lib/fleet.ts +109 -0
  399. package/src/extensions/cursor-cloud-agents/lib/inputs.ts +5 -0
  400. package/src/extensions/cursor-cloud-agents/lib/repos.ts +81 -0
  401. package/src/extensions/cursor-cloud-agents/lib/transcript.ts +23 -0
  402. package/src/extensions/cursor-cloud-agents/skills/handoff.md +41 -0
  403. package/src/extensions/cursor-cloud-agents/tools/cancel.ts +19 -0
  404. package/src/extensions/cursor-cloud-agents/tools/dump.ts +51 -0
  405. package/src/extensions/cursor-cloud-agents/tools/get.ts +18 -0
  406. package/src/extensions/cursor-cloud-agents/tools/launch.ts +64 -0
  407. package/src/extensions/cursor-cloud-agents/tools/list.ts +31 -0
  408. package/src/extensions/cursor-cloud-agents/tools/reply.ts +34 -0
  409. package/src/extensions.ts +141 -0
  410. package/src/files-backends/cursor-hosted.ts +1 -17
  411. package/src/files.ts +43 -34
  412. package/src/index.ts +11 -0
  413. package/src/internal/advertise-tools.ts +37 -12
  414. package/src/internal/as-of.ts +163 -0
  415. package/src/internal/cli-deploy.ts +86 -23
  416. package/src/internal/continuation-identity.ts +3 -1
  417. package/src/internal/conversation-mirror.ts +23 -18
  418. package/src/internal/cursor/backend-client.ts +32 -10
  419. package/src/internal/cursor/hosted-deployment-secrets.ts +72 -0
  420. package/src/internal/cursor/hosted-store-secrets.ts +27 -0
  421. package/src/internal/cursor-deployment-relay.ts +65 -8
  422. package/src/internal/deploy-client.ts +73 -18
  423. package/src/internal/discovery/ab.ts +111 -0
  424. package/src/internal/discovery/agent-config.ts +222 -0
  425. package/src/internal/discovery/agent.ts +360 -0
  426. package/src/internal/discovery/channels.ts +103 -0
  427. package/src/internal/discovery/connections.ts +540 -0
  428. package/src/internal/discovery/context.ts +46 -0
  429. package/src/internal/discovery/cursor-plugin.ts +708 -0
  430. package/src/internal/discovery/extensions.ts +348 -0
  431. package/src/internal/discovery/fs.ts +108 -0
  432. package/src/internal/discovery/hooks.ts +45 -0
  433. package/src/internal/discovery/info.ts +179 -0
  434. package/src/internal/discovery/instructions.ts +113 -0
  435. package/src/internal/discovery/modules.ts +107 -0
  436. package/src/internal/discovery/names.ts +44 -0
  437. package/src/internal/discovery/project.ts +270 -0
  438. package/src/internal/discovery/runtime.ts +159 -0
  439. package/src/internal/discovery/schedules.ts +94 -0
  440. package/src/internal/discovery/seed-files.ts +37 -0
  441. package/src/internal/discovery/services.ts +124 -0
  442. package/src/internal/discovery/skills.ts +176 -0
  443. package/src/internal/discovery/subagent.ts +29 -0
  444. package/src/internal/discovery/tools.ts +182 -0
  445. package/src/internal/discovery.ts +20 -2724
  446. package/src/internal/distribution.ts +5 -0
  447. package/src/internal/framework-file-storage.ts +43 -9
  448. package/src/internal/framework-storage-selection.ts +9 -9
  449. package/src/internal/frontmatter.ts +36 -3
  450. package/src/internal/hosted-delivery.ts +38 -63
  451. package/src/internal/hosted-execution-diag.ts +6 -0
  452. package/src/internal/hosted-execution-flush.ts +142 -0
  453. package/src/internal/mcp-endpoint.ts +1 -0
  454. package/src/internal/mcp-host.ts +116 -37
  455. package/src/internal/otel/collector.ts +125 -25
  456. package/src/internal/otel/http.ts +2 -12
  457. package/src/internal/otel/mcp.ts +69 -0
  458. package/src/internal/otel/propagation.ts +17 -0
  459. package/src/internal/otel/runtime.ts +2 -0
  460. package/src/internal/review-comments.ts +5 -2
  461. package/src/internal/server.ts +81 -100
  462. package/src/internal/session-engine.ts +152 -39
  463. package/src/internal/session-run-log.ts +18 -16
  464. package/src/internal/start-log-store.ts +174 -0
  465. package/src/internal/storage-coordinator.ts +19 -16
  466. package/src/internal/tool-catalog.ts +9 -6
  467. package/src/internal/tool-policy.ts +39 -9
  468. package/src/internal/turn-cost.ts +47 -4
  469. package/src/internal/v2-hosting.ts +38 -0
  470. package/src/otel.ts +38 -0
  471. package/src/storage-protocol.ts +126 -79
  472. package/src/storage.ts +5 -0
  473. package/src/tools.ts +18 -0
  474. package/src/types.ts +213 -14
  475. package/dist/docs/assets/chunks/@localSearchIndexroot.j70vvPL4.js +0 -1
  476. package/dist/docs/assets/concepts.md.lwAgBIMI.js +0 -1
  477. package/dist/docs/assets/index.md.Bb4k8kUm.js +0 -5
  478. package/dist/docs/assets/skills_framework-map.md.BTi817yv.js +0 -1
  479. package/dist/playground/assets/index-BszoQDc6.css +0 -1
  480. /package/dist/docs/assets/{concepts.md.lwAgBIMI.lean.js → concepts.md.rPzW2HZN.lean.js} +0 -0
  481. /package/dist/docs/assets/{guides_cloud-runtime.md.CkYbjnAX.lean.js → guides_cloud-runtime.md.BRirbRAN.lean.js} +0 -0
  482. /package/dist/docs/assets/{reference_http-api.md.BEJx9XVj.lean.js → reference_http-api.md.oySXBO8o.lean.js} +0 -0
  483. /package/dist/docs/assets/{reference_skills.md.BFW9retM.lean.js → reference_skills.md.l2ezAcaN.lean.js} +0 -0
  484. /package/dist/docs/assets/{reference_tools.md.CvAHsdSp.lean.js → reference_tools.md.B1up2r9W.lean.js} +0 -0
@@ -4,2727 +4,23 @@
4
4
  * manifest. Identity comes from the path — a tool at `agent/tools/foo.ts`
5
5
  * is named `foo`, a subagent at `agent/subagents/researcher/` is named
6
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 { resolveArtifactsMax } from "../artifacts.js";
22
- import { httpChannel } from "../channels.js";
23
- import type { OtelDefinition } from "../otel.js";
24
- import type { StorageDefinition } from "../storage.js";
25
- import {
26
- type AgentArchitecture,
27
- type AgentConcurrencyOptions,
28
- type AgentConfigDefinition,
29
- type AgentHostingOptions,
30
- type AgentProject,
31
- type AgentProjectInfo,
32
- type AgentRuntime,
33
- type ArtifactsDefinition,
34
- type BuiltinToolsConfig,
35
- type ChannelDefinition,
36
- type ConnectionDefinition,
37
- type ConnectionTransport,
38
- cursorAccountServersHasMixedWildcard,
39
- DEFAULT_MODEL,
40
- type Diagnostic,
41
- type DiscoveredAB,
42
- type DiscoveredChannel,
43
- type DiscoveredConnection,
44
- type DiscoveredHook,
45
- type DiscoveredSchedule,
46
- type DiscoveredSkill,
47
- type DiscoveredTool,
48
- type HookDefinition,
49
- type HttpChannelDefinition,
50
- type InlineStdioConnectionConfig,
51
- type InstructionsDefinition,
52
- isInlineStdioConnection,
53
- isToolEffect,
54
- MAX_RUNNING_TURNS_LIMIT,
55
- type ModelSetting,
56
- normalizeConnectionEffects,
57
- normalizeCursorAccountServers,
58
- type ResolvedAgent,
59
- type ScheduleDefinition,
60
- type SeedFile,
61
- type SkillDefinition,
62
- type SkillFileSource,
63
- type ToolDefinition,
64
- type ToolEffect,
65
- toPublicCloudInfo,
66
- } from "../types.js";
67
- import { registerAuthoredModuleLoaders } from "./authored-loaders.js";
68
- import { normalizePositiveInt } from "./bounded-int.js";
69
- import { getDefinitionKind } from "./brand.js";
70
- import {
71
- artifactBuiltinTool,
72
- builtinTools,
73
- TAG_ARTIFACT_TOOL_NAME,
74
- } from "./builtin-tools/index.js";
75
- import { applyAgentContinuationDefaults } from "./continuation-channel.js";
76
- import { isValidCron } from "./cron.js";
77
- import { parseFrontmatter } from "./frontmatter.js";
78
- import {
79
- isReservedSecretName,
80
- MAX_EGRESS_DOMAINS,
81
- validateEgressDomain,
82
- validateSecretName,
83
- } from "./hosting.js";
84
- import { toolInputJsonSchema, toolOutputJsonSchema } from "./json-schema.js";
85
- import { MCP_ENDPOINT_PATH, mcpEndpointToolNames } from "./mcp-endpoint.js";
86
- import { packageInlineStdioConnection } from "./package-inline-stdio.js";
87
- import {
88
- defaultLocalHarnessCwd,
89
- isNestedInGitRepo,
90
- withIdentityPreamble,
91
- } from "./workspace.js";
92
-
93
- const MODULE_EXTENSIONS = new Set([".ts", ".mts", ".js", ".mjs"]);
94
- const NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
95
- const SKILL_DESCRIPTION_FALLBACK = (name: string): string =>
96
- `Instructions for the ${name} skill.`;
97
-
98
- export interface LoadAgentProjectOptions {
99
- /** Override the module loader (embedders / tests). */
100
- importModule?: (absolutePath: string) => Promise<Record<string, unknown>>;
101
- }
102
-
103
- type ImportModule = (absolutePath: string) => Promise<Record<string, unknown>>;
104
-
105
- async function defaultImportModule(
106
- absolutePath: string
107
- ): Promise<Record<string, unknown>> {
108
- return (await import(pathToFileURL(absolutePath).href)) as Record<
109
- string,
110
- unknown
111
- >;
112
- }
113
-
114
- interface DiscoveryContext {
115
- rootDir: string;
116
- diagnostics: Diagnostic[];
117
- importModule: ImportModule;
118
- }
119
-
120
- /**
121
- * True when `dir` looks like an agent project root: it has a nested `agent/`
122
- * directory, or flat-layout markers (`agent.ts` / `instructions.md`) directly
123
- * inside it.
124
- */
125
- export async function isAgentProjectDir(dir: string): Promise<boolean> {
126
- const resolved = resolve(dir);
127
- if (!(await isDirectory(resolved))) {
128
- return false;
129
- }
130
- if (await isDirectory(join(resolved, "agent"))) {
131
- return true;
132
- }
133
- return hasFlatLayoutMarkers(resolved);
134
- }
135
-
136
- /** One immediate child agent directory under a multi-agent parent. */
137
- export interface ChildAgentDir {
138
- slug: string;
139
- dir: string;
140
- }
141
-
142
- /**
143
- * List agent projects directly under `rootDir` (one level deep) without
144
- * loading them. Empty when the path is missing or has no child agents.
145
- * Slug rules match {@link loadAgentProjects} / multi-agent serve.
146
- */
147
- export async function listChildAgentDirs(
148
- rootDir: string
149
- ): Promise<ChildAgentDir[]> {
150
- const resolved = resolve(rootDir);
151
- if (!(await isDirectory(resolved))) {
152
- return [];
153
- }
154
-
155
- const results: ChildAgentDir[] = [];
156
- const seenSlugs = new Set<string>();
157
- for (const entry of await sortedEntries(resolved)) {
158
- if (!entry.isDirectory() || entry.name.startsWith(".")) {
159
- continue;
160
- }
161
- if (entry.name === "node_modules") {
162
- continue;
163
- }
164
- const childDir = join(resolved, entry.name);
165
- if (!(await isAgentProjectDir(childDir))) {
166
- continue;
167
- }
168
- const slug = assertSlug(entry.name, resolved);
169
- if (seenSlugs.has(slug)) {
170
- throw new Error(`Duplicate agent slug "${slug}" under ${resolved}.`);
171
- }
172
- seenSlugs.add(slug);
173
- results.push({ slug, dir: childDir });
174
- }
175
- return results;
176
- }
177
-
178
- /**
179
- * Discover the agent projects directly under `rootDir` (one level deep):
180
- * each immediate child directory that is itself an agent project becomes a
181
- * mounted agent, keyed by a slug derived from its directory name. Used for
182
- * multi-agent serving, where one server hosts many agents under
183
- * `/<slug>/...`. Throws when no child projects are found.
184
- */
185
- export async function loadAgentProjects(
186
- rootDir: string,
187
- options: LoadAgentProjectOptions = {}
188
- ): Promise<Array<{ slug: string; project: AgentProject }>> {
189
- const resolved = resolve(rootDir);
190
- if (!(await isDirectory(resolved))) {
191
- throw new Error(`Agent projects directory does not exist: ${resolved}`);
192
- }
193
-
194
- const children = await listChildAgentDirs(resolved);
195
- if (children.length === 0) {
196
- throw new Error(
197
- `No agent projects found directly under ${resolved}. Each agent should be a subdirectory with its own agent/ directory (or flat-layout markers).`
198
- );
199
- }
200
- const results: Array<{ slug: string; project: AgentProject }> = [];
201
- // Storage namespaces (session mirror, host kv) key on the project
202
- // NAME, not the mount slug — two co-hosted agents sharing a name would
203
- // silently share sessions and KV state.
204
- const slugByName = new Map<string, string>();
205
- for (const child of children) {
206
- const project = await loadAgentProject(child.dir, options);
207
- const priorSlug = slugByName.get(project.name);
208
- if (priorSlug !== undefined) {
209
- throw new Error(
210
- `Duplicate agent name "${project.name}" under ${resolved}: agents "${priorSlug}" and "${child.slug}" both resolve to it (package.json "name", or the directory name when unset). Co-hosted agents must have distinct names — their storage and session namespaces key on the name.`
211
- );
212
- }
213
- slugByName.set(project.name, child.slug);
214
- results.push({ slug: child.slug, project });
215
- }
216
- return results;
217
- }
218
-
219
- /**
220
- * Resolve mounts for `serve` / CLI. Defaults to multi-agent layout:
221
- * - `mode: "single"` — one agent at `/v1/*` (empty slug).
222
- * - otherwise, if `rootDir` is itself an agent project — one agent under
223
- * `/<dirname>/...`.
224
- * - otherwise, every immediate child agent project under `/<slug>/...`.
225
- */
226
- export async function resolveAgentMounts(
227
- rootDir: string,
228
- options: LoadAgentProjectOptions & {
229
- mode?: "single" | "multi";
230
- } = {}
231
- ): Promise<Array<{ slug: string; project: AgentProject }>> {
232
- const { mode = "multi", ...discovery } = options;
233
- if (mode === "single") {
234
- return [{ slug: "", project: await loadAgentProject(rootDir, discovery) }];
235
- }
236
- if (await isAgentProjectDir(rootDir)) {
237
- const resolved = resolve(rootDir);
238
- const slug = assertSlug(basename(resolved), resolved);
239
- return [
240
- {
241
- slug,
242
- project: await loadAgentProject(rootDir, discovery),
243
- },
244
- ];
245
- }
246
- return loadAgentProjects(rootDir, discovery);
247
- }
248
-
249
- function assertSlug(name: string, parentDir: string): string {
250
- if (!NAME_PATTERN.test(name) || RESERVED_SLUGS.has(name)) {
251
- throw new Error(
252
- `Cannot mount agent directory "${name}" under ${parentDir}: its name must match ${NAME_PATTERN} and not be one of ${[...RESERVED_SLUGS].join(", ")}.`
253
- );
254
- }
255
- return name;
256
- }
257
-
258
- /** URL path segments reserved by the server; agents cannot use them as slugs. */
259
- export const RESERVED_SLUGS: Set<string> = new Set([
260
- "v1",
261
- "playground",
262
- "docs",
263
- ]);
264
-
265
- export async function loadAgentProject(
266
- dir: string,
267
- options: LoadAgentProjectOptions = {}
268
- ): Promise<AgentProject> {
269
- // Public API: embedders can call discovery directly (without `serve()` or
270
- // the CLI), and it dynamically imports authored TypeScript.
271
- await registerAuthoredModuleLoaders();
272
- const rootDir = resolve(dir);
273
- if (!(await isDirectory(rootDir))) {
274
- throw new Error(`Agent project directory does not exist: ${rootDir}`);
275
- }
276
-
277
- const nestedAgentDir = join(rootDir, "agent");
278
- let agentDir: string;
279
- if (await isDirectory(nestedAgentDir)) {
280
- agentDir = nestedAgentDir;
281
- } else if (await hasFlatLayoutMarkers(rootDir)) {
282
- agentDir = rootDir;
283
- } else {
284
- throw new Error(
285
- `No agent found under ${rootDir}: expected an agent/ directory (or a flat layout with agent.ts / instructions.md at the root)`
286
- );
287
- }
288
-
289
- const diagnostics: Diagnostic[] = [];
290
- const ctx: DiscoveryContext = {
291
- rootDir,
292
- diagnostics,
293
- importModule: options.importModule ?? defaultImportModule,
294
- };
295
-
296
- const name = (await readPackageName(rootDir)) ?? basename(rootDir);
297
- const agent = await loadAgent(ctx, agentDir, {
298
- name,
299
- isRoot: true,
300
- });
301
-
302
- const { channels, httpChannelDefinition } = await loadChannels(
303
- ctx,
304
- join(agentDir, "channels")
305
- );
306
- const schedules = await loadSchedules(ctx, join(agentDir, "schedules"));
307
- const hooks = await loadHooks(ctx, join(agentDir, "hooks"));
308
- const abs = await loadABs(ctx, agentDir);
309
- const abConfig = await loadABConfig(ctx, agentDir);
310
- const storage = await loadStorage(ctx, agentDir, agent.architecture);
311
- const artifacts = await loadArtifacts(ctx, agentDir);
312
- const otel = await loadOtel(ctx, agentDir);
313
- await appendArtifactTool(ctx, agentDir, agent, artifacts);
314
-
315
- if (agentDir !== rootDir) {
316
- await warnUnknownSlots(ctx, agentDir);
317
- }
318
-
319
- const resolvedHttpChannel = httpChannelDefinition ?? httpChannel();
320
- applyAgentContinuationDefaults({
321
- agent,
322
- channels,
323
- httpChannel: resolvedHttpChannel,
324
- });
325
-
326
- return {
327
- rootDir,
328
- agentDir,
329
- name: agent.name,
330
- agent,
331
- httpChannel: resolvedHttpChannel,
332
- channels,
333
- schedules,
334
- hooks,
335
- abs,
336
- abConfig,
337
- storage,
338
- artifacts,
339
- otel,
340
- diagnostics,
341
- };
342
- }
343
-
344
- /** True when the project has diagnostics that must block serving. */
345
- export function hasErrorDiagnostics(project: AgentProject): boolean {
346
- return project.diagnostics.some((d) => d.severity === "error");
347
- }
348
-
349
- export function formatDiagnostics(diagnostics: Diagnostic[]): string {
350
- return diagnostics
351
- .map((d) => {
352
- const location = d.path === undefined ? "" : ` (${d.path})`;
353
- return `${d.severity}: ${d.message}${location}`;
354
- })
355
- .join("\n");
356
- }
357
-
358
- // ============================================================================
359
- // Agents (root + subagents)
360
- // ============================================================================
361
-
362
- async function loadAgent(
363
- ctx: DiscoveryContext,
364
- agentDir: string,
365
- options: { name: string; isRoot: boolean }
366
- ): Promise<ResolvedAgent> {
367
- const config = await loadAgentConfig(ctx, agentDir, options.isRoot);
368
- const name = config?.name ?? options.name;
369
- const authoredInstructions = await loadInstructions(
370
- ctx,
371
- agentDir,
372
- config,
373
- options.isRoot
374
- );
375
- // Root agents without authored instructions stay undefined (diagnostic
376
- // already recorded). Subagents still get the identity line alone.
377
- const instructions =
378
- authoredInstructions === undefined && options.isRoot
379
- ? undefined
380
- : withIdentityPreamble(name, authoredInstructions);
381
-
382
- // Subagents run as Cursor SDK custom subagents (prompt + description +
383
- // model); their other slots are inert today, so don't load them — just
384
- // warn that they exist.
385
- if (!options.isRoot) {
386
- await warnUnsupportedSubagentSlots(ctx, agentDir);
387
- if (config?.builtinTools !== undefined) {
388
- ctx.diagnostics.push({
389
- severity: "warning",
390
- path: displayPath(ctx, join(agentDir, "agent.ts")),
391
- message: "builtinTools is not supported on subagents; ignoring.",
392
- });
393
- }
394
- if (config?.tools !== undefined) {
395
- ctx.diagnostics.push({
396
- severity: "warning",
397
- path: displayPath(ctx, join(agentDir, "agent.ts")),
398
- message:
399
- "tools is not supported on subagents (they keep the harness task-tool toolset); ignoring.",
400
- });
401
- }
402
- if (config?.architecture !== undefined) {
403
- ctx.diagnostics.push({
404
- severity: "warning",
405
- path: displayPath(ctx, join(agentDir, "agent.ts")),
406
- message: "architecture is not supported on subagents; ignoring.",
407
- });
408
- }
409
- const artifactsPath = await findModuleFile(agentDir, "artifacts");
410
- if (artifactsPath !== undefined) {
411
- ctx.diagnostics.push({
412
- severity: "warning",
413
- path: displayPath(ctx, artifactsPath),
414
- message: "artifacts are not supported on subagents; ignoring.",
415
- });
416
- }
417
- return {
418
- name,
419
- ...(config?.description === undefined
420
- ? {}
421
- : { description: config.description }),
422
- ...(config?.model === undefined ? {} : { model: config.model }),
423
- ...(instructions === undefined ? {} : { instructions }),
424
- runtime: "local",
425
- tools: [],
426
- skills: [],
427
- connections: [],
428
- hostConnections: [],
429
- subagents: [],
430
- seedFiles: [],
431
- };
432
- }
433
-
434
- const tools = await loadTools(ctx, join(agentDir, "tools"));
435
- const skills = await loadSkills(ctx, join(agentDir, "skills"));
436
- const connections = await loadConnections(
437
- ctx,
438
- await resolveMcpConnectionsDir(ctx, agentDir)
439
- );
440
- const hostConnections = await loadHostConnections(ctx, agentDir, connections);
441
- const seedFiles = await loadSeedFiles(ctx, agentDir);
442
- const subagents = await loadSubagents(ctx, join(agentDir, "subagents"));
443
- const runtime = resolveRuntime(ctx, agentDir, config?.runtime);
444
- // Keep authored `cloud` even when default runtime is local — per-session
445
- // `send({ cloud })` merges it under cloudOverride for handoff sessions.
446
- const cloud = config?.cloud;
447
- const localWorkspaceDir = resolveAgentCwd(ctx, config?.local?.workspaceDir);
448
- const localCwd = await resolveLocalCwd(
449
- ctx,
450
- config?.local?.cwd,
451
- config?.local?.workspaceDir
452
- );
453
- const localSandbox = config?.local?.sandbox === true;
454
- // Built-in tool allowlist (top-level `tools`). `[]` is meaningful (no
455
- // native built-in tools), so presence is keyed on the field, not on
456
- // emptiness. "mcp" is always granted alongside a configured allowlist:
457
- // it is transport, not capability — this kit's own server tools ride the
458
- // SDK custom-tool MCP path, so omitting it silently breaks
459
- // `agent/tools/`, and what MCP actually exposes stays controlled by the
460
- // second layer (authored tools, mcp-connections, the harness cwd's
461
- // ambient config).
462
- const authoredAllowlist = config?.tools;
463
- const allowedTools =
464
- authoredAllowlist === undefined || authoredAllowlist.includes("mcp")
465
- ? authoredAllowlist
466
- : [...authoredAllowlist, "mcp" as const];
467
- // The allowlist states runtime-agnostic intent, but only the local
468
- // harness can enforce it (the Cursor SDK rejects tool restrictions on
469
- // cloud agents, and the Grok Bot harness has its own fixed toolset) — a
470
- // non-local agent that declares one must fail at serve start, never run
471
- // unrestricted. (Hybrid per-send cloud sessions fail closed in the
472
- // session engine.)
473
- if (allowedTools !== undefined && runtime !== "local") {
474
- ctx.diagnostics.push({
475
- severity: "error",
476
- path: displayPath(ctx, join(agentDir, "agent.ts")),
477
- message: `tools cannot be enforced on runtime: "${runtime}". Remove \`tools\` or run the agent on the local runtime.`,
478
- });
479
- }
480
- // Per-session connection features (defineConnection auth/advertiseTools)
481
- // only run on local turns: advertised tools are in-process server tools,
482
- // and per-session headers cannot reach a cloud VM's MCP config. A
483
- // non-local agent that declares them must fail at serve start — never
484
- // run without the connection's tools or identity. (Hybrid per-send
485
- // cloud sessions fail closed in the session engine.)
486
- const perSessionConnection = connections.find(
487
- (connection) =>
488
- connection.advertiseTools === true || connection.auth !== undefined
489
- );
490
- if (perSessionConnection !== undefined && runtime !== "local") {
491
- ctx.diagnostics.push({
492
- severity: "error",
493
- path: displayPath(ctx, join(agentDir, "agent.ts")),
494
- message: `MCP connection "${perSessionConnection.name}" uses advertiseTools/auth, which only run on runtime: "local". Remove them or run the agent on the local runtime.`,
495
- });
496
- }
497
- // `execution: "agent"` tool scripts run through the shell tool. Shell is
498
- // a real capability, so it is never injected — but an allowlist that
499
- // strands authored scripts deserves a loud diagnostic, not silence.
500
- if (
501
- allowedTools !== undefined &&
502
- !allowedTools.includes("shell") &&
503
- tools.some((tool) => tool.execution === "agent")
504
- ) {
505
- ctx.diagnostics.push({
506
- severity: "warning",
507
- path: displayPath(ctx, join(agentDir, "agent.ts")),
508
- message:
509
- '`tools` omits "shell" but this agent has execution: "agent" tools, which run as shell scripts; the model will not be able to run them.',
510
- });
511
- }
512
- if (localCwd !== undefined && localWorkspaceDir !== undefined) {
513
- ctx.diagnostics.push({
514
- severity: "warning",
515
- path: displayPath(ctx, join(agentDir, "agent.ts")),
516
- message:
517
- "local.workspaceDir pins every session to one directory, so local.cwd is unused. Drop one of them.",
518
- });
519
- }
520
- const local =
521
- localCwd === undefined && localWorkspaceDir === undefined && !localSandbox
522
- ? undefined
523
- : {
524
- cwd: localCwd,
525
- workspaceDir: localWorkspaceDir,
526
- sandbox: localSandbox,
527
- };
528
- const hosting = resolveHosting(ctx, agentDir, config?.hosting);
529
- const concurrency = resolveConcurrency(ctx, agentDir, config?.concurrency);
530
- const architecture = resolveArchitecture(ctx, agentDir, config?.architecture);
531
- const runtimeArchitectureError = runtimeArchitectureCompatibilityError({
532
- runtime,
533
- architecture,
534
- });
535
- if (runtimeArchitectureError !== undefined) {
536
- ctx.diagnostics.push({
537
- severity: "error",
538
- path: displayPath(ctx, join(agentDir, "agent.ts")),
539
- message: runtimeArchitectureError,
540
- });
541
- }
542
-
543
- if (runtime === "cloud") {
544
- warnCloudRuntimeLimitations(ctx, agentDir, {
545
- agentTools: tools.filter((tool) => tool.execution === "agent").length,
546
- skills: skills.length,
547
- seedFiles: seedFiles.length,
548
- hasCloudOptions: cloud !== undefined,
549
- });
550
- }
551
- appendBuiltinTools(ctx, agentDir, tools, config?.builtinTools);
552
-
553
- // After appendBuiltinTools so opted-in built-ins (server-executed) count.
554
- if (runtime === "grokbot") {
555
- checkGrokBotRuntimeLimitations(ctx, agentDir, {
556
- serverTools: tools.filter((tool) => tool.execution === "server").length,
557
- agentTools: tools.filter((tool) => tool.execution === "agent").length,
558
- skills: skills.length,
559
- seedFiles: seedFiles.length,
560
- connections: connections.length + hostConnections.length,
561
- subagents: subagents.length,
562
- hasCloudOptions: cloud !== undefined,
563
- hasLocalSandbox: localSandbox,
564
- });
565
- }
566
-
567
- return {
568
- name,
569
- description: config?.description,
570
- // Root agents always get a concrete model: authored value or DEFAULT_MODEL.
571
- model: isValidModelSetting(config?.model) ? config.model : DEFAULT_MODEL,
572
- instructions,
573
- runtime,
574
- cloud,
575
- local,
576
- ...(allowedTools === undefined ? {} : { allowedTools }),
577
- hosting,
578
- concurrency,
579
- ...(architecture === undefined ? {} : { architecture }),
580
- tools,
581
- skills,
582
- connections,
583
- hostConnections,
584
- subagents,
585
- seedFiles,
586
- };
587
- }
588
-
589
- export function runtimeArchitectureCompatibilityError(agent: {
590
- runtime: AgentRuntime;
591
- architecture?: AgentArchitecture;
592
- }): string | undefined {
593
- return agent.architecture === "v2" && agent.runtime === "grokbot"
594
- ? 'architecture: "v2" cannot use runtime: "grokbot": the grokbot runtime executes through the signed-in account\'s hosted /v0/grokbot box, not a deployment-scoped v2 runtime.'
595
- : undefined;
596
- }
597
-
598
- function resolveArchitecture(
599
- ctx: DiscoveryContext,
600
- agentDir: string,
601
- architecture: AgentArchitecture | undefined
602
- ): AgentArchitecture | undefined {
603
- if (
604
- architecture === undefined ||
605
- architecture === "v1" ||
606
- architecture === "v2"
607
- ) {
608
- return architecture;
609
- }
610
- ctx.diagnostics.push({
611
- severity: "error",
612
- path: displayPath(ctx, join(agentDir, "agent.ts")),
613
- message: `architecture must be "v1" or "v2"; got ${JSON.stringify(architecture)}.`,
614
- });
615
- return undefined;
616
- }
617
-
618
- /** Absolute harness cwd parent, or undefined when unset / blank. */
619
- function resolveAgentCwd(
620
- ctx: DiscoveryContext,
621
- authored: string | undefined
622
- ): string | undefined {
623
- if (authored === undefined) {
624
- return undefined;
625
- }
626
- const trimmed = authored.trim();
627
- if (trimmed === "") {
628
- return undefined;
629
- }
630
- return isAbsolute(trimmed) ? trimmed : resolve(ctx.rootDir, trimmed);
631
- }
632
-
633
- /**
634
- * Nested git checkouts inherit ancestor AGENTS.md / .cursor rules and MCP.
635
- * Point those harnesses at ~/.cache/agent-serve/<dir> unless the agent
636
- * already set cwd or workspaceDir.
637
- */
638
- async function resolveLocalCwd(
639
- ctx: DiscoveryContext,
640
- authoredCwd: string | undefined,
641
- authoredWorkspaceDir: string | undefined
642
- ): Promise<string | undefined> {
643
- const cwd = resolveAgentCwd(ctx, authoredCwd);
644
- if (cwd !== undefined) {
645
- return cwd;
646
- }
647
- if (resolveAgentCwd(ctx, authoredWorkspaceDir) !== undefined) {
648
- return undefined;
649
- }
650
- if (!(await isNestedInGitRepo(ctx.rootDir))) {
651
- return undefined;
652
- }
653
- return defaultLocalHarnessCwd(basename(ctx.rootDir));
654
- }
655
-
656
- /**
657
- * Validate the authored `hosting` block against the same grammar the
658
- * backend enforces on deploy. Violations are warning diagnostics (reported
659
- * by `agent-sdk validate`), not hard failures: the backend stays
660
- * authoritative and `agent-sdk deploy` re-checks before any network call.
661
- */
662
- function resolveHosting(
663
- ctx: DiscoveryContext,
664
- agentDir: string,
665
- hosting: AgentHostingOptions | undefined
666
- ): AgentHostingOptions | undefined {
667
- if (hosting === undefined) {
668
- return undefined;
669
- }
670
- const agentPath = displayPath(ctx, join(agentDir, "agent.ts"));
671
- const warn = (message: string): void => {
672
- ctx.diagnostics.push({ severity: "warning", path: agentPath, message });
673
- };
674
-
675
- const egressDomains = hosting.egressDomains ?? [];
676
- for (const domain of egressDomains) {
677
- const domainError = validateEgressDomain(domain);
678
- if (domainError !== undefined) {
679
- warn(`hosting.egressDomains: ${domainError}`);
680
- }
681
- }
682
- if (egressDomains.length > MAX_EGRESS_DOMAINS) {
683
- warn(
684
- `hosting.egressDomains lists ${egressDomains.length} domains; deploys are capped at ${MAX_EGRESS_DOMAINS}.`
685
- );
686
- }
687
-
688
- for (const name of hosting.secretNames ?? []) {
689
- const nameError = validateSecretName(name);
690
- if (nameError !== undefined) {
691
- warn(`hosting.secretNames: ${nameError}`);
692
- } else if (isReservedSecretName(name)) {
693
- warn(
694
- `hosting.secretNames: ${JSON.stringify(name)} is platform-managed (CURSOR_* / reserved hosted-storage names); omit it from secretNames.`
695
- );
696
- }
697
- }
698
-
699
- return {
700
- ...(hosting.egressDomains === undefined
701
- ? {}
702
- : { egressDomains: [...hosting.egressDomains] }),
703
- ...(hosting.secretNames === undefined
704
- ? {}
705
- : { secretNames: [...hosting.secretNames] }),
706
- };
707
- }
708
-
709
- /**
710
- * Validate the authored `concurrency` block. Invalid values are **error**
711
- * diagnostics (serve refuses to start): silently dropping a turn ceiling
712
- * would fail open to unlimited parallelism, the opposite of what the
713
- * author asked for.
714
- */
715
- function resolveConcurrency(
716
- ctx: DiscoveryContext,
717
- agentDir: string,
718
- concurrency: AgentConcurrencyOptions | undefined
719
- ): AgentConcurrencyOptions | undefined {
720
- if (concurrency === undefined || concurrency.maxRunningTurns === undefined) {
721
- return undefined;
722
- }
723
- try {
724
- return {
725
- maxRunningTurns: normalizePositiveInt(concurrency.maxRunningTurns, {
726
- label: "concurrency.maxRunningTurns",
727
- max: MAX_RUNNING_TURNS_LIMIT,
728
- }),
729
- };
730
- } catch (error) {
731
- ctx.diagnostics.push({
732
- severity: "error",
733
- path: displayPath(ctx, join(agentDir, "agent.ts")),
734
- message: error instanceof Error ? error.message : String(error),
735
- });
736
- return undefined;
737
- }
738
- }
739
-
740
- async function loadAgentConfig(
741
- ctx: DiscoveryContext,
742
- agentDir: string,
743
- isRoot: boolean
744
- ): Promise<AgentConfigDefinition | undefined> {
745
- const configPath = await findModuleFile(agentDir, "agent");
746
- if (configPath === undefined) {
747
- ctx.diagnostics.push({
748
- severity: "error",
749
- path: displayPath(ctx, join(agentDir, "agent.ts")),
750
- message: isRoot
751
- ? "agent.ts is required on the root agent: default-export defineAgent({ ... })"
752
- : "agent.ts is required on a subagent: default-export defineAgent({ description: ... })",
753
- });
754
- return undefined;
755
- }
756
-
757
- const definition = await importDefault(ctx, configPath, "agent");
758
- if (definition === undefined) {
759
- return undefined;
760
- }
761
- const config = definition as AgentConfigDefinition;
762
-
763
- if (!isRoot && isBlank(config.description)) {
764
- ctx.diagnostics.push({
765
- severity: "error",
766
- path: displayPath(ctx, configPath),
767
- message:
768
- "Subagents require a description; the parent model reads it to decide when to delegate.",
769
- });
770
- }
771
- return config;
772
- }
773
-
774
- async function loadInstructions(
775
- ctx: DiscoveryContext,
776
- agentDir: string,
777
- config: AgentConfigDefinition | undefined,
778
- isRoot: boolean
779
- ): Promise<string | undefined> {
780
- const parts: string[] = [];
781
-
782
- const markdownPath = join(agentDir, "instructions.md");
783
- if (await isFile(markdownPath)) {
784
- parts.push((await readFile(markdownPath, "utf8")).trim());
785
- }
786
-
787
- const modulePath = await findModuleFile(agentDir, "instructions");
788
- if (modulePath !== undefined) {
789
- const loaded = await loadInstructionsModule(ctx, modulePath);
790
- if (loaded !== undefined) {
791
- parts.push(loaded);
792
- }
793
- }
794
-
795
- const instructionsDir = join(agentDir, "instructions");
796
- if (await isDirectory(instructionsDir)) {
797
- for (const entry of await sortedEntries(instructionsDir)) {
798
- if (!entry.isFile()) {
799
- continue;
800
- }
801
- const entryPath = join(instructionsDir, entry.name);
802
- if (entry.name.endsWith(".md")) {
803
- parts.push((await readFile(entryPath, "utf8")).trim());
804
- } else if (isModuleFile(entry.name)) {
805
- const loaded = await loadInstructionsModule(ctx, entryPath);
806
- if (loaded !== undefined) {
807
- parts.push(loaded);
808
- }
809
- }
810
- }
811
- }
812
-
813
- if (config?.instructions !== undefined && !isBlank(config.instructions)) {
814
- parts.push(config.instructions.trim());
815
- }
816
-
817
- const combined = parts.filter((part) => part !== "").join("\n\n");
818
- if (combined === "") {
819
- if (isRoot) {
820
- ctx.diagnostics.push({
821
- severity: "error",
822
- path: displayPath(ctx, markdownPath),
823
- message:
824
- "instructions.md is required on the root agent (the always-on system prompt).",
825
- });
826
- }
827
- return undefined;
828
- }
829
- return combined;
830
- }
831
-
832
- async function loadInstructionsModule(
833
- ctx: DiscoveryContext,
834
- modulePath: string
835
- ): Promise<string | undefined> {
836
- let mod: Record<string, unknown>;
837
- try {
838
- mod = await ctx.importModule(modulePath);
839
- } catch (error) {
840
- ctx.diagnostics.push({
841
- severity: "error",
842
- path: displayPath(ctx, modulePath),
843
- message: `Failed to load module: ${errorMessage(error)}`,
844
- });
845
- return undefined;
846
- }
847
- const value = mod.default;
848
- if (typeof value === "string") {
849
- return value.trim();
850
- }
851
- if (getDefinitionKind(value) === "instructions") {
852
- return (value as InstructionsDefinition).markdown.trim();
853
- }
854
- ctx.diagnostics.push({
855
- severity: "error",
856
- path: displayPath(ctx, modulePath),
857
- message:
858
- "Instructions modules must default-export a string or defineInstructions({ markdown }).",
859
- });
860
- return undefined;
861
- }
862
-
863
- // ============================================================================
864
- // Tools
865
- // ============================================================================
866
-
867
- async function loadTools(
868
- ctx: DiscoveryContext,
869
- toolsDir: string
870
- ): Promise<DiscoveredTool[]> {
871
- if (!(await isDirectory(toolsDir))) {
872
- return [];
873
- }
874
- const tools: DiscoveredTool[] = [];
875
- for (const entry of await sortedEntries(toolsDir)) {
876
- const entryPath = join(toolsDir, entry.name);
877
- if (entry.isDirectory()) {
878
- ctx.diagnostics.push({
879
- severity: "warning",
880
- path: displayPath(ctx, entryPath),
881
- message: "Nested tool directories are not supported; ignoring.",
882
- });
883
- continue;
884
- }
885
- if (!entry.isFile() || !isModuleFile(entry.name)) {
886
- continue;
887
- }
888
- const name = moduleStem(entry.name);
889
- if (!NAME_PATTERN.test(name)) {
890
- ctx.diagnostics.push({
891
- severity: "error",
892
- path: displayPath(ctx, entryPath),
893
- message: `Tool name "${name}" must match ${NAME_PATTERN} (the filename becomes the tool name the model sees).`,
894
- });
895
- continue;
896
- }
897
- const definition = await importDefault(ctx, entryPath, "tool");
898
- if (definition === undefined) {
899
- continue;
900
- }
901
- const tool = definition as ToolDefinition;
902
- if (isBlank(tool.description)) {
903
- ctx.diagnostics.push({
904
- severity: "error",
905
- path: displayPath(ctx, entryPath),
906
- message: "Tools require a non-empty description.",
907
- });
908
- continue;
909
- }
910
- const execution = tool.execution === "agent" ? "agent" : "server";
911
- const needsApprovalConfigured =
912
- tool.needsApproval === true || typeof tool.needsApproval === "function";
913
- const effect = declaredEffect(tool);
914
- if (execution === "agent") {
915
- if (isBlank(tool.script)) {
916
- ctx.diagnostics.push({
917
- severity: "error",
918
- path: displayPath(ctx, entryPath),
919
- message:
920
- 'Tools with execution: "agent" require a non-empty `script` (shell body; JSON on stdin, result on stdout).',
921
- });
922
- continue;
923
- }
924
- if (typeof tool.execute === "function") {
925
- ctx.diagnostics.push({
926
- severity: "warning",
927
- path: displayPath(ctx, entryPath),
928
- message:
929
- '`execute` is ignored when execution is "agent"; the Cursor agent runs `script` instead.',
930
- });
931
- }
932
- if (needsApprovalConfigured) {
933
- ctx.diagnostics.push({
934
- severity: "error",
935
- path: displayPath(ctx, entryPath),
936
- message:
937
- '`needsApproval` is only supported for execution: "server" tools (in-process).',
938
- });
939
- continue;
940
- }
941
- } else if (typeof tool.execute !== "function") {
942
- ctx.diagnostics.push({
943
- severity: "error",
944
- path: displayPath(ctx, entryPath),
945
- message:
946
- 'Tools require an execute(input, ctx) function (or set execution: "agent" with a script).',
947
- });
948
- continue;
949
- }
950
- let inputSchema: DiscoveredTool["inputSchema"];
951
- try {
952
- inputSchema = toolInputJsonSchema(tool.inputSchema);
953
- } catch (error) {
954
- ctx.diagnostics.push({
955
- severity: "error",
956
- path: displayPath(ctx, entryPath),
957
- message: `Failed to convert inputSchema to JSON Schema: ${errorMessage(error)}`,
958
- });
959
- continue;
960
- }
961
- let outputSchema: DiscoveredTool["outputSchema"];
962
- try {
963
- outputSchema = toolOutputJsonSchema(tool.outputSchema);
964
- } catch (error) {
965
- ctx.diagnostics.push({
966
- severity: "error",
967
- path: displayPath(ctx, entryPath),
968
- message: `Failed to convert outputSchema to JSON Schema: ${errorMessage(error)}`,
969
- });
970
- continue;
971
- }
972
- tools.push({
973
- name,
974
- description: tool.description,
975
- inputSchema,
976
- ...(outputSchema === undefined ? {} : { outputSchema }),
977
- execution,
978
- needsApproval: needsApprovalConfigured,
979
- ...(effect === undefined ? {} : { effect }),
980
- ...(execution === "agent" && tool.script !== undefined
981
- ? { script: tool.script }
982
- : {}),
983
- definition: tool,
984
- });
985
- }
986
- return tools;
987
- }
988
-
989
- /**
990
- * Static view of a tool's `effect` for the manifest: the literal when it is
991
- * one, `"dynamic"` for a per-call predicate, `undefined` when undeclared. A
992
- * malformed value is left undeclared rather than guessed — undeclared is the
993
- * safe state (a dry run stubs it), where a guess could be the unsafe one.
994
- */
995
- function declaredEffect(
996
- tool: ToolDefinition
997
- ): ToolEffect | "dynamic" | undefined {
998
- if (typeof tool.effect === "function") {
999
- return "dynamic";
1000
- }
1001
- return tool.effect === "read" || tool.effect === "write"
1002
- ? tool.effect
1003
- : undefined;
1004
- }
1005
-
1006
- // ============================================================================
1007
- // Skills
1008
- // ============================================================================
1009
-
1010
- async function loadSkills(
1011
- ctx: DiscoveryContext,
1012
- skillsDir: string
1013
- ): Promise<DiscoveredSkill[]> {
1014
- if (!(await isDirectory(skillsDir))) {
1015
- return [];
1016
- }
1017
- const skills: DiscoveredSkill[] = [];
1018
- for (const entry of await sortedEntries(skillsDir)) {
1019
- const entryPath = join(skillsDir, entry.name);
1020
- if (entry.isDirectory()) {
1021
- const skill = await loadPackagedSkill(ctx, entryPath, entry.name);
1022
- if (skill !== undefined) {
1023
- skills.push(skill);
1024
- }
1025
- continue;
1026
- }
1027
- if (!entry.isFile()) {
1028
- continue;
1029
- }
1030
- if (entry.name.endsWith(".md")) {
1031
- const name = entry.name.slice(0, -".md".length);
1032
- if (!validateSkillName(ctx, name, entryPath)) {
1033
- continue;
1034
- }
1035
- const { data, body } = parseFrontmatter(
1036
- await readFile(entryPath, "utf8")
1037
- );
1038
- skills.push({
1039
- name,
1040
- description:
1041
- data.description ??
1042
- deriveSkillDescription(body) ??
1043
- SKILL_DESCRIPTION_FALLBACK(name),
1044
- markdown: body.trim(),
1045
- files: {},
1046
- });
1047
- continue;
1048
- }
1049
- if (isModuleFile(entry.name)) {
1050
- const name = moduleStem(entry.name);
1051
- if (!validateSkillName(ctx, name, entryPath)) {
1052
- continue;
1053
- }
1054
- const definition = await importDefault(ctx, entryPath, "skill");
1055
- if (definition === undefined) {
1056
- continue;
1057
- }
1058
- const skill = definition as SkillDefinition;
1059
- const files: Record<string, SkillFileSource> = {};
1060
- for (const [rel, contents] of Object.entries(skill.files ?? {})) {
1061
- files[rel] = { contents };
1062
- }
1063
- skills.push({
1064
- name,
1065
- description:
1066
- skill.description ??
1067
- deriveSkillDescription(skill.markdown) ??
1068
- SKILL_DESCRIPTION_FALLBACK(name),
1069
- markdown: skill.markdown.trim(),
1070
- files,
1071
- });
1072
- }
1073
- }
1074
- return skills;
1075
- }
1076
-
1077
- async function loadPackagedSkill(
1078
- ctx: DiscoveryContext,
1079
- skillDir: string,
1080
- name: string
1081
- ): Promise<DiscoveredSkill | undefined> {
1082
- const skillMdPath = join(skillDir, "SKILL.md");
1083
- if (!(await isFile(skillMdPath))) {
1084
- ctx.diagnostics.push({
1085
- severity: "warning",
1086
- path: displayPath(ctx, skillDir),
1087
- message: "Skill directories must contain a SKILL.md; ignoring.",
1088
- });
1089
- return undefined;
1090
- }
1091
- if (!validateSkillName(ctx, name, skillDir)) {
1092
- return undefined;
1093
- }
1094
- const { data, body } = parseFrontmatter(await readFile(skillMdPath, "utf8"));
1095
- if (data.description === undefined || isBlank(data.description)) {
1096
- ctx.diagnostics.push({
1097
- severity: "error",
1098
- path: displayPath(ctx, skillMdPath),
1099
- message:
1100
- "Packaged skills require `description` frontmatter in SKILL.md (there is no filename slug to fall back on).",
1101
- });
1102
- return undefined;
1103
- }
1104
-
1105
- const files: Record<string, SkillFileSource> = {};
1106
- for (const filePath of await walkFiles(skillDir)) {
1107
- const rel = toPosix(relative(skillDir, filePath));
1108
- if (rel === "SKILL.md") {
1109
- continue;
1110
- }
1111
- files[rel] = { sourcePath: filePath };
1112
- }
1113
-
1114
- return {
1115
- name,
1116
- description: data.description,
1117
- markdown: body.trim(),
1118
- files,
1119
- };
1120
- }
1121
-
1122
- /** One-line `/v1/info` label for a connection transport. */
1123
- function connectionTransportLabel(transport: ConnectionTransport): string {
1124
- if ("cursorAccount" in transport) {
1125
- return transport.servers === undefined
1126
- ? "cursor-account"
1127
- : `cursor-account:${transport.servers.join(",")}`;
1128
- }
1129
- if ("agent" in transport) {
1130
- return `agent:${transport.agent}`;
1131
- }
1132
- if ("url" in transport) {
1133
- return transport.oauth === true
1134
- ? `${transport.url} (oauth)`
1135
- : transport.url;
1136
- }
1137
- return transport.command;
1138
- }
1139
-
1140
- function hasAuthorizationHeader(
1141
- headers: Record<string, string> | undefined
1142
- ): boolean {
1143
- if (headers === undefined) {
1144
- return false;
1145
- }
1146
- return Object.keys(headers).some(
1147
- (key) => key.toLowerCase() === "authorization"
1148
- );
1149
- }
1150
-
1151
- function parseConnectionEffects(
1152
- effects: unknown
1153
- ): { ok: true; value: ToolEffect[] | undefined } | { ok: false } {
1154
- if (effects === undefined) {
1155
- return { ok: true, value: undefined };
1156
- }
1157
- const raw = typeof effects === "string" ? [effects] : effects;
1158
- if (!Array.isArray(raw) || raw.length === 0) {
1159
- return { ok: false };
1160
- }
1161
- const parsed: ToolEffect[] = [];
1162
- for (const value of raw) {
1163
- if (!isToolEffect(value)) {
1164
- return { ok: false };
1165
- }
1166
- parsed.push(value);
1167
- }
1168
- return { ok: true, value: normalizeConnectionEffects(parsed) };
1169
- }
1170
-
1171
- function connectionHasSpawnOrRemote(connection: ConnectionDefinition): boolean {
1172
- if ("cursorAccount" in connection && connection.cursorAccount === true) {
1173
- return true;
1174
- }
1175
- if ("agent" in connection && !isBlank(connection.agent)) {
1176
- return true;
1177
- }
1178
- if ("url" in connection && !isBlank(connection.url)) {
1179
- return true;
1180
- }
1181
- return "command" in connection && !isBlank(connection.command);
1182
- }
1183
-
1184
- function connectionTransport(
1185
- connection: ConnectionDefinition
1186
- ): ConnectionTransport | undefined {
1187
- if ("cursorAccount" in connection && connection.cursorAccount === true) {
1188
- const servers = normalizeCursorAccountServers(connection.servers);
1189
- return {
1190
- cursorAccount: true,
1191
- ...(servers === undefined ? {} : { servers }),
1192
- };
1193
- }
1194
- if ("agent" in connection && !isBlank(connection.agent)) {
1195
- return { agent: connection.agent };
1196
- }
1197
- if ("url" in connection && !isBlank(connection.url)) {
1198
- return {
1199
- type: connection.type,
1200
- url: connection.url,
1201
- headers: connection.headers,
1202
- oauth: connection.oauth === true ? true : undefined,
1203
- };
1204
- }
1205
- if ("command" in connection && !isBlank(connection.command)) {
1206
- return {
1207
- ...(connection.type === undefined ? {} : { type: connection.type }),
1208
- command: connection.command,
1209
- ...(connection.args === undefined ? {} : { args: connection.args }),
1210
- ...(connection.env === undefined ? {} : { env: connection.env }),
1211
- ...(connection.cwd === undefined ? {} : { cwd: connection.cwd }),
1212
- };
1213
- }
1214
- return undefined;
1215
- }
1216
-
1217
- function validateSkillName(
1218
- ctx: DiscoveryContext,
1219
- name: string,
1220
- entryPath: string
1221
- ): boolean {
1222
- if (NAME_PATTERN.test(name)) {
1223
- return true;
1224
- }
1225
- ctx.diagnostics.push({
1226
- severity: "error",
1227
- path: displayPath(ctx, entryPath),
1228
- message: `Skill name "${name}" must match ${NAME_PATTERN}.`,
1229
- });
1230
- return false;
1231
- }
1232
-
1233
- /**
1234
- * Advertise the first non-empty, non-code-fence line of the body, with any
1235
- * leading `#`, `>`, `*`, or `-` markers stripped.
1236
- */
1237
- export function deriveSkillDescription(markdown: string): string | undefined {
1238
- let inFence = false;
1239
- for (const rawLine of markdown.split(/\r?\n/)) {
1240
- const line = rawLine.trim();
1241
- if (line.startsWith("```")) {
1242
- inFence = !inFence;
1243
- continue;
1244
- }
1245
- if (inFence || line === "") {
1246
- continue;
1247
- }
1248
- const stripped = line.replace(/^[#>*\s-]+/, "").trim();
1249
- if (stripped !== "") {
1250
- return stripped.length > 200 ? `${stripped.slice(0, 197)}...` : stripped;
1251
- }
1252
- }
1253
- return undefined;
1254
- }
1255
-
1256
- // ============================================================================
1257
- // MCP connections
1258
- // ============================================================================
1259
-
1260
- /**
1261
- * Prefer `agent/mcp-connections/`. Keep loading `agent/connections/` with a
1262
- * deprecation warning so existing agents keep working during the rename.
1263
- */
1264
- async function resolveMcpConnectionsDir(
1265
- ctx: DiscoveryContext,
1266
- agentDir: string
1267
- ): Promise<string> {
1268
- const preferred = join(agentDir, "mcp-connections");
1269
- const legacy = join(agentDir, "connections");
1270
- const hasPreferred = await isDirectory(preferred);
1271
- const hasLegacy = await isDirectory(legacy);
1272
- if (hasPreferred && hasLegacy) {
1273
- ctx.diagnostics.push({
1274
- severity: "warning",
1275
- path: displayPath(ctx, legacy),
1276
- message:
1277
- "Both agent/mcp-connections/ and agent/connections/ exist; using mcp-connections/ and ignoring connections/. Rename or remove the legacy directory.",
1278
- });
1279
- return preferred;
1280
- }
1281
- if (hasLegacy) {
1282
- ctx.diagnostics.push({
1283
- severity: "warning",
1284
- path: displayPath(ctx, legacy),
1285
- message:
1286
- "agent/connections/ is deprecated; rename it to agent/mcp-connections/. Loading from the legacy path for now.",
1287
- });
1288
- return legacy;
1289
- }
1290
- return preferred;
1291
- }
1292
-
1293
- async function loadHostConnections(
1294
- ctx: DiscoveryContext,
1295
- agentDir: string,
1296
- modelConnections: readonly DiscoveredConnection[]
1297
- ): Promise<DiscoveredConnection[]> {
1298
- const hostDir = join(agentDir, "host-connections");
1299
- const loaded = await loadConnections(ctx, hostDir);
1300
- const modelNames = new Set(
1301
- modelConnections.map((connection) => connection.name)
1302
- );
1303
- const hostConnections: DiscoveredConnection[] = [];
1304
- for (const connection of loaded) {
1305
- if (modelNames.has(connection.name)) {
1306
- ctx.diagnostics.push({
1307
- severity: "error",
1308
- path: displayPath(ctx, join(hostDir, `${connection.name}.ts`)),
1309
- message: `Host connection "${connection.name}" collides with agent/mcp-connections/${connection.name}. Rename one of them.`,
1310
- });
1311
- continue;
1312
- }
1313
- if (connection.advertiseTools === true) {
1314
- ctx.diagnostics.push({
1315
- severity: "error",
1316
- path: displayPath(ctx, join(hostDir, `${connection.name}.ts`)),
1317
- message:
1318
- "Host connections are off the model. `advertiseTools` cannot apply. Drop it.",
1319
- });
1320
- continue;
1321
- }
1322
- if (connection.auth !== undefined) {
1323
- ctx.diagnostics.push({
1324
- severity: "error",
1325
- path: displayPath(ctx, join(hostDir, `${connection.name}.ts`)),
1326
- message:
1327
- "Host connections are off the model. `auth` cannot apply. Use static headers or `oauth: true`.",
1328
- });
1329
- continue;
1330
- }
1331
- hostConnections.push(connection);
1332
- }
1333
- return hostConnections;
1334
- }
1335
-
1336
- async function loadConnections(
1337
- ctx: DiscoveryContext,
1338
- connectionsDir: string
1339
- ): Promise<DiscoveredConnection[]> {
1340
- if (!(await isDirectory(connectionsDir))) {
1341
- return [];
1342
- }
1343
- const connections: DiscoveredConnection[] = [];
1344
- for (const entry of await sortedEntries(connectionsDir)) {
1345
- const entryPath = join(connectionsDir, entry.name);
1346
- if (!entry.isFile() || !isModuleFile(entry.name)) {
1347
- continue;
1348
- }
1349
- const name = moduleStem(entry.name);
1350
- if (!NAME_PATTERN.test(name)) {
1351
- ctx.diagnostics.push({
1352
- severity: "error",
1353
- path: displayPath(ctx, entryPath),
1354
- message: `MCP connection name "${name}" must match ${NAME_PATTERN}.`,
1355
- });
1356
- continue;
1357
- }
1358
- const definition = await importDefault(ctx, entryPath, "connection");
1359
- if (definition === undefined) {
1360
- continue;
1361
- }
1362
- const connection = definition as ConnectionDefinition;
1363
- if (Object.hasOwn(connection, "hostOnly")) {
1364
- ctx.diagnostics.push({
1365
- severity: "error",
1366
- path: displayPath(ctx, entryPath),
1367
- message:
1368
- "MCP connection `hostOnly` was removed. Files under `agent/mcp-connections/` are always visible to the model. Put privileged servers in `agent/host-connections/` so only host tools can call them.",
1369
- });
1370
- continue;
1371
- }
1372
- if (
1373
- isInlineStdioConnection(connection) &&
1374
- connectionHasSpawnOrRemote(connection)
1375
- ) {
1376
- ctx.diagnostics.push({
1377
- severity: "error",
1378
- path: displayPath(ctx, entryPath),
1379
- message:
1380
- "MCP connection `tools` cannot be combined with `command`, `url`, `agent`, or `cursorAccount`. Author TypeScript tools, or point at an existing server, not both.",
1381
- });
1382
- continue;
1383
- }
1384
- if (
1385
- connection.auth !== undefined &&
1386
- typeof connection.auth !== "function"
1387
- ) {
1388
- ctx.diagnostics.push({
1389
- severity: "error",
1390
- path: displayPath(ctx, entryPath),
1391
- message:
1392
- "MCP connection `auth` must be a function: (session) => { headers }.",
1393
- });
1394
- continue;
1395
- }
1396
- if (connection.auth !== undefined && !("url" in connection)) {
1397
- ctx.diagnostics.push({
1398
- severity: "error",
1399
- path: displayPath(ctx, entryPath),
1400
- message:
1401
- "MCP connection `auth` (per-session headers) is only supported on http/sse connections (`url`).",
1402
- });
1403
- continue;
1404
- }
1405
- if (
1406
- connection.advertiseTools !== undefined &&
1407
- typeof connection.advertiseTools !== "boolean"
1408
- ) {
1409
- ctx.diagnostics.push({
1410
- severity: "error",
1411
- path: displayPath(ctx, entryPath),
1412
- message: "MCP connection `advertiseTools` must be a boolean.",
1413
- });
1414
- continue;
1415
- }
1416
- const parsedEffects = parseConnectionEffects(connection.effects);
1417
- if (!parsedEffects.ok) {
1418
- ctx.diagnostics.push({
1419
- severity: "error",
1420
- path: displayPath(ctx, entryPath),
1421
- message:
1422
- 'MCP connection `effects` must be "read", "write", or a non-empty array of those.',
1423
- });
1424
- continue;
1425
- }
1426
- if (
1427
- parsedEffects.value !== undefined &&
1428
- connection.advertiseTools !== true
1429
- ) {
1430
- ctx.diagnostics.push({
1431
- severity: "error",
1432
- path: displayPath(ctx, entryPath),
1433
- message:
1434
- "MCP connection `effects` filters advertised tools; set `advertiseTools: true` or drop `effects`.",
1435
- });
1436
- continue;
1437
- }
1438
- if (
1439
- connection.advertiseTools === true &&
1440
- isInlineStdioConnection(connection)
1441
- ) {
1442
- ctx.diagnostics.push({
1443
- severity: "error",
1444
- path: displayPath(ctx, entryPath),
1445
- message:
1446
- "MCP connection `advertiseTools` is not supported on inline `tools` connections — author those as agent/tools/ server tools instead.",
1447
- });
1448
- continue;
1449
- }
1450
- if (
1451
- connection.auth !== undefined &&
1452
- "url" in connection &&
1453
- connection.oauth === true
1454
- ) {
1455
- ctx.diagnostics.push({
1456
- severity: "error",
1457
- path: displayPath(ctx, entryPath),
1458
- message:
1459
- "MCP connection `oauth: true` cannot be combined with `auth` (per-session headers): the host OAuth provider owns the Authorization header. Drop one of them.",
1460
- });
1461
- continue;
1462
- }
1463
- if (isInlineStdioConnection(connection)) {
1464
- const packed = await loadInlineStdioConnection(
1465
- ctx,
1466
- name,
1467
- entryPath,
1468
- connection
1469
- );
1470
- if (packed === undefined) {
1471
- continue;
1472
- }
1473
- connections.push(packed);
1474
- continue;
1475
- }
1476
- if (
1477
- "url" in connection &&
1478
- connection.oauth === true &&
1479
- hasAuthorizationHeader(connection.headers)
1480
- ) {
1481
- ctx.diagnostics.push({
1482
- severity: "error",
1483
- path: displayPath(ctx, entryPath),
1484
- message:
1485
- "MCP connection `oauth: true` cannot set an Authorization header. Drop the header or remove `oauth`.",
1486
- });
1487
- continue;
1488
- }
1489
- if (
1490
- "cursorAccount" in connection &&
1491
- connection.cursorAccount === true &&
1492
- cursorAccountServersHasMixedWildcard(connection.servers)
1493
- ) {
1494
- ctx.diagnostics.push({
1495
- severity: "error",
1496
- path: displayPath(ctx, entryPath),
1497
- message:
1498
- 'Cursor account MCP connection `servers` cannot mix `"*"` with connector names. Use `"*"` / `["*"]` (or omit `servers`) for every connected server, or list only concrete connector names.',
1499
- });
1500
- continue;
1501
- }
1502
- const transport = connectionTransport(connection);
1503
- if (transport === undefined) {
1504
- ctx.diagnostics.push({
1505
- severity: "error",
1506
- path: displayPath(ctx, entryPath),
1507
- message:
1508
- "MCP connections require a `url` (http/sse), a `command` (stdio), `tools` (TypeScript packaged as stdio), an `agent` (peer agent slug on the same serve host), or `cursorAccount: true` (the Cursor account's MCP connectors).",
1509
- });
1510
- continue;
1511
- }
1512
- if (
1513
- "cursorAccount" in transport &&
1514
- transport.servers !== undefined &&
1515
- (transport.servers.length === 0 ||
1516
- transport.servers.some((server) => isBlank(server)))
1517
- ) {
1518
- ctx.diagnostics.push({
1519
- severity: "error",
1520
- path: displayPath(ctx, entryPath),
1521
- message:
1522
- 'Cursor account MCP connection `servers` must be a non-empty list of connector names, `"*"` / `["*"]` for every connected server, or omitted for the same all-connectors behavior.',
1523
- });
1524
- continue;
1525
- }
1526
- if ("agent" in transport && !NAME_PATTERN.test(transport.agent)) {
1527
- ctx.diagnostics.push({
1528
- severity: "error",
1529
- path: displayPath(ctx, entryPath),
1530
- message: `Peer MCP connection agent "${transport.agent}" must be a mount slug matching ${NAME_PATTERN}.`,
1531
- });
1532
- continue;
1533
- }
1534
- connections.push({
1535
- name,
1536
- transport,
1537
- description: connection.description,
1538
- auth: connection.auth,
1539
- advertiseTools: connection.advertiseTools,
1540
- effects: parsedEffects.value,
1541
- });
1542
- }
1543
- return connections;
1544
- }
1545
-
1546
- async function loadInlineStdioConnection(
1547
- ctx: DiscoveryContext,
1548
- name: string,
1549
- entryPath: string,
1550
- connection: InlineStdioConnectionConfig & {
1551
- description?: string;
1552
- }
1553
- ): Promise<DiscoveredConnection | undefined> {
1554
- const tools = connection.tools;
1555
- const names = Object.keys(tools);
1556
- if (names.length === 0) {
1557
- ctx.diagnostics.push({
1558
- severity: "error",
1559
- path: displayPath(ctx, entryPath),
1560
- message: "MCP connection `tools` must declare at least one tool.",
1561
- });
1562
- return undefined;
1563
- }
1564
- for (const toolName of names) {
1565
- if (!NAME_PATTERN.test(toolName)) {
1566
- ctx.diagnostics.push({
1567
- severity: "error",
1568
- path: displayPath(ctx, entryPath),
1569
- message: `MCP tool name "${toolName}" must match ${NAME_PATTERN}.`,
1570
- });
1571
- return undefined;
1572
- }
1573
- const tool = tools[toolName];
1574
- if (tool === undefined || isBlank(tool.description)) {
1575
- ctx.diagnostics.push({
1576
- severity: "error",
1577
- path: displayPath(ctx, entryPath),
1578
- message: `MCP tool "${toolName}" requires a non-empty description.`,
1579
- });
1580
- return undefined;
1581
- }
1582
- if (typeof tool.execute !== "function") {
1583
- ctx.diagnostics.push({
1584
- severity: "error",
1585
- path: displayPath(ctx, entryPath),
1586
- message: `MCP tool "${toolName}" requires an execute(input) function.`,
1587
- });
1588
- return undefined;
1589
- }
1590
- }
1591
- try {
1592
- const transport = await packageInlineStdioConnection({
1593
- name,
1594
- entryPath,
1595
- tools,
1596
- absWorkingDir: ctx.rootDir,
1597
- });
1598
- return {
1599
- name,
1600
- transport,
1601
- description: connection.description,
1602
- };
1603
- } catch (error) {
1604
- ctx.diagnostics.push({
1605
- severity: "error",
1606
- path: displayPath(ctx, entryPath),
1607
- message: `Failed to package inline stdio MCP connection: ${errorMessage(error)}`,
1608
- });
1609
- return undefined;
1610
- }
1611
- }
1612
-
1613
- // ============================================================================
1614
- // Subagents
1615
- // ============================================================================
1616
-
1617
- async function loadSubagents(
1618
- ctx: DiscoveryContext,
1619
- subagentsDir: string
1620
- ): Promise<ResolvedAgent[]> {
1621
- if (!(await isDirectory(subagentsDir))) {
1622
- return [];
1623
- }
1624
- const subagents: ResolvedAgent[] = [];
1625
- for (const entry of await sortedEntries(subagentsDir)) {
1626
- if (!entry.isDirectory()) {
1627
- continue;
1628
- }
1629
- const subagentDir = join(subagentsDir, entry.name);
1630
- if (!NAME_PATTERN.test(entry.name)) {
1631
- ctx.diagnostics.push({
1632
- severity: "error",
1633
- path: displayPath(ctx, subagentDir),
1634
- message: `Subagent name "${entry.name}" must match ${NAME_PATTERN}.`,
1635
- });
1636
- continue;
1637
- }
1638
- subagents.push(
1639
- await loadAgent(ctx, subagentDir, { name: entry.name, isRoot: false })
1640
- );
1641
- }
1642
- return subagents;
1643
- }
1644
-
1645
- async function warnUnsupportedSubagentSlots(
1646
- ctx: DiscoveryContext,
1647
- subagentDir: string
1648
- ): Promise<void> {
1649
- const unsupported = [
1650
- ["tools", "tools"],
1651
- ["skills", "skills"],
1652
- ["mcp-connections", "MCP connections"],
1653
- ["host-connections", "host MCP connections"],
1654
- ["connections", "MCP connections"],
1655
- ["subagents", "nested subagents"],
1656
- ["channels", "channels"],
1657
- ["schedules", "schedules"],
1658
- ["hooks", "hooks"],
1659
- ["sandbox", "sandbox files"],
1660
- ] as const;
1661
- for (const [slot, label] of unsupported) {
1662
- if (await isDirectory(join(subagentDir, slot))) {
1663
- ctx.diagnostics.push({
1664
- severity: "warning",
1665
- path: displayPath(ctx, join(subagentDir, slot)),
1666
- message: `Subagent ${label} are not supported on the Cursor harness yet; the subagent runs with the parent's execution surface. Ignoring.`,
1667
- });
1668
- }
1669
- }
1670
- }
1671
-
1672
- /**
1673
- * Append enabled built-in tools after authored tools. Authored tools with
1674
- * the same name win.
1675
- */
1676
- function appendBuiltinTools(
1677
- ctx: DiscoveryContext,
1678
- agentDir: string,
1679
- tools: DiscoveredTool[],
1680
- config: BuiltinToolsConfig | undefined
1681
- ): void {
1682
- const builtin = builtinTools(config);
1683
- if (builtin.length === 0) {
1684
- return;
1685
- }
1686
- const agentPath = displayPath(ctx, join(agentDir, "agent.ts"));
1687
- const authored = new Set(tools.map((tool) => tool.name));
1688
- for (const tool of builtin) {
1689
- if (authored.has(tool.name)) {
1690
- ctx.diagnostics.push({
1691
- severity: "warning",
1692
- path: agentPath,
1693
- message: `Authored tool "${tool.name}" shadows the built-in tool of the same name; the authored tool wins.`,
1694
- });
1695
- continue;
1696
- }
1697
- tools.push(tool);
1698
- }
1699
- }
1700
-
1701
- /**
1702
- * Validate the authored runtime. An unrecognized value is an error
1703
- * diagnostic and falls back to local — a typo must not silently pick a
1704
- * different harness for every turn.
1705
- */
1706
- function resolveRuntime(
1707
- ctx: DiscoveryContext,
1708
- agentDir: string,
1709
- runtime: AgentRuntime | undefined
1710
- ): AgentRuntime {
1711
- if (
1712
- runtime === undefined ||
1713
- runtime === "local" ||
1714
- runtime === "cloud" ||
1715
- runtime === "grokbot"
1716
- ) {
1717
- return runtime ?? "local";
1718
- }
1719
- ctx.diagnostics.push({
1720
- severity: "error",
1721
- path: displayPath(ctx, join(agentDir, "agent.ts")),
1722
- message: `runtime must be "local", "cloud", or "grokbot"; got ${JSON.stringify(runtime)}.`,
1723
- });
1724
- return "local";
1725
- }
1726
-
1727
- /**
1728
- * Grok Bot turns run on the Sand harness with its own toolset: authored
1729
- * server tools, MCP connections, and subagents have no path onto that
1730
- * toolset yet, so an agent declaring them must fail at serve start rather
1731
- * than run with capabilities silently missing.
1732
- */
1733
- function checkGrokBotRuntimeLimitations(
1734
- ctx: DiscoveryContext,
1735
- agentDir: string,
1736
- counts: {
1737
- serverTools: number;
1738
- agentTools: number;
1739
- skills: number;
1740
- seedFiles: number;
1741
- connections: number;
1742
- subagents: number;
1743
- hasCloudOptions: boolean;
1744
- hasLocalSandbox: boolean;
1745
- }
1746
- ): void {
1747
- const agentPath = displayPath(ctx, join(agentDir, "agent.ts"));
1748
- const error = (path: string, message: string): void => {
1749
- ctx.diagnostics.push({ severity: "error", path, message });
1750
- };
1751
- const warn = (path: string, message: string): void => {
1752
- ctx.diagnostics.push({ severity: "warning", path, message });
1753
- };
1754
- if (counts.serverTools > 0) {
1755
- error(
1756
- displayPath(ctx, join(agentDir, "tools")),
1757
- 'Server tools (execution: "server", including opted-in builtinTools) are not reachable from grokbot turns yet. Remove them or run the agent on the local runtime.'
1758
- );
1759
- }
1760
- if (counts.connections > 0) {
1761
- error(
1762
- displayPath(ctx, agentDir),
1763
- "MCP connections (agent/mcp-connections/ or agent/host-connections/) are not wired into grokbot turns yet. Remove them or run the agent on the local runtime."
1764
- );
1765
- }
1766
- if (counts.subagents > 0) {
1767
- error(
1768
- displayPath(ctx, join(agentDir, "subagents")),
1769
- "Authored subagents are not available on grokbot turns (the Grok Bot harness runs its own subagent set). Remove them or run the agent on the local runtime."
1770
- );
1771
- }
1772
- // The remaining declarations mirror the cloud runtime's warnings: the
1773
- // turn runs on a hosted machine (the account's box), so local-workspace
1774
- // materialization never reaches it.
1775
- if (counts.agentTools > 0) {
1776
- warn(
1777
- displayPath(ctx, join(agentDir, "tools")),
1778
- 'Agent tools (execution: "agent") are described in the hosted session\'s instructions (with script bodies); they are not written onto the box filesystem automatically.'
1779
- );
1780
- }
1781
- if (counts.skills > 0) {
1782
- warn(
1783
- displayPath(ctx, join(agentDir, "skills")),
1784
- "Authored skills are materialized into the local session workspace only; the hosted Grok Bot box does not see them."
1785
- );
1786
- }
1787
- if (counts.seedFiles > 0) {
1788
- warn(
1789
- displayPath(ctx, join(agentDir, "sandbox")),
1790
- "sandbox/workspace seed files apply to the local session workspace only; grokbot turns run on the account's hosted box."
1791
- );
1792
- }
1793
- if (counts.hasCloudOptions) {
1794
- warn(
1795
- agentPath,
1796
- 'The `cloud` block is unused on runtime: "grokbot", and grokbot sessions refuse per-session cloud attach.'
1797
- );
1798
- }
1799
- if (counts.hasLocalSandbox) {
1800
- warn(
1801
- agentPath,
1802
- 'local.sandbox does not apply to runtime: "grokbot"; the box is the account\'s Cursor-hosted computer.'
1803
- );
1804
- }
1805
- }
1806
-
1807
- function warnCloudRuntimeLimitations(
1808
- ctx: DiscoveryContext,
1809
- agentDir: string,
1810
- counts: {
1811
- agentTools: number;
1812
- skills: number;
1813
- seedFiles: number;
1814
- hasCloudOptions: boolean;
1815
- }
1816
- ): void {
1817
- const agentPath = displayPath(ctx, join(agentDir, "agent.ts"));
1818
- if (!counts.hasCloudOptions) {
1819
- ctx.diagnostics.push({
1820
- severity: "warning",
1821
- path: agentPath,
1822
- message:
1823
- 'runtime: "cloud" usually needs a `cloud` block (e.g. repos / env). Without it the Cursor cloud API may reject create.',
1824
- });
1825
- }
1826
- if (counts.agentTools > 0) {
1827
- ctx.diagnostics.push({
1828
- severity: "warning",
1829
- path: displayPath(ctx, join(agentDir, "tools")),
1830
- message:
1831
- 'Agent tools (execution: "agent") are described on the first cloud prompt (with script bodies); they are not written into the cloud VM filesystem automatically.',
1832
- });
1833
- }
1834
- if (counts.skills > 0) {
1835
- ctx.diagnostics.push({
1836
- severity: "warning",
1837
- path: displayPath(ctx, join(agentDir, "skills")),
1838
- message:
1839
- "Authored skills reach cloud agents through native store discovery: hosted deployments write the deployment store's skills/ directory; local serve/run with a personal API key writes agent-serve/<agent>/skills/ on the USER store. Without a reachable store, only skills already in the cloud repo are visible.",
1840
- });
1841
- }
1842
- if (counts.seedFiles > 0) {
1843
- ctx.diagnostics.push({
1844
- severity: "warning",
1845
- path: displayPath(ctx, join(agentDir, "sandbox")),
1846
- message:
1847
- "sandbox/workspace seed files apply to the local session workspace only; cloud agents use the configured cloud repos.",
1848
- });
1849
- }
1850
- }
1851
-
1852
- // ============================================================================
1853
- // Channels
1854
- // ============================================================================
1855
-
1856
- async function loadChannels(
1857
- ctx: DiscoveryContext,
1858
- channelsDir: string
1859
- ): Promise<{
1860
- channels: DiscoveredChannel[];
1861
- httpChannelDefinition: HttpChannelDefinition | undefined;
1862
- }> {
1863
- const channels: DiscoveredChannel[] = [];
1864
- let httpChannelDefinition: HttpChannelDefinition | undefined;
1865
- if (!(await isDirectory(channelsDir))) {
1866
- return { channels, httpChannelDefinition };
1867
- }
1868
-
1869
- for (const entry of await sortedEntries(channelsDir)) {
1870
- const entryPath = join(channelsDir, entry.name);
1871
- if (!entry.isFile() || !isModuleFile(entry.name)) {
1872
- continue;
1873
- }
1874
- const id = moduleStem(entry.name);
1875
- if (!NAME_PATTERN.test(id)) {
1876
- ctx.diagnostics.push({
1877
- severity: "error",
1878
- path: displayPath(ctx, entryPath),
1879
- message: `Channel id "${id}" must match ${NAME_PATTERN}.`,
1880
- });
1881
- continue;
1882
- }
1883
-
1884
- let mod: Record<string, unknown>;
1885
- try {
1886
- mod = await ctx.importModule(entryPath);
1887
- } catch (error) {
1888
- ctx.diagnostics.push({
1889
- severity: "error",
1890
- path: displayPath(ctx, entryPath),
1891
- message: `Failed to load module: ${errorMessage(error)}`,
1892
- });
1893
- continue;
1894
- }
1895
- const kind = getDefinitionKind(mod.default);
1896
-
1897
- if (kind === "http-channel") {
1898
- if (id !== "http") {
1899
- ctx.diagnostics.push({
1900
- severity: "error",
1901
- path: displayPath(ctx, entryPath),
1902
- message:
1903
- "httpChannel() configures the built-in HTTP channel and must be authored at channels/http.ts.",
1904
- });
1905
- continue;
1906
- }
1907
- httpChannelDefinition = mod.default as HttpChannelDefinition;
1908
- continue;
1909
- }
1910
-
1911
- if (kind !== "channel") {
1912
- ctx.diagnostics.push({
1913
- severity: "error",
1914
- path: displayPath(ctx, entryPath),
1915
- message:
1916
- id === "http"
1917
- ? "channels/http.ts must default-export httpChannel({ ... })."
1918
- : "Channel modules must default-export defineChannel({ ... }).",
1919
- });
1920
- continue;
1921
- }
1922
- if (id === "http") {
1923
- ctx.diagnostics.push({
1924
- severity: "error",
1925
- path: displayPath(ctx, entryPath),
1926
- message:
1927
- "The channel id `http` is reserved for the built-in HTTP channel; use httpChannel() to configure it.",
1928
- });
1929
- continue;
1930
- }
1931
- const definition = mod.default as ChannelDefinition;
1932
- for (const notice of definition.meta?.deprecations ?? []) {
1933
- ctx.diagnostics.push({
1934
- severity: "warning",
1935
- path: displayPath(ctx, entryPath),
1936
- message: notice,
1937
- });
1938
- }
1939
- channels.push({ id, definition });
1940
- }
1941
- return { channels, httpChannelDefinition };
1942
- }
1943
-
1944
- // ============================================================================
1945
- // Schedules
1946
- // ============================================================================
1947
-
1948
- async function loadSchedules(
1949
- ctx: DiscoveryContext,
1950
- schedulesDir: string
1951
- ): Promise<DiscoveredSchedule[]> {
1952
- if (!(await isDirectory(schedulesDir))) {
1953
- return [];
1954
- }
1955
- const schedules: DiscoveredSchedule[] = [];
1956
- for (const filePath of await walkFiles(schedulesDir)) {
1957
- const rel = toPosix(relative(schedulesDir, filePath));
1958
- if (rel.endsWith(".md")) {
1959
- const name = rel.slice(0, -".md".length);
1960
- const { data, body } = parseFrontmatter(await readFile(filePath, "utf8"));
1961
- const cron = data.cron;
1962
- if (cron === undefined || !isValidCron(cron)) {
1963
- ctx.diagnostics.push({
1964
- severity: "error",
1965
- path: displayPath(ctx, filePath),
1966
- message:
1967
- cron === undefined
1968
- ? "Markdown schedules require `cron` frontmatter."
1969
- : `Invalid cron expression: "${cron}".`,
1970
- });
1971
- continue;
1972
- }
1973
- const markdown = body.trim();
1974
- if (markdown === "") {
1975
- ctx.diagnostics.push({
1976
- severity: "error",
1977
- path: displayPath(ctx, filePath),
1978
- message: "Markdown schedules require a non-empty prompt body.",
1979
- });
1980
- continue;
1981
- }
1982
- schedules.push({
1983
- name,
1984
- cron,
1985
- kind: "markdown",
1986
- definition: {
1987
- __agentServe: "schedule",
1988
- cron,
1989
- markdown,
1990
- },
1991
- });
1992
- continue;
1993
- }
1994
-
1995
- if (!isModuleFile(basename(filePath))) {
1996
- continue;
1997
- }
1998
- const name = rel.slice(0, -extname(rel).length);
1999
- const definition = await importDefault(ctx, filePath, "schedule");
2000
- if (definition === undefined) {
2001
- continue;
2002
- }
2003
- const schedule = definition as ScheduleDefinition;
2004
- if ((schedule.markdown === undefined) === (schedule.run === undefined)) {
2005
- ctx.diagnostics.push({
2006
- severity: "error",
2007
- path: displayPath(ctx, filePath),
2008
- message: "Schedules require exactly one of `markdown` or `run`.",
2009
- });
2010
- continue;
2011
- }
2012
- if (!isValidCron(schedule.cron)) {
2013
- ctx.diagnostics.push({
2014
- severity: "error",
2015
- path: displayPath(ctx, filePath),
2016
- message: `Invalid cron expression: "${schedule.cron}".`,
2017
- });
2018
- continue;
2019
- }
2020
- schedules.push({
2021
- name,
2022
- cron: schedule.cron,
2023
- kind: schedule.run === undefined ? "markdown" : "run",
2024
- definition: schedule,
2025
- });
2026
- }
2027
- return schedules;
2028
- }
2029
-
2030
- // ============================================================================
2031
- // Hooks
2032
- // ============================================================================
2033
-
2034
- async function loadHooks(
2035
- ctx: DiscoveryContext,
2036
- hooksDir: string
2037
- ): Promise<DiscoveredHook[]> {
2038
- if (!(await isDirectory(hooksDir))) {
2039
- return [];
2040
- }
2041
- const hooks: DiscoveredHook[] = [];
2042
- for (const filePath of await walkFiles(hooksDir)) {
2043
- if (!isModuleFile(basename(filePath))) {
2044
- continue;
2045
- }
2046
- const rel = toPosix(relative(hooksDir, filePath));
2047
- const name = rel.slice(0, -extname(rel).length);
2048
- const definition = await importDefault(ctx, filePath, "hook");
2049
- if (definition === undefined) {
2050
- continue;
2051
- }
2052
- const hook = definition as HookDefinition;
2053
- if (
2054
- typeof hook.events !== "object" ||
2055
- hook.events === null ||
2056
- Object.keys(hook.events).length === 0
2057
- ) {
2058
- ctx.diagnostics.push({
2059
- severity: "warning",
2060
- path: displayPath(ctx, filePath),
2061
- message: "Hook declares no event handlers.",
2062
- });
2063
- continue;
2064
- }
2065
- hooks.push({ name, definition: hook });
2066
- }
2067
- return hooks;
2068
- }
2069
-
2070
- // ============================================================================
2071
- // A/B metrics (agent/ab.ts or agent/ab/*.ts) + agent/ab.config.ts
2072
- // ============================================================================
2073
-
2074
- async function loadABs(
2075
- ctx: DiscoveryContext,
2076
- agentDir: string
2077
- ): Promise<DiscoveredAB[]> {
2078
- const abs: DiscoveredAB[] = [];
2079
- const seen = new Set<string>();
2080
-
2081
- const single = await findModuleFile(agentDir, "ab");
2082
- if (single !== undefined) {
2083
- const definition = await importDefault(ctx, single, "ab");
2084
- if (definition !== undefined) {
2085
- const name = resolveABName(definition, "ab");
2086
- seen.add(name);
2087
- abs.push({
2088
- name,
2089
- definition: definition as DiscoveredAB["definition"],
2090
- });
2091
- }
2092
- }
2093
-
2094
- const absDir = join(agentDir, "ab");
2095
- if (await isDirectory(absDir)) {
2096
- for (const filePath of await walkFiles(absDir)) {
2097
- if (!isModuleFile(basename(filePath))) {
2098
- continue;
2099
- }
2100
- const rel = toPosix(relative(absDir, filePath));
2101
- const stem = rel.slice(0, -extname(rel).length);
2102
- const definition = await importDefault(ctx, filePath, "ab");
2103
- if (definition === undefined) {
2104
- continue;
2105
- }
2106
- const name = resolveABName(definition, stem);
2107
- if (seen.has(name)) {
2108
- ctx.diagnostics.push({
2109
- severity: "error",
2110
- path: displayPath(ctx, filePath),
2111
- message: `Duplicate A/B experiment name ${JSON.stringify(name)}.`,
2112
- });
2113
- continue;
2114
- }
2115
- seen.add(name);
2116
- abs.push({
2117
- name,
2118
- definition: definition as DiscoveredAB["definition"],
2119
- });
2120
- }
2121
- }
2122
-
2123
- return abs;
2124
- }
2125
-
2126
- async function loadABConfig(
2127
- ctx: DiscoveryContext,
2128
- agentDir: string
2129
- ): Promise<ABConfigFile | undefined> {
2130
- const path = await findModuleFile(agentDir, "ab.config");
2131
- if (path === undefined) {
2132
- return undefined;
2133
- }
2134
- try {
2135
- const mod = await ctx.importModule(path);
2136
- if (mod.default === null || typeof mod.default !== "object") {
2137
- ctx.diagnostics.push({
2138
- severity: "error",
2139
- path: displayPath(ctx, path),
2140
- message:
2141
- "ab.config.ts must default-export defineABConfig({ ... }) from @cursor/july/ab.",
2142
- });
2143
- return undefined;
2144
- }
2145
- return mod.default as ABConfigFile;
2146
- } catch (error) {
2147
- ctx.diagnostics.push({
2148
- severity: "error",
2149
- path: displayPath(ctx, path),
2150
- message: `Failed to load ab.config: ${error instanceof Error ? error.message : String(error)}`,
2151
- });
2152
- return undefined;
2153
- }
2154
- }
2155
-
2156
- // ============================================================================
2157
- // Storage (agent/storage.ts)
2158
- // ============================================================================
2159
-
2160
- async function loadStorage(
2161
- ctx: DiscoveryContext,
2162
- agentDir: string,
2163
- architecture: AgentArchitecture | undefined
2164
- ): Promise<StorageDefinition | undefined> {
2165
- const path = await findModuleFile(agentDir, "storage");
2166
- if (path === undefined) {
2167
- return undefined;
2168
- }
2169
- if (architecture === "v2") {
2170
- // Storage is a hosting-mode decision on v2, made by
2171
- // framework-storage-selection.ts: the state root when self-hosted,
2172
- // Cursor-managed storage on Cursor hosting. Fail early (error
2173
- // diagnostics refuse serve and deploy) rather than silently running
2174
- // with storage the author believes they configured. Not imported.
2175
- ctx.diagnostics.push({
2176
- severity: "error",
2177
- path: displayPath(ctx, path),
2178
- message:
2179
- 'agent/storage.ts is not supported on architecture "v2": the platform selects storage (the local state root when self-hosted; Cursor-managed storage on Cursor hosting). Delete the file.',
2180
- });
2181
- return undefined;
2182
- }
2183
- // v1 keeps loading the authored sink, with notice: defineStorage is going
2184
- // away with the v1 control plane.
2185
- ctx.diagnostics.push({
2186
- severity: "warning",
2187
- path: displayPath(ctx, path),
2188
- message:
2189
- 'agent/storage.ts (defineStorage) is deprecated and going away: architecture "v2" selects storage automatically (the local state root when self-hosted; Cursor-managed storage on Cursor hosting). Still honored on v1.',
2190
- });
2191
- const definition = await importDefault(ctx, path, "storage");
2192
- return definition as StorageDefinition | undefined;
2193
- }
2194
-
2195
- // ============================================================================
2196
- // OpenTelemetry (agent/otel.ts)
2197
- // ============================================================================
2198
-
2199
- async function loadOtel(
2200
- ctx: DiscoveryContext,
2201
- agentDir: string
2202
- ): Promise<OtelDefinition | undefined> {
2203
- const path = await findModuleFile(agentDir, "otel");
2204
- if (path === undefined) {
2205
- return undefined;
2206
- }
2207
- const definition = await importDefault(ctx, path, "otel");
2208
- return definition as OtelDefinition | undefined;
2209
- }
2210
-
2211
- // ============================================================================
2212
- // Artifacts (agent/artifacts.ts)
2213
- // ============================================================================
2214
-
2215
- async function loadArtifacts(
2216
- ctx: DiscoveryContext,
2217
- agentDir: string
2218
- ): Promise<ArtifactsDefinition | undefined> {
2219
- const path = await findModuleFile(agentDir, "artifacts");
2220
- if (path === undefined) {
2221
- return undefined;
2222
- }
2223
- const imported = await importDefault(ctx, path, "artifacts");
2224
- if (imported === undefined) {
2225
- return undefined;
2226
- }
2227
- const definition = imported as ArtifactsDefinition;
2228
- if (
2229
- definition.agentTool === true &&
2230
- Object.keys(definition.kinds ?? {}).length === 0
2231
- ) {
2232
- ctx.diagnostics.push({
2233
- severity: "error",
2234
- path: displayPath(ctx, path),
2235
- message:
2236
- "artifacts.agentTool requires kinds: the tag_artifact tool needs at least one declared kind; its descriptions are the tool prompt.",
2237
- });
2238
- }
2239
- return definition;
2240
- }
2241
-
2242
- /**
2243
- * Materialize the `tag_artifact` built-in when `defineArtifacts` set
2244
- * `agentTool: true`. Appended after `loadAgent` because the tool is
2245
- * generated from the artifacts kinds registry, not `AgentConfig.builtinTools`.
2246
- */
2247
- async function appendArtifactTool(
2248
- ctx: DiscoveryContext,
2249
- agentDir: string,
2250
- agent: ResolvedAgent,
2251
- artifacts: ArtifactsDefinition | undefined
2252
- ): Promise<void> {
2253
- const tool = artifactBuiltinTool(artifacts);
2254
- if (tool === undefined) {
2255
- return;
2256
- }
2257
- const artifactsPath = displayPath(
2258
- ctx,
2259
- (await findModuleFile(agentDir, "artifacts")) ??
2260
- join(agentDir, "artifacts.ts")
2261
- );
2262
- if (agent.tools.some((t) => t.name === TAG_ARTIFACT_TOOL_NAME)) {
2263
- ctx.diagnostics.push({
2264
- severity: "warning",
2265
- path: artifactsPath,
2266
- message: `Authored tool "${TAG_ARTIFACT_TOOL_NAME}" shadows the built-in tool of the same name; the authored tool wins.`,
2267
- });
2268
- return;
2269
- }
2270
- agent.tools.push(tool);
2271
- }
2272
-
2273
- function resolveABName(definition: unknown, fallback: string): string {
2274
- if (
2275
- typeof definition === "object" &&
2276
- definition !== null &&
2277
- "name" in definition &&
2278
- typeof (definition as { name: unknown }).name === "string" &&
2279
- (definition as { name: string }).name.length > 0
2280
- ) {
2281
- return (definition as { name: string }).name;
2282
- }
2283
- return fallback;
2284
- }
2285
-
2286
- // ============================================================================
2287
- // Sandbox seed files
2288
- // ============================================================================
2289
-
2290
- async function loadSeedFiles(
2291
- ctx: DiscoveryContext,
2292
- agentDir: string
2293
- ): Promise<SeedFile[]> {
2294
- const sandboxDir = join(agentDir, "sandbox");
2295
- for (const candidate of ["sandbox.ts", join("sandbox", "sandbox.ts")]) {
2296
- if (await isFile(join(agentDir, candidate))) {
2297
- ctx.diagnostics.push({
2298
- severity: "warning",
2299
- path: displayPath(ctx, join(agentDir, candidate)),
2300
- message:
2301
- "Custom sandbox backends are not supported; the agent runs in the session workspace directory. Ignoring.",
2302
- });
2303
- }
2304
- }
2305
- const workspaceDir = join(sandboxDir, "workspace");
2306
- if (!(await isDirectory(workspaceDir))) {
2307
- return [];
2308
- }
2309
- const seedFiles: SeedFile[] = [];
2310
- for (const filePath of await walkFiles(workspaceDir)) {
2311
- seedFiles.push({
2312
- relativePath: toPosix(relative(workspaceDir, filePath)),
2313
- sourcePath: filePath,
2314
- });
2315
- }
2316
- return seedFiles;
2317
- }
2318
-
2319
- // ============================================================================
2320
- // Info projection
2321
- // ============================================================================
2322
-
2323
- export function projectInfo(project: AgentProject): AgentProjectInfo {
2324
- const agent = project.agent;
2325
- const httpRoutes: AgentProjectInfo["channels"][number]["routes"] = [
2326
- { method: "POST", path: "/v1/session", url: "/v1/session" },
2327
- {
2328
- method: "POST",
2329
- path: "/v1/session/:sessionId",
2330
- url: "/v1/session/:sessionId",
2331
- },
2332
- {
2333
- method: "POST",
2334
- path: "/v1/session/:sessionId/stop",
2335
- url: "/v1/session/:sessionId/stop",
2336
- },
2337
- {
2338
- method: "GET",
2339
- path: "/v1/session/:sessionId/stream",
2340
- url: "/v1/session/:sessionId/stream",
2341
- },
2342
- {
2343
- method: "GET",
2344
- path: "/v1/session/:sessionId/approvals",
2345
- url: "/v1/session/:sessionId/approvals",
2346
- },
2347
- {
2348
- method: "POST",
2349
- path: "/v1/session/:sessionId/approvals/:callId",
2350
- url: "/v1/session/:sessionId/approvals/:callId",
2351
- },
2352
- ];
2353
- return {
2354
- name: project.name,
2355
- description: agent.description,
2356
- model: agent.model,
2357
- runtime: agent.runtime,
2358
- cloud:
2359
- agent.cloud === undefined ? undefined : toPublicCloudInfo(agent.cloud),
2360
- local: agent.local,
2361
- hosting: agent.hosting,
2362
- concurrency: agent.concurrency,
2363
- mcp: { path: MCP_ENDPOINT_PATH, tools: mcpEndpointToolNames(project) },
2364
- instructions:
2365
- agent.instructions === undefined
2366
- ? undefined
2367
- : {
2368
- chars: agent.instructions.length,
2369
- preview:
2370
- agent.instructions.length > 280
2371
- ? `${agent.instructions.slice(0, 277)}...`
2372
- : agent.instructions,
2373
- },
2374
- tools: agent.tools.map((tool) => ({
2375
- name: tool.name,
2376
- description: tool.description,
2377
- execution: tool.execution,
2378
- needsApproval: tool.needsApproval,
2379
- effect: tool.effect,
2380
- inputSchema: tool.inputSchema,
2381
- })),
2382
- skills: agent.skills.map((skill) => ({
2383
- name: skill.name,
2384
- description: skill.description,
2385
- })),
2386
- connections: agent.connections.map((connection) => ({
2387
- name: connection.name,
2388
- transport: connectionTransportLabel(connection.transport),
2389
- advertiseTools: connection.advertiseTools,
2390
- effects: connection.effects,
2391
- cursorAccountServers:
2392
- "cursorAccount" in connection.transport
2393
- ? (connection.transport.servers ?? "*")
2394
- : undefined,
2395
- })),
2396
- subagents: agent.subagents.map((subagent) => ({
2397
- name: subagent.name,
2398
- description: subagent.description,
2399
- model: subagent.model,
2400
- })),
2401
- channels: [
2402
- { id: "http", routes: httpRoutes },
2403
- ...project.channels.map((channel) => {
2404
- const info: AgentProjectInfo["channels"][number] = {
2405
- id: channel.id,
2406
- routes: (channel.definition.routes ?? []).map((r) => {
2407
- const routeInfo: AgentProjectInfo["channels"][number]["routes"][number] =
2408
- {
2409
- method: r.method,
2410
- path: r.path,
2411
- url: `/v1/channels/${channel.id}${r.path}`,
2412
- };
2413
- if (r.description !== undefined) {
2414
- routeInfo.description = r.description;
2415
- }
2416
- if (r.bodySchema !== undefined) {
2417
- routeInfo.bodySchema = toolInputJsonSchema(r.bodySchema);
2418
- }
2419
- if (r.querySchema !== undefined) {
2420
- routeInfo.querySchema = toolInputJsonSchema(r.querySchema);
2421
- }
2422
- return routeInfo;
2423
- }),
2424
- };
2425
- const meta = channel.definition.meta;
2426
- if (meta?.kind !== undefined) {
2427
- info.kind = meta.kind;
2428
- }
2429
- if (meta?.githubWebhookEvents !== undefined) {
2430
- info.githubWebhookEvents = meta.githubWebhookEvents;
2431
- }
2432
- if (meta?.originWebhookEvents !== undefined) {
2433
- info.originWebhookEvents = meta.originWebhookEvents;
2434
- }
2435
- if (meta?.deploymentEventTypes !== undefined) {
2436
- info.deploymentEventTypes = meta.deploymentEventTypes;
2437
- }
2438
- if (meta?.slackChannelPosts !== undefined) {
2439
- info.slackChannelPosts = meta.slackChannelPosts;
2440
- }
2441
- return info;
2442
- }),
2443
- ],
2444
- schedules: project.schedules.map((schedule) => ({
2445
- name: schedule.name,
2446
- cron: schedule.cron,
2447
- kind: schedule.kind,
2448
- })),
2449
- hooks: project.hooks.map((hook) => hook.name),
2450
- abs: project.abs.map((experiment) => experiment.name),
2451
- ...(project.abConfig === undefined && project.abs.length === 0
2452
- ? {}
2453
- : {
2454
- abConfig: {
2455
- maxPlaygroundSessions: resolveABMaxPlaygroundSessions(
2456
- project.abConfig?.maxPlaygroundSessions
2457
- ),
2458
- durableSamples: project.storage?.abs !== undefined,
2459
- durableSnapshots: project.storage?.abs?.putSnapshot !== undefined,
2460
- },
2461
- }),
2462
- storage:
2463
- project.storage === undefined
2464
- ? undefined
2465
- : { name: project.storage.name },
2466
- artifacts:
2467
- project.artifacts === undefined
2468
- ? undefined
2469
- : {
2470
- kinds: Object.keys(project.artifacts.kinds ?? {}),
2471
- agentTool: project.artifacts.agentTool === true,
2472
- max: resolveArtifactsMax(project.artifacts),
2473
- },
2474
- otel:
2475
- project.otel === undefined
2476
- ? undefined
2477
- : {
2478
- enabled: true as const,
2479
- serviceName: project.otel.serviceName,
2480
- },
2481
- diagnostics: project.diagnostics,
2482
- };
2483
- }
2484
-
2485
- // ============================================================================
2486
- // Shared helpers
2487
- // ============================================================================
2488
-
2489
- async function importDefault(
2490
- ctx: DiscoveryContext,
2491
- modulePath: string,
2492
- expectedKind:
2493
- | "agent"
2494
- | "tool"
2495
- | "skill"
2496
- | "connection"
2497
- | "schedule"
2498
- | "hook"
2499
- | "ab"
2500
- | "storage"
2501
- | "artifacts"
2502
- | "otel"
2503
- ): Promise<unknown> {
2504
- let mod: Record<string, unknown>;
2505
- try {
2506
- mod = await ctx.importModule(modulePath);
2507
- } catch (error) {
2508
- ctx.diagnostics.push({
2509
- severity: "error",
2510
- path: displayPath(ctx, modulePath),
2511
- message: `Failed to load module: ${errorMessage(error)}`,
2512
- });
2513
- return undefined;
2514
- }
2515
- const kind = getDefinitionKind(mod.default);
2516
- if (kind !== expectedKind) {
2517
- const helper = DEFINE_HELPER_NAME[expectedKind];
2518
- ctx.diagnostics.push({
2519
- severity: "error",
2520
- path: displayPath(ctx, modulePath),
2521
- message:
2522
- kind === undefined
2523
- ? `Module must default-export ${helper}({ ... }).`
2524
- : `Expected a ${expectedKind} definition but found "${kind}" (wrong authored slot?).`,
2525
- });
2526
- return undefined;
2527
- }
2528
- return mod.default;
2529
- }
2530
-
2531
- const DEFINE_HELPER_NAME: Record<
2532
- | "agent"
2533
- | "instructions"
2534
- | "tool"
2535
- | "skill"
2536
- | "connection"
2537
- | "channel"
2538
- | "http-channel"
2539
- | "schedule"
2540
- | "hook"
2541
- | "ab"
2542
- | "storage"
2543
- | "artifacts"
2544
- | "otel",
2545
- string
2546
- > = {
2547
- agent: "defineAgent",
2548
- instructions: "defineInstructions",
2549
- tool: "defineTool",
2550
- skill: "defineSkill",
2551
- connection: "defineConnection",
2552
- channel: "defineChannel",
2553
- "http-channel": "httpChannel",
2554
- schedule: "defineSchedule",
2555
- hook: "defineHook",
2556
- ab: "defineAB",
2557
- storage: "defineStorage",
2558
- artifacts: "defineArtifacts",
2559
- otel: "defineOtel",
2560
- };
2561
-
2562
- async function warnUnknownSlots(
2563
- ctx: DiscoveryContext,
2564
- agentDir: string
2565
- ): Promise<void> {
2566
- const known = new Set([
2567
- "agent",
2568
- "instructions",
2569
- "tools",
2570
- "skills",
2571
- "mcp-connections",
2572
- "host-connections",
2573
- // Legacy slot name; resolveMcpConnectionsDir warns and still loads it.
2574
- "connections",
2575
- "subagents",
2576
- "channels",
2577
- "schedules",
2578
- "hooks",
2579
- "ab",
2580
- "sandbox",
2581
- "lib",
2582
- // Vite playground custom tool chips (agent/playground/tools).
2583
- "playground",
2584
- ]);
2585
- for (const entry of await sortedEntries(agentDir)) {
2586
- if (!entry.isDirectory() || entry.name.startsWith(".")) {
2587
- continue;
2588
- }
2589
- if (entry.name === "node_modules" || known.has(entry.name)) {
2590
- continue;
2591
- }
2592
- // Footgun: evals live at project-root `evals/`, not under `agent/`.
2593
- if (entry.name === "evals") {
2594
- ctx.diagnostics.push({
2595
- severity: "warning",
2596
- path: displayPath(ctx, join(agentDir, entry.name)),
2597
- message:
2598
- 'Directory "evals" under agent/ is ignored — put defineEval files at project-root evals/ instead.',
2599
- });
2600
- continue;
2601
- }
2602
- ctx.diagnostics.push({
2603
- severity: "warning",
2604
- path: displayPath(ctx, join(agentDir, entry.name)),
2605
- message: `Unrecognized directory "${entry.name}" under agent/ (not an authored slot; use lib/ for shared code).`,
2606
- });
2607
- }
2608
- }
2609
-
2610
- async function hasFlatLayoutMarkers(rootDir: string): Promise<boolean> {
2611
- if (await isFile(join(rootDir, "instructions.md"))) {
2612
- return true;
2613
- }
2614
- return (await findModuleFile(rootDir, "agent")) !== undefined;
2615
- }
2616
-
2617
- async function readPackageName(rootDir: string): Promise<string | undefined> {
2618
- const packageJsonPath = join(rootDir, "package.json");
2619
- if (!(await isFile(packageJsonPath))) {
2620
- return undefined;
2621
- }
2622
- try {
2623
- const parsed = JSON.parse(await readFile(packageJsonPath, "utf8")) as {
2624
- name?: unknown;
2625
- };
2626
- if (typeof parsed.name === "string" && parsed.name !== "") {
2627
- return parsed.name;
2628
- }
2629
- } catch {
2630
- // Malformed package.json falls back to the directory name.
2631
- }
2632
- return undefined;
2633
- }
2634
-
2635
- async function findModuleFile(
2636
- dir: string,
2637
- stem: string
2638
- ): Promise<string | undefined> {
2639
- for (const extension of [".ts", ".mts", ".js", ".mjs"]) {
2640
- const candidate = join(dir, `${stem}${extension}`);
2641
- if (await isFile(candidate)) {
2642
- return candidate;
2643
- }
2644
- }
2645
- return undefined;
2646
- }
2647
-
2648
- function isModuleFile(fileName: string): boolean {
2649
- if (fileName.endsWith(".d.ts") || fileName.startsWith(".")) {
2650
- return false;
2651
- }
2652
- if (!MODULE_EXTENSIONS.has(extname(fileName))) {
2653
- return false;
2654
- }
2655
- // Colocated unit tests (Vitest/Jest convention) are not definitions.
2656
- // Without this, `channels/slack.test.ts` is scanned as a channel named
2657
- // "slack.test", fails NAME_PATTERN, and the whole agent refuses to serve.
2658
- const stem = moduleStem(fileName);
2659
- return !stem.endsWith(".test") && !stem.endsWith(".spec");
2660
- }
2661
-
2662
- function moduleStem(fileName: string): string {
2663
- return fileName.slice(0, -extname(fileName).length);
2664
- }
2665
-
2666
- async function sortedEntries(dir: string): Promise<Dirent[]> {
2667
- const entries = await readdir(dir, { withFileTypes: true });
2668
- return entries.sort((a, b) => a.name.localeCompare(b.name));
2669
- }
2670
-
2671
- async function walkFiles(dir: string): Promise<string[]> {
2672
- const files: string[] = [];
2673
- for (const entry of await sortedEntries(dir)) {
2674
- if (entry.name.startsWith(".")) {
2675
- continue;
2676
- }
2677
- const entryPath = join(dir, entry.name);
2678
- if (entry.isDirectory()) {
2679
- files.push(...(await walkFiles(entryPath)));
2680
- } else if (entry.isFile()) {
2681
- files.push(entryPath);
2682
- }
2683
- }
2684
- return files;
2685
- }
2686
-
2687
- async function isDirectory(path: string): Promise<boolean> {
2688
- try {
2689
- return (await stat(path)).isDirectory();
2690
- } catch {
2691
- return false;
2692
- }
2693
- }
2694
-
2695
- async function isFile(path: string): Promise<boolean> {
2696
- try {
2697
- return (await stat(path)).isFile();
2698
- } catch {
2699
- return false;
2700
- }
2701
- }
2702
-
2703
- function isValidModelSetting(model: unknown): model is ModelSetting {
2704
- if (typeof model === "string") {
2705
- return model.trim() !== "";
2706
- }
2707
- return (
2708
- typeof model === "object" &&
2709
- model !== null &&
2710
- typeof (model as { id?: unknown }).id === "string" &&
2711
- (model as { id: string }).id.trim() !== ""
2712
- );
2713
- }
2714
-
2715
- function isBlank(value: unknown): boolean {
2716
- return typeof value !== "string" || value.trim() === "";
2717
- }
2718
-
2719
- function toPosix(path: string): string {
2720
- return path.split("\\").join("/");
2721
- }
2722
-
2723
- function displayPath(ctx: DiscoveryContext, absolutePath: string): string {
2724
- const rel = relative(ctx.rootDir, absolutePath);
2725
- return rel.startsWith("..") ? absolutePath : toPosix(rel);
2726
- }
2727
-
2728
- function errorMessage(error: unknown): string {
2729
- return error instanceof Error ? error.message : String(error);
2730
- }
7
+ *
8
+ * The loaders live under `./discovery/`, one module per authored slot;
9
+ * this module is the public surface the CLI, server, and tests import.
10
+ */
11
+
12
+ export { runtimeArchitectureCompatibilityError } from "./discovery/agent-config.js";
13
+ export type { LoadAgentProjectOptions } from "./discovery/context.js";
14
+ export { projectInfo } from "./discovery/info.js";
15
+ export {
16
+ type ChildAgentDir,
17
+ formatDiagnostics,
18
+ hasErrorDiagnostics,
19
+ isAgentProjectDir,
20
+ listChildAgentDirs,
21
+ loadAgentProject,
22
+ loadAgentProjects,
23
+ RESERVED_SLUGS,
24
+ resolveAgentMounts,
25
+ } from "./discovery/project.js";
26
+ export { deriveSkillDescription } from "./discovery/skills.js";