@cursor/july 0.1.96 → 0.1.97

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 (321) hide show
  1. package/README.md +3 -2
  2. package/dist/bin/agent-serve.js +14 -3
  3. package/dist/channels/origin/api.d.ts +15 -4
  4. package/dist/channels/origin/api.d.ts.map +1 -1
  5. package/dist/channels/origin/api.js +19 -3
  6. package/dist/channels/origin/binding.d.ts +11 -0
  7. package/dist/channels/origin/binding.d.ts.map +1 -1
  8. package/dist/channels/origin/binding.js +41 -25
  9. package/dist/channels/origin/index.d.ts +1 -0
  10. package/dist/channels/origin/index.d.ts.map +1 -1
  11. package/dist/channels/origin/index.js +1 -0
  12. package/dist/channels/origin/origin-channel.d.ts.map +1 -1
  13. package/dist/channels/origin/origin-channel.js +3 -0
  14. package/dist/channels/origin/reactions.d.ts +56 -0
  15. package/dist/channels/origin/reactions.d.ts.map +1 -0
  16. package/dist/channels/origin/reactions.js +67 -0
  17. package/dist/channels/origin/types.d.ts +11 -0
  18. package/dist/channels/origin/types.d.ts.map +1 -1
  19. package/dist/channels/slack/approvals.d.ts +16 -0
  20. package/dist/channels/slack/approvals.d.ts.map +1 -1
  21. package/dist/channels/slack/approvals.js +86 -0
  22. package/dist/channels/slack/cli-project.d.ts +19 -0
  23. package/dist/channels/slack/cli-project.d.ts.map +1 -0
  24. package/dist/channels/slack/cli-project.js +234 -0
  25. package/dist/channels/slack/defaults.d.ts.map +1 -1
  26. package/dist/channels/slack/defaults.js +97 -39
  27. package/dist/channels/slack/init.d.ts +3 -2
  28. package/dist/channels/slack/init.d.ts.map +1 -1
  29. package/dist/channels/slack/init.js +5 -5
  30. package/dist/channels/slack/interactive.d.ts +2 -1
  31. package/dist/channels/slack/interactive.d.ts.map +1 -1
  32. package/dist/channels/slack/interactive.js +15 -4
  33. package/dist/channels/slack/manifest.d.ts +1 -1
  34. package/dist/channels/slack/manifest.js +1 -1
  35. package/dist/channels/slack/setup.d.ts +1 -1
  36. package/dist/channels/slack/setup.d.ts.map +1 -1
  37. package/dist/channels/slack/setup.js +10 -6
  38. package/dist/channels/slack/slack-channel.d.ts.map +1 -1
  39. package/dist/channels/slack/slack-channel.js +1 -0
  40. package/dist/channels/slack/tool-approval-events.d.ts.map +1 -1
  41. package/dist/channels/slack/tool-approval-events.js +91 -1
  42. package/dist/docs/404.html +2 -2
  43. package/dist/docs/ab.html +4 -4
  44. package/dist/docs/assets/{app.jXleCmOc.js → app.B3rWNYE1.js} +1 -1
  45. package/dist/docs/assets/chunks/@localSearchIndexroot.BU9nHdnQ.js +1 -0
  46. package/dist/docs/assets/chunks/{VPLocalSearchBox.8LhyglIT.js → VPLocalSearchBox.C1lhqJJR.js} +1 -1
  47. package/dist/docs/assets/chunks/{theme.9ged-pr2.js → theme.B40_SXuv.js} +2 -2
  48. package/dist/docs/assets/{deployment.md.D9msOFOW.js → deployment.md.MFPKqsqI.js} +2 -2
  49. package/dist/docs/assets/{guides_mcp-oauth.md.DPYmBCbV.js → guides_mcp-oauth.md.CN-6YmTJ.js} +4 -4
  50. package/dist/docs/assets/{guides_mcp-oauth.md.DPYmBCbV.lean.js → guides_mcp-oauth.md.CN-6YmTJ.lean.js} +1 -1
  51. package/dist/docs/assets/guides_slack.md.VDXQV3ja.js +22 -0
  52. package/dist/docs/assets/guides_slack.md.VDXQV3ja.lean.js +1 -0
  53. package/dist/docs/assets/index.md.CVeRUOeZ.js +5 -0
  54. package/dist/docs/assets/{reference_agent-config.md.Cp_x38Nl.js → reference_agent-config.md.BHeYMgme.js} +1 -1
  55. package/dist/docs/assets/{reference_cli.md.CCnG-nJx.js → reference_cli.md.RyZf5OTE.js} +9 -6
  56. package/dist/docs/assets/{reference_cli.md.CCnG-nJx.lean.js → reference_cli.md.RyZf5OTE.lean.js} +1 -1
  57. package/dist/docs/assets/{reference_connections.md.BH8Oc0D0.js → reference_connections.md.DxldvyIB.js} +4 -3
  58. package/dist/docs/assets/{reference_http-api.md.D89k1mdm.js → reference_http-api.md.5zOAbV86.js} +1 -1
  59. package/dist/docs/assets/reference_sessions.md.NxFP_CAC.js +1 -0
  60. package/dist/docs/assets/{reference_tools.md.BHeXn2id.js → reference_tools.md.B84gw9Ii.js} +1 -1
  61. package/dist/docs/assets/templates_agents-md.md.CXSMZljE.js +2 -0
  62. package/dist/docs/assets/templates_agents-md.md.CXSMZljE.lean.js +1 -0
  63. package/dist/docs/assets/troubleshooting.md.DCiPBhYs.js +1 -0
  64. package/dist/docs/building-with-agents.html +4 -4
  65. package/dist/docs/concepts.html +4 -4
  66. package/dist/docs/deployment.html +7 -7
  67. package/dist/docs/deployment.md +5 -4
  68. package/dist/docs/evals.html +4 -4
  69. package/dist/docs/guides/agent-to-agent.html +4 -4
  70. package/dist/docs/guides/cloud-runtime.html +4 -4
  71. package/dist/docs/guides/convert-automation.html +4 -4
  72. package/dist/docs/guides/github.html +4 -4
  73. package/dist/docs/guides/human-in-the-loop.html +4 -4
  74. package/dist/docs/guides/mcp-oauth.html +8 -8
  75. package/dist/docs/guides/mcp-oauth.md +26 -8
  76. package/dist/docs/guides/opentelemetry.html +4 -4
  77. package/dist/docs/guides/slack.html +17 -33
  78. package/dist/docs/guides/slack.md +92 -201
  79. package/dist/docs/guides/webhooks.html +4 -4
  80. package/dist/docs/hashmap.json +1 -1
  81. package/dist/docs/hillclimbing.html +4 -4
  82. package/dist/docs/index.html +6 -6
  83. package/dist/docs/index.md +5 -3
  84. package/dist/docs/llms-full.txt +299 -653
  85. package/dist/docs/llms.txt +2 -2
  86. package/dist/docs/quickstart.html +4 -4
  87. package/dist/docs/reference/agent-config.html +6 -6
  88. package/dist/docs/reference/agent-config.md +2 -2
  89. package/dist/docs/reference/artifacts.html +4 -4
  90. package/dist/docs/reference/channels.html +4 -4
  91. package/dist/docs/reference/cli.html +13 -10
  92. package/dist/docs/reference/cli.md +38 -16
  93. package/dist/docs/reference/connections.html +9 -8
  94. package/dist/docs/reference/connections.md +6 -3
  95. package/dist/docs/reference/hooks.html +4 -4
  96. package/dist/docs/reference/http-api.html +6 -6
  97. package/dist/docs/reference/http-api.md +2 -1
  98. package/dist/docs/reference/instructions.html +4 -4
  99. package/dist/docs/reference/playground.html +4 -4
  100. package/dist/docs/reference/project-layout.html +4 -4
  101. package/dist/docs/reference/prompt.html +4 -4
  102. package/dist/docs/reference/schedules.html +4 -4
  103. package/dist/docs/reference/sessions.html +5 -5
  104. package/dist/docs/reference/sessions.md +4 -2
  105. package/dist/docs/reference/skills.html +4 -4
  106. package/dist/docs/reference/subagents.html +4 -4
  107. package/dist/docs/reference/tools.html +6 -6
  108. package/dist/docs/reference/tools.md +9 -2
  109. package/dist/docs/scaffolding-agents.html +4 -4
  110. package/dist/docs/storage.html +4 -4
  111. package/dist/docs/templates/agentic-owners.html +4 -4
  112. package/dist/docs/templates/agents-md.html +28 -0
  113. package/dist/docs/templates/agents-md.md +105 -0
  114. package/dist/docs/templates/code-wiki.html +5 -5
  115. package/dist/docs/templates/demo.html +4 -4
  116. package/dist/docs/templates/pr-autofixer.html +4 -4
  117. package/dist/docs/templates/security-help.html +4 -4
  118. package/dist/docs/templates/security-reviewer.html +5 -5
  119. package/dist/docs/templates/triage.html +4 -4
  120. package/dist/docs/troubleshooting.html +5 -5
  121. package/dist/docs/troubleshooting.md +2 -2
  122. package/dist/evals/run-facts.d.ts.map +1 -1
  123. package/dist/evals/run-facts.js +27 -0
  124. package/dist/internal/advertise-tools.d.ts.map +1 -1
  125. package/dist/internal/advertise-tools.js +15 -9
  126. package/dist/internal/approval-gate.d.ts +12 -13
  127. package/dist/internal/approval-gate.d.ts.map +1 -1
  128. package/dist/internal/approval-gate.js +16 -21
  129. package/dist/internal/chat-client.js +10 -0
  130. package/dist/internal/cli-deploy.d.ts +5 -0
  131. package/dist/internal/cli-deploy.d.ts.map +1 -1
  132. package/dist/internal/cli-deploy.js +50 -17
  133. package/dist/internal/cli-mcp-oauth.js +8 -18
  134. package/dist/internal/cli-slack-manual.d.ts +24 -2
  135. package/dist/internal/cli-slack-manual.d.ts.map +1 -1
  136. package/dist/internal/cli-slack-manual.js +251 -20
  137. package/dist/internal/cli-slack.d.ts +9 -2
  138. package/dist/internal/cli-slack.d.ts.map +1 -1
  139. package/dist/internal/cli-slack.js +29 -82
  140. package/dist/internal/cursor/account-mcp.d.ts +1 -0
  141. package/dist/internal/cursor/account-mcp.d.ts.map +1 -1
  142. package/dist/internal/cursor/account-mcp.js +17 -0
  143. package/dist/internal/cursor-agent-template.d.ts +1 -1
  144. package/dist/internal/cursor-agent-template.d.ts.map +1 -1
  145. package/dist/internal/cursor-agent-template.js +1 -0
  146. package/dist/internal/deploy-client.d.ts +17 -0
  147. package/dist/internal/deploy-client.d.ts.map +1 -1
  148. package/dist/internal/deploy-client.js +31 -10
  149. package/dist/internal/env-file.d.ts +19 -0
  150. package/dist/internal/env-file.d.ts.map +1 -0
  151. package/dist/internal/env-file.js +90 -0
  152. package/dist/internal/framework-file-storage.d.ts +1 -1
  153. package/dist/internal/framework-file-storage.d.ts.map +1 -1
  154. package/dist/internal/framework-file-storage.js +6 -0
  155. package/dist/internal/hosted-delivery-protocol.d.ts +7 -0
  156. package/dist/internal/hosted-delivery-protocol.d.ts.map +1 -1
  157. package/dist/internal/hosted-delivery-protocol.js +7 -0
  158. package/dist/internal/hosted-delivery.d.ts +6 -0
  159. package/dist/internal/hosted-delivery.d.ts.map +1 -1
  160. package/dist/internal/hosted-delivery.js +63 -22
  161. package/dist/internal/hosted-execution-diag.d.ts +48 -0
  162. package/dist/internal/hosted-execution-diag.d.ts.map +1 -0
  163. package/dist/internal/hosted-execution-diag.js +150 -0
  164. package/dist/internal/http-channel.d.ts.map +1 -1
  165. package/dist/internal/http-channel.js +81 -1
  166. package/dist/internal/init-project.d.ts.map +1 -1
  167. package/dist/internal/init-project.js +2 -0
  168. package/dist/internal/logs-client.d.ts +5 -0
  169. package/dist/internal/logs-client.d.ts.map +1 -1
  170. package/dist/internal/logs-client.js +5 -0
  171. package/dist/internal/mcp-endpoint.js +2 -3
  172. package/dist/internal/mcp-host.d.ts +10 -0
  173. package/dist/internal/mcp-host.d.ts.map +1 -1
  174. package/dist/internal/mcp-host.js +57 -8
  175. package/dist/internal/mcp-oauth-on-demand.d.ts +68 -0
  176. package/dist/internal/mcp-oauth-on-demand.d.ts.map +1 -0
  177. package/dist/internal/mcp-oauth-on-demand.js +287 -0
  178. package/dist/internal/mcp-oauth-persist.d.ts +42 -0
  179. package/dist/internal/mcp-oauth-persist.d.ts.map +1 -0
  180. package/dist/internal/mcp-oauth-persist.js +92 -0
  181. package/dist/internal/mcp-oauth.d.ts +1 -0
  182. package/dist/internal/mcp-oauth.d.ts.map +1 -1
  183. package/dist/internal/mcp-oauth.js +10 -2
  184. package/dist/internal/otel/collector.d.ts.map +1 -1
  185. package/dist/internal/otel/collector.js +22 -9
  186. package/dist/internal/server.d.ts +3 -0
  187. package/dist/internal/server.d.ts.map +1 -1
  188. package/dist/internal/server.js +49 -22
  189. package/dist/internal/session-engine.d.ts +88 -3
  190. package/dist/internal/session-engine.d.ts.map +1 -1
  191. package/dist/internal/session-engine.js +515 -43
  192. package/dist/internal/sessions-client.d.ts +9 -7
  193. package/dist/internal/sessions-client.d.ts.map +1 -1
  194. package/dist/internal/sessions-client.js +3 -1
  195. package/dist/internal/storage-coordinator.d.ts +7 -0
  196. package/dist/internal/storage-coordinator.d.ts.map +1 -1
  197. package/dist/internal/storage-coordinator.js +28 -5
  198. package/dist/internal/storage-roles.d.ts +5 -0
  199. package/dist/internal/storage-roles.d.ts.map +1 -1
  200. package/dist/internal/stream-progress.d.ts.map +1 -1
  201. package/dist/internal/stream-progress.js +10 -0
  202. package/dist/internal/tool-policy.d.ts +9 -1
  203. package/dist/internal/tool-policy.d.ts.map +1 -1
  204. package/dist/internal/tool-policy.js +13 -0
  205. package/dist/internal/trajectory.d.ts.map +1 -1
  206. package/dist/internal/trajectory.js +10 -0
  207. package/dist/internal/turn-outbox.d.ts +24 -0
  208. package/dist/internal/turn-outbox.d.ts.map +1 -0
  209. package/dist/internal/turn-outbox.js +80 -0
  210. package/dist/playground/assets/{index-Cx9CclK_.js → index-DrkI6y5O.js} +45 -45
  211. package/dist/playground/assets/index-DzNGwm7q.css +1 -0
  212. package/dist/playground/index.html +2 -2
  213. package/dist/storage-backends/cursor-hosted.d.ts +1 -1
  214. package/dist/storage-backends/cursor-hosted.d.ts.map +1 -1
  215. package/dist/storage-backends/cursor-hosted.js +12 -0
  216. package/dist/storage.d.ts +8 -0
  217. package/dist/storage.d.ts.map +1 -1
  218. package/dist/types.d.ts +63 -7
  219. package/dist/types.d.ts.map +1 -1
  220. package/docs/README.md +5 -3
  221. package/docs/deployment.md +5 -4
  222. package/docs/guides/mcp-oauth.md +26 -8
  223. package/docs/guides/slack.md +93 -202
  224. package/docs/reference/agent-config.md +2 -2
  225. package/docs/reference/cli.md +38 -16
  226. package/docs/reference/connections.md +6 -3
  227. package/docs/reference/http-api.md +2 -1
  228. package/docs/reference/sessions.md +4 -2
  229. package/docs/reference/tools.md +9 -2
  230. package/docs/templates/agents-md.md +110 -0
  231. package/docs/troubleshooting.md +2 -2
  232. package/package.json +1 -1
  233. package/skills/create-agent/SKILL.md +2 -1
  234. package/skills/mcp-auth/SKILL.md +6 -3
  235. package/skills/setup-slack/SKILL.md +23 -13
  236. package/src/bin/agent-serve.ts +13 -1
  237. package/src/channels/origin/api.ts +24 -5
  238. package/src/channels/origin/binding.ts +55 -26
  239. package/src/channels/origin/index.ts +1 -0
  240. package/src/channels/origin/origin-channel.ts +4 -0
  241. package/src/channels/origin/reactions.ts +82 -0
  242. package/src/channels/origin/types.ts +11 -0
  243. package/src/channels/slack/approvals.ts +105 -0
  244. package/src/channels/slack/cli-project.ts +267 -0
  245. package/src/channels/slack/defaults.ts +109 -46
  246. package/src/channels/slack/init.ts +10 -5
  247. package/src/channels/slack/interactive.ts +35 -9
  248. package/src/channels/slack/manifest.ts +1 -1
  249. package/src/channels/slack/setup.ts +10 -6
  250. package/src/channels/slack/slack-channel.ts +1 -0
  251. package/src/channels/slack/tool-approval-events.ts +93 -0
  252. package/src/evals/run-facts.ts +32 -0
  253. package/src/internal/advertise-tools.ts +31 -10
  254. package/src/internal/approval-gate.ts +20 -29
  255. package/src/internal/chat-client.ts +19 -0
  256. package/src/internal/cli-deploy.ts +79 -9
  257. package/src/internal/cli-mcp-oauth.ts +7 -21
  258. package/src/internal/cli-slack-manual.ts +350 -18
  259. package/src/internal/cli-slack.ts +40 -84
  260. package/src/internal/cursor/account-mcp.ts +21 -0
  261. package/src/internal/cursor-agent-template.ts +1 -0
  262. package/src/internal/deploy-client.ts +43 -0
  263. package/src/internal/env-file.ts +84 -0
  264. package/src/internal/framework-file-storage.ts +7 -1
  265. package/src/internal/hosted-delivery-protocol.ts +7 -0
  266. package/src/internal/hosted-delivery.ts +100 -27
  267. package/src/internal/hosted-execution-diag.ts +207 -0
  268. package/src/internal/http-channel.ts +99 -0
  269. package/src/internal/init-project.ts +2 -0
  270. package/src/internal/logs-client.ts +5 -0
  271. package/src/internal/mcp-endpoint.ts +13 -4
  272. package/src/internal/mcp-host.ts +77 -11
  273. package/src/internal/mcp-oauth-on-demand.ts +352 -0
  274. package/src/internal/mcp-oauth-persist.ts +127 -0
  275. package/src/internal/mcp-oauth.ts +17 -1
  276. package/src/internal/otel/collector.ts +30 -0
  277. package/src/internal/server.ts +59 -9
  278. package/src/internal/session-engine.ts +659 -56
  279. package/src/internal/sessions-client.ts +17 -8
  280. package/src/internal/storage-coordinator.ts +38 -8
  281. package/src/internal/storage-roles.ts +7 -0
  282. package/src/internal/stream-progress.ts +19 -0
  283. package/src/internal/tool-policy.ts +18 -0
  284. package/src/internal/trajectory.ts +10 -0
  285. package/src/internal/turn-outbox.ts +85 -0
  286. package/src/storage-backends/cursor-hosted.ts +13 -1
  287. package/src/storage.ts +12 -0
  288. package/src/types.ts +68 -8
  289. package/templates/agents-md/README.md +39 -0
  290. package/templates/agents-md/agent/agent.ts +8 -0
  291. package/templates/agents-md/agent/channels/github.ts +10 -0
  292. package/templates/agents-md/agent/channels/queue.ts +28 -0
  293. package/templates/agents-md/agent/channels/slack.ts +9 -0
  294. package/templates/agents-md/agent/instructions.md +39 -0
  295. package/templates/agents-md/agent/lib/collect.ts +439 -0
  296. package/templates/agents-md/agent/lib/config.ts +8 -0
  297. package/templates/agents-md/agent/lib/paths.ts +51 -0
  298. package/templates/agents-md/agent/schedules/weekly.ts +17 -0
  299. package/templates/agents-md/agent/tools/apply_agents.ts +226 -0
  300. package/templates/agents-md/evals/agents.eval.ts +135 -0
  301. package/templates/agents-md/evals/evals.config.ts +5 -0
  302. package/templates/agents-md/init.json +38 -0
  303. package/templates/agents-md/package.json +18 -0
  304. package/templates/agents-md/tsconfig.json +12 -0
  305. package/dist/docs/assets/chunks/@localSearchIndexroot.DFoGuETX.js +0 -1
  306. package/dist/docs/assets/guides_slack.md.C32HsdKk.js +0 -38
  307. package/dist/docs/assets/guides_slack.md.C32HsdKk.lean.js +0 -1
  308. package/dist/docs/assets/index.md.CI93Ltlb.js +0 -5
  309. package/dist/docs/assets/reference_sessions.md.D0mIh4KK.js +0 -1
  310. package/dist/docs/assets/troubleshooting.md.Ctv3T8C2.js +0 -1
  311. package/dist/docs/design/agsh.md +0 -406
  312. package/dist/playground/assets/index-B1DDvBeC.css +0 -1
  313. package/docs/design/agsh.md +0 -406
  314. /package/dist/docs/assets/{deployment.md.D9msOFOW.lean.js → deployment.md.MFPKqsqI.lean.js} +0 -0
  315. /package/dist/docs/assets/{index.md.CI93Ltlb.lean.js → index.md.CVeRUOeZ.lean.js} +0 -0
  316. /package/dist/docs/assets/{reference_agent-config.md.Cp_x38Nl.lean.js → reference_agent-config.md.BHeYMgme.lean.js} +0 -0
  317. /package/dist/docs/assets/{reference_connections.md.BH8Oc0D0.lean.js → reference_connections.md.DxldvyIB.lean.js} +0 -0
  318. /package/dist/docs/assets/{reference_http-api.md.D89k1mdm.lean.js → reference_http-api.md.5zOAbV86.lean.js} +0 -0
  319. /package/dist/docs/assets/{reference_sessions.md.D0mIh4KK.lean.js → reference_sessions.md.NxFP_CAC.lean.js} +0 -0
  320. /package/dist/docs/assets/{reference_tools.md.BHeXn2id.lean.js → reference_tools.md.B84gw9Ii.lean.js} +0 -0
  321. /package/dist/docs/assets/{troubleshooting.md.Ctv3T8C2.lean.js → troubleshooting.md.DCiPBhYs.lean.js} +0 -0
@@ -15,6 +15,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
15
15
  step((generator = generator.apply(thisArg, _arguments || [])).next());
16
16
  });
17
17
  };
18
+ import { AsyncLocalStorage } from "node:async_hooks";
18
19
  import { mkdir, rm, writeFile } from "node:fs/promises";
19
20
  import { dirname, join } from "node:path";
20
21
  import { mergeABInstructions, resolveABMaxPlaygroundSessions, resolveABSplit, } from "../ab.js";
@@ -24,11 +25,11 @@ import { SlackAskDedupeStore, SlackNudgeStore, } from "../channels/slack/nudge-s
24
25
  import { resolveDedicatedSlackHostBinding } from "../channels/slack/slack-channel.js";
25
26
  import { AGENT_SERVE_USER_STORE_FILES_SOURCE_ID, agentStoreKeys, } from "../files.js";
26
27
  import { cursorHostedFiles, isCursorHostedFilesAvailable, isCursorUserStoreFilesAvailable, } from "../files-backends/cursor-hosted.js";
27
- import { DEFAULT_MODEL, DIRECT_TOOL_CALL_CHANNEL_ID, isHostOauthConnectionTransport, isSymbolicConnectionTransport, } from "../types.js";
28
+ import { DEFAULT_MODEL, DIRECT_TOOL_CALL_CHANNEL_ID, isCursorAccountTransport, isHostOauthConnectionTransport, isSymbolicConnectionTransport, } from "../types.js";
28
29
  import { ABCollector } from "./ab-collector.js";
29
30
  import { foldABStream } from "./ab-fold.js";
30
31
  import { buildABSnapshot } from "./ab-snapshot.js";
31
- import { resolveAdvertisedTools } from "./advertise-tools.js";
32
+ import { resolveAdvertisedTools, } from "./advertise-tools.js";
32
33
  import { ApprovalGate } from "./approval-gate.js";
33
34
  import { ArtifactsStore } from "./artifacts-store.js";
34
35
  import { SessionAuthError, samePrincipal } from "./auth.js";
@@ -47,7 +48,8 @@ import { createHostPlatforms, createHostSlackClient, } from "./host-platforms.js
47
48
  import { HostedAdmissionLocalSendError, isDuringHostedAdmission, } from "./hosted-admission-context.js";
48
49
  import { formatContinuationToken, newContinuationKey, newSessionId, newToolCallId, parseContinuationToken, } from "./ids.js";
49
50
  import { validateToolInput } from "./json-schema.js";
50
- import { McpHost } from "./mcp-host.js";
51
+ import { isMcpNeedsAuthError, McpHost } from "./mcp-host.js";
52
+ import { McpOauthOnDemandHub } from "./mcp-oauth-on-demand.js";
51
53
  import { shouldStartProcessMemoryTelemetry, startProcessMemoryTelemetry, } from "./process-memory-telemetry.js";
52
54
  import { buildSessionContextBlock, SCHEDULE_CHANNEL_PREFIX, } from "./prompt-context.js";
53
55
  import { assertCloudCanHonorAccountServersFilters } from "./resolved-connections.js";
@@ -56,11 +58,12 @@ import { SessionStore } from "./session-store.js";
56
58
  import { syncSkillsToStore } from "./skills-sync.js";
57
59
  import { steeringEnabled } from "./steering.js";
58
60
  import { StorageCoordinator } from "./storage-coordinator.js";
59
- import { decideToolCall, resolveEffect } from "./tool-policy.js";
61
+ import { decideToolCall, directCallEffect } from "./tool-policy.js";
60
62
  import { normalizeToolResult, toolCallErrorMessage } from "./tool-result.js";
61
63
  import { estimateTurnCostFromApi, roundCostUsd, } from "./turn-cost.js";
62
64
  import { TurnGovernor } from "./turn-governor.js";
63
65
  import { TurnLeaseStore } from "./turn-lease-store.js";
66
+ import { TurnOutbox } from "./turn-outbox.js";
64
67
  import { buildAgentsMdContent, buildCloudMemorySection, ensureOwnGitRoot, isNestedInGitRepo, materializeWorkspace, SERVER_TOOLS_MCP_SERVER_NAME, writeWorkspaceFiles, } from "./workspace.js";
65
68
  export const HTTP_CHANNEL_ID = "http";
66
69
  export { SCHEDULE_CHANNEL_PREFIX } from "./prompt-context.js";
@@ -134,6 +137,19 @@ export class ApprovalInterruptedError extends Error {
134
137
  this.name = "ApprovalInterruptedError";
135
138
  }
136
139
  }
140
+ export class AuthNotFoundError extends Error {
141
+ constructor(sessionId, callId) {
142
+ super(`No pending MCP auth for session ${sessionId} call ${callId}`);
143
+ this.name = "AuthNotFoundError";
144
+ }
145
+ }
146
+ export class AuthInterruptedError extends Error {
147
+ constructor(sessionId, callId) {
148
+ super(`MCP auth for session ${sessionId} call ${callId} is no longer live (host may have restarted while the tool was parked)`);
149
+ this.name = "AuthInterruptedError";
150
+ }
151
+ }
152
+ const mcpAuthAls = new AsyncLocalStorage();
137
153
  export class SessionEngine {
138
154
  constructor(options) {
139
155
  var _a, _b, _c, _d, _e;
@@ -143,6 +159,13 @@ export class SessionEngine {
143
159
  /** Sessions with a turn being admitted (before the run promise exists). */
144
160
  this.pendingTurns = new Set();
145
161
  this.activeTurns = new Map();
162
+ /**
163
+ * Sessions whose workspace a turn in this process has materialized. A
164
+ * session is busy from admit — before the governor slot and the turn's
165
+ * own materialize — so a read running alongside a turn consults this,
166
+ * not busy, to know whether the workspace is ready to be left alone.
167
+ */
168
+ this.materializedWorkspaces = new Set();
146
169
  /** Context used by the cloud turn's HTTP MCP callbacks. */
147
170
  this.activeToolContexts = new Map();
148
171
  /**
@@ -179,6 +202,8 @@ export class SessionEngine {
179
202
  this.dispatchChains = new Map();
180
203
  this.backgroundWork = new Set();
181
204
  this.approvals = new ApprovalGate();
205
+ this.auths = new ApprovalGate();
206
+ this.oauthHub = new McpOauthOnDemandHub();
182
207
  /** Serve-time resolved connections (see {@link setResolvedConnections}). */
183
208
  this.resolvedConnections = [];
184
209
  /** Sessions whose completed turns are fully priced (no further backfill). */
@@ -203,6 +228,7 @@ export class SessionEngine {
203
228
  this.nudgeStore = new SlackNudgeStore(join(options.stateRoot, "slack-nudges"));
204
229
  this.askDedupe = new SlackAskDedupeStore(join(options.stateRoot, "slack-asks"));
205
230
  this.turnLeases = new TurnLeaseStore(join(options.stateRoot, "turn-leases"));
231
+ this.outbox = new TurnOutbox(join(options.stateRoot, "outbox"));
206
232
  this.logs = new EventLogStore((sessionId) => this.sessions.eventFilePath(sessionId), (sessionId, error) => this.logger(`[agent-sdk] failed to persist events for ${sessionId}: ${describeError(error)}`));
207
233
  // Framework session state (records, event chunks, continuations) is
208
234
  // mirrored to whatever this selects; see framework-storage-selection.ts
@@ -284,10 +310,20 @@ export class SessionEngine {
284
310
  setCloudServerToolsConnection(connection) {
285
311
  this.cloudServerToolsConnection = connection;
286
312
  }
313
+ setMcpOauthCallbackUrl(url) {
314
+ this.oauthHub.callbackUrl = url;
315
+ this.mcpHost.setOAuthRedirectUrl(url);
316
+ }
317
+ setCursorAccountAuthProbe(probe) {
318
+ this.probeCursorAccount = probe;
319
+ }
320
+ get oauthCallbackHub() {
321
+ return this.oauthHub;
322
+ }
287
323
  /** Shared host services (MCP / GitHub / Slack / reminders / evals / kv / files / otel / Slack nudges). */
288
324
  get host() {
289
325
  return {
290
- mcp: this.mcpHost,
326
+ mcp: this.wrapMcp(this.mcpHost),
291
327
  github: this.platforms.github,
292
328
  slack: this.platforms.slack,
293
329
  reminders: this.reminderApi,
@@ -584,12 +620,33 @@ export class SessionEngine {
584
620
  const now = new Date().toISOString();
585
621
  const channelState = this.initialChannelState(channelId, sendOptions.state);
586
622
  const purpose = sendOptions.purpose === "eval" ? "eval" : "live";
587
- let record = Object.assign(Object.assign({ sessionId: (_d = sendOptions.hostedSessionId) !== null && _d !== void 0 ? _d : newSessionId(), channelId, mode: (_e = sendOptions.mode) !== null && _e !== void 0 ? _e : "chat", purpose }, (sendOptions.dryRun === true ? { dryRun: true } : {})), { continuationKey: (_f = sendOptions.continuationToken) !== null && _f !== void 0 ? _f : newContinuationKey(), auth, title: sendOptions.title, channelState, workspaceOverride: sendOptions.workspaceDir, pendingWorkspaceFiles: sendOptions.workspaceFiles, pendingImages: sendOptions.images, cloudOverride: sendOptions.cloud, sdkAgentId: sendOptions.sdkAgentId, turnCount: 0, createdAt: now, updatedAt: now });
623
+ let record = {
624
+ sessionId: (_d = sendOptions.hostedSessionId) !== null && _d !== void 0 ? _d : newSessionId(),
625
+ channelId,
626
+ mode: (_e = sendOptions.mode) !== null && _e !== void 0 ? _e : "chat",
627
+ purpose,
628
+ dryRun: sendOptions.dryRun === true ? true : undefined,
629
+ continuationKey: (_f = sendOptions.continuationToken) !== null && _f !== void 0 ? _f : newContinuationKey(),
630
+ auth,
631
+ title: sendOptions.title,
632
+ channelState,
633
+ workspaceOverride: sendOptions.workspaceDir,
634
+ pendingWorkspaceFiles: sendOptions.workspaceFiles,
635
+ pendingImages: sendOptions.images,
636
+ cloudOverride: sendOptions.cloud,
637
+ sdkAgentId: sendOptions.sdkAgentId,
638
+ turnCount: 0,
639
+ createdAt: now,
640
+ updatedAt: now,
641
+ };
588
642
  yield this.sessions.save(record);
589
643
  (_a = this.storage) === null || _a === void 0 ? void 0 : _a.sessionRecord(record);
590
644
  yield this.appendEvent(record.sessionId, {
591
645
  type: "session.started",
592
- data: { channelId },
646
+ data: {
647
+ channelId,
648
+ dryRun: record.dryRun === true ? true : undefined,
649
+ },
593
650
  });
594
651
  record = yield this.enrollABs(record);
595
652
  const since = (yield this.logs.get(record.sessionId)).count;
@@ -791,10 +848,28 @@ export class SessionEngine {
791
848
  const prior = (_a = this.admissionChains.get(sessionId)) !== null && _a !== void 0 ? _a : Promise.resolve();
792
849
  const admitted = prior
793
850
  .catch(() => { })
794
- .then(() => this.admitTurn(sessionId, message, options));
851
+ .then(() => __awaiter(this, void 0, void 0, function* () {
852
+ // Another server holds the turn: hand the message to it and coalesce.
853
+ if (yield this.turnLeases.runningElsewhere(sessionId)) {
854
+ yield this.outbox.post(sessionId, { message, options });
855
+ return true;
856
+ }
857
+ return this.admitTurn(sessionId, message, options);
858
+ }));
795
859
  this.admissionChains.set(sessionId, admitted.catch(() => { }));
796
860
  return admitted;
797
861
  }
862
+ /** Admit what other servers handed to this turn's holder. Never awaited by the lifecycle. */
863
+ drainOutbox(sessionId) {
864
+ void this.outbox
865
+ .takeAll(sessionId)
866
+ .then((items) => {
867
+ for (const item of items) {
868
+ void this.startTurn(sessionId, item.message, item.options).catch(() => { });
869
+ }
870
+ })
871
+ .catch(() => { });
872
+ }
798
873
  enqueueCoalesced(sessionId, item) {
799
874
  var _a;
800
875
  const queue = (_a = this.coalesceQueues.get(sessionId)) !== null && _a !== void 0 ? _a : [];
@@ -1004,6 +1079,8 @@ export class SessionEngine {
1004
1079
  let lifecycle;
1005
1080
  lifecycle = (() => __awaiter(this, void 0, void 0, function* () {
1006
1081
  let outcome;
1082
+ const poll = setInterval(() => this.drainOutbox(sessionId), 1000);
1083
+ poll.unref();
1007
1084
  try {
1008
1085
  outcome = yield this.executeTurn(record, turnId, message, isFirstTurn, abort.signal);
1009
1086
  }
@@ -1013,6 +1090,9 @@ export class SessionEngine {
1013
1090
  errorMessage: describeError(error),
1014
1091
  };
1015
1092
  }
1093
+ finally {
1094
+ clearInterval(poll);
1095
+ }
1016
1096
  this.lastOutcomes.set(sessionId, outcome);
1017
1097
  const wasCurrent = this.activeTurns.get(sessionId) === lifecycle;
1018
1098
  if (wasCurrent) {
@@ -1038,6 +1118,10 @@ export class SessionEngine {
1038
1118
  // The drain may have nested a successor turn, which took its own
1039
1119
  // reference; the lease outlives this release until that one ends too.
1040
1120
  yield lease.release();
1121
+ // Handed off between the last poll and the release: nobody else will.
1122
+ if (!abort.signal.aborted) {
1123
+ this.drainOutbox(sessionId);
1124
+ }
1041
1125
  return outcome;
1042
1126
  }))();
1043
1127
  this.activeTurns.set(sessionId, lifecycle);
@@ -1087,6 +1171,7 @@ export class SessionEngine {
1087
1171
  this.coalesceDrains.has(sessionId) ||
1088
1172
  ((_b = (_a = this.coalesceQueues.get(sessionId)) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) > 0;
1089
1173
  this.clearCoalesced(sessionId);
1174
+ yield this.outbox.clear(sessionId);
1090
1175
  const record = yield this.sessions.get(sessionId);
1091
1176
  const continuationKey = record === null || record === void 0 ? void 0 : record.continuationKey;
1092
1177
  if (continuationKey !== undefined && continuationKey !== null) {
@@ -1275,6 +1360,7 @@ export class SessionEngine {
1275
1360
  seed: isFirstTurn,
1276
1361
  stateRoot: this.stateRoot,
1277
1362
  });
1363
+ this.materializedWorkspaces.add(sessionId);
1278
1364
  // Must precede the carrier: the harness resolves rules, skills,
1279
1365
  // `mcp.json` and hooks from its git root, so a workspace nested in an
1280
1366
  // outer repo would load that repo's hooks instead of the ones written
@@ -1337,12 +1423,15 @@ export class SessionEngine {
1337
1423
  }
1338
1424
  catch (error) {
1339
1425
  this.approvals.cancelSession(sessionId, "deny");
1426
+ this.auths.cancelSession(sessionId, "deny");
1340
1427
  outcome = { status: "error", errorMessage: describeError(error) };
1341
1428
  }
1342
1429
  finally {
1343
1430
  permit.release();
1344
1431
  this.approvals.cancelSession(sessionId, "deny");
1432
+ this.auths.cancelSession(sessionId, "deny");
1345
1433
  yield this.clearPendingApprovals(sessionId);
1434
+ yield this.clearPendingAuths(sessionId);
1346
1435
  // Anything still queued belongs to the next turn's boundary drain, not
1347
1436
  // to this turn's edge detection.
1348
1437
  this.deliveredNudges.delete(sessionId);
@@ -1833,11 +1922,17 @@ export class SessionEngine {
1833
1922
  // named passthroughs resolved fresh on every turn — reminder fires and
1834
1923
  // post-restart follow-ups included — and a listing failure throws,
1835
1924
  // failing the turn closed.
1836
- for (const tool of yield resolveAdvertisedTools({
1925
+ for (const tool of yield mcpAuthAls.run({
1926
+ sessionId: record.sessionId,
1927
+ turnId,
1928
+ callId: newToolCallId("mcp"),
1929
+ toolName: "mcp",
1930
+ emit,
1931
+ }, () => resolveAdvertisedTools({
1837
1932
  agent,
1838
1933
  session: this.sessionInfo(record),
1839
- hostMcp: this.mcpHost,
1840
- })) {
1934
+ hostMcp: this.advertiseMcp(),
1935
+ }))) {
1841
1936
  register(tool);
1842
1937
  }
1843
1938
  return tools;
@@ -1945,7 +2040,13 @@ export class SessionEngine {
1945
2040
  }
1946
2041
  const started = Date.now();
1947
2042
  this.logger(`[session] tool ${args.tool.name} start`);
1948
- const result = yield execute(validation.value, toolCtx);
2043
+ const result = yield mcpAuthAls.run({
2044
+ sessionId: args.record.sessionId,
2045
+ turnId: args.turnId,
2046
+ callId: toolCallId,
2047
+ toolName: args.tool.name,
2048
+ emit: args.emit,
2049
+ }, () => execute(validation.value, toolCtx));
1949
2050
  this.logger(`[session] tool ${args.tool.name} done (${Date.now() - started}ms)`);
1950
2051
  return normalizeToolResult(result);
1951
2052
  }
@@ -1970,7 +2071,7 @@ export class SessionEngine {
1970
2071
  args: args.args,
1971
2072
  requestedAt,
1972
2073
  };
1973
- const resolutionPromise = this.approvals.wait(Object.assign({ sessionId: args.sessionId }, pendingRecord));
2074
+ const resolutionPromise = this.approvals.wait(args.sessionId, pendingRecord);
1974
2075
  yield this.updateRecord(args.sessionId, (current) => { var _a; return (Object.assign(Object.assign({}, current), { pendingApprovals: [
1975
2076
  ...((_a = current.pendingApprovals) !== null && _a !== void 0 ? _a : []).filter((item) => item.callId !== args.callId),
1976
2077
  pendingRecord,
@@ -2056,6 +2157,245 @@ export class SessionEngine {
2056
2157
  yield this.updateRecord(sessionId, (current) => { var _a; return (Object.assign(Object.assign({}, current), { pendingApprovals: ((_a = current.pendingApprovals) !== null && _a !== void 0 ? _a : []).filter((item) => item.callId !== callId) })); });
2057
2158
  });
2058
2159
  }
2160
+ wrapMcp(inner) {
2161
+ return {
2162
+ names: () => inner.names(),
2163
+ listTools: (name) => this.withMcpAuth(name, () => inner.listTools(name)),
2164
+ callTool: (name, tool, args) => this.withMcpAuth(name, () => inner.callTool(name, tool, args)),
2165
+ };
2166
+ }
2167
+ advertiseMcp() {
2168
+ return {
2169
+ listTools: (name) => this.withMcpListAuth(name, () => this.mcpHost.listTools(name)),
2170
+ callTool: (name, tool, args) => this.withMcpAuth(name, () => this.mcpHost.callTool(name, tool, args)),
2171
+ oneOff: (connection) => {
2172
+ const inner = this.mcpHost.oneOff(connection);
2173
+ return {
2174
+ listTools: () => this.withMcpListAuth(connection.name, () => inner.listTools()),
2175
+ callTool: (tool, args) => this.withMcpAuth(connection.name, () => inner.callTool(tool, args)),
2176
+ };
2177
+ },
2178
+ };
2179
+ }
2180
+ withMcpListAuth(connection, run) {
2181
+ const ctx = mcpAuthAls.getStore();
2182
+ if (ctx === undefined) {
2183
+ return this.withMcpAuth(connection, run);
2184
+ }
2185
+ const toolName = `mcp:${connection}`;
2186
+ return mcpAuthAls.run(Object.assign(Object.assign({}, ctx), { callId: newToolCallId(toolName), toolName }), () => this.withMcpAuth(connection, run));
2187
+ }
2188
+ withMcpAuth(connection, run) {
2189
+ return __awaiter(this, void 0, void 0, function* () {
2190
+ try {
2191
+ return yield run();
2192
+ }
2193
+ catch (error) {
2194
+ if (!isMcpNeedsAuthError(error)) {
2195
+ throw error;
2196
+ }
2197
+ return yield this.parkAndRetryMcp(connection, run, error);
2198
+ }
2199
+ });
2200
+ }
2201
+ parkAndRetryMcp(connection, run, error) {
2202
+ return __awaiter(this, void 0, void 0, function* () {
2203
+ const ctx = mcpAuthAls.getStore();
2204
+ if (ctx === undefined) {
2205
+ throw error;
2206
+ }
2207
+ const minted = yield this.mintAuthUrl(connection, ctx.sessionId);
2208
+ if (minted === undefined) {
2209
+ throw error;
2210
+ }
2211
+ if ("authorized" in minted) {
2212
+ yield this.persistHostedMcpOauth(connection);
2213
+ yield this.mcpHost.dropClient(connection);
2214
+ return yield this.withMcpAuth(connection, run);
2215
+ }
2216
+ if (minted.state !== "") {
2217
+ const bind = this.oauthHub.bind(minted.state, ctx.sessionId, ctx.callId);
2218
+ if (bind === "already_done") {
2219
+ yield this.persistHostedMcpOauth(connection);
2220
+ yield this.mcpHost.dropClient(connection);
2221
+ return yield this.withMcpAuth(connection, run);
2222
+ }
2223
+ }
2224
+ const decision = yield this.parkForAuth({
2225
+ sessionId: ctx.sessionId,
2226
+ turnId: ctx.turnId,
2227
+ callId: ctx.callId,
2228
+ toolName: ctx.toolName,
2229
+ connection,
2230
+ authUrl: minted.authUrl,
2231
+ emit: ctx.emit,
2232
+ });
2233
+ if (decision === "deny") {
2234
+ throw new Error(`MCP connection "${connection}" authorization was denied.`);
2235
+ }
2236
+ yield this.persistHostedMcpOauth(connection);
2237
+ yield this.mcpHost.dropClient(connection);
2238
+ return yield this.withMcpAuth(connection, run);
2239
+ });
2240
+ }
2241
+ persistHostedMcpOauth(connection) {
2242
+ return __awaiter(this, void 0, void 0, function* () {
2243
+ const authored = this.authoredConnection(connection);
2244
+ if (authored === undefined ||
2245
+ !isHostOauthConnectionTransport(authored.transport)) {
2246
+ return;
2247
+ }
2248
+ yield this.oauthHub.persistFor(connection, authored.transport.url);
2249
+ });
2250
+ }
2251
+ authoredConnection(name) {
2252
+ var _a;
2253
+ var _b;
2254
+ return ((_b = this.project.agent.connections.find((entry) => entry.name === name)) !== null && _b !== void 0 ? _b : (_a = this.project.agent.hostConnections) === null || _a === void 0 ? void 0 : _a.find((entry) => entry.name === name));
2255
+ }
2256
+ mintAuthUrl(connection, sessionId) {
2257
+ return __awaiter(this, void 0, void 0, function* () {
2258
+ const authored = this.authoredConnection(connection);
2259
+ if (authored === undefined) {
2260
+ return undefined;
2261
+ }
2262
+ if (isCursorAccountTransport(authored.transport)) {
2263
+ const probe = this.probeCursorAccount;
2264
+ if (probe === undefined) {
2265
+ return undefined;
2266
+ }
2267
+ const authUrl = yield probe(connection);
2268
+ if (authUrl === undefined || authUrl === "") {
2269
+ return undefined;
2270
+ }
2271
+ return { authUrl, state: "" };
2272
+ }
2273
+ if (!isHostOauthConnectionTransport(authored.transport)) {
2274
+ return undefined;
2275
+ }
2276
+ return yield this.oauthHub.mint({
2277
+ connection,
2278
+ url: authored.transport.url,
2279
+ type: authored.transport.type === "sse" ? "sse" : "http",
2280
+ headers: authored.transport.headers,
2281
+ sessionId,
2282
+ });
2283
+ });
2284
+ }
2285
+ parkForAuth(args) {
2286
+ return __awaiter(this, void 0, void 0, function* () {
2287
+ const requestedAt = new Date().toISOString();
2288
+ const pendingRecord = {
2289
+ callId: args.callId,
2290
+ turnId: args.turnId,
2291
+ toolName: args.toolName,
2292
+ connection: args.connection,
2293
+ authUrl: args.authUrl,
2294
+ requestedAt,
2295
+ };
2296
+ const resolutionPromise = this.auths.wait(args.sessionId, pendingRecord);
2297
+ yield this.updateRecord(args.sessionId, (current) => { var _a; return (Object.assign(Object.assign({}, current), { pendingAuths: [
2298
+ ...((_a = current.pendingAuths) !== null && _a !== void 0 ? _a : []).filter((item) => item.callId !== args.callId),
2299
+ pendingRecord,
2300
+ ] })); });
2301
+ args.emit({
2302
+ type: "action.auth_requested",
2303
+ data: {
2304
+ callId: args.callId,
2305
+ toolName: args.toolName,
2306
+ connection: args.connection,
2307
+ authUrl: args.authUrl,
2308
+ },
2309
+ });
2310
+ this.logger(`[session] tool ${args.toolName} awaiting MCP auth ${args.connection} (${args.callId})`);
2311
+ const { decision, by } = yield resolutionPromise;
2312
+ yield this.updateRecord(args.sessionId, (current) => { var _a; return (Object.assign(Object.assign({}, current), { pendingAuths: ((_a = current.pendingAuths) !== null && _a !== void 0 ? _a : []).filter((item) => item.callId !== args.callId) })); });
2313
+ args.emit({
2314
+ type: "action.auth_resolved",
2315
+ data: {
2316
+ callId: args.callId,
2317
+ toolName: args.toolName,
2318
+ connection: args.connection,
2319
+ decision,
2320
+ by,
2321
+ },
2322
+ });
2323
+ this.logger(`[session] tool ${args.toolName} MCP auth ${decision} (${args.callId})`);
2324
+ return decision;
2325
+ });
2326
+ }
2327
+ clearPendingAuths(sessionId) {
2328
+ return __awaiter(this, void 0, void 0, function* () {
2329
+ yield this.oauthHub.abortSession(sessionId);
2330
+ const current = yield this.sessions.get(sessionId);
2331
+ if (current === undefined ||
2332
+ current.pendingAuths === undefined ||
2333
+ current.pendingAuths.length === 0) {
2334
+ return;
2335
+ }
2336
+ yield this.updateRecord(sessionId, (record) => (Object.assign(Object.assign({}, record), { pendingAuths: [] })));
2337
+ });
2338
+ }
2339
+ listPendingAuths(sessionId) {
2340
+ return __awaiter(this, void 0, void 0, function* () {
2341
+ var _a;
2342
+ const record = yield this.sessions.get(sessionId);
2343
+ if (record === undefined) {
2344
+ return [];
2345
+ }
2346
+ const live = this.auths.list(sessionId);
2347
+ if (live.length > 0) {
2348
+ return live;
2349
+ }
2350
+ return (_a = record.pendingAuths) !== null && _a !== void 0 ? _a : [];
2351
+ });
2352
+ }
2353
+ resolveAuth(sessionId, callId, decision, auth, options) {
2354
+ return __awaiter(this, void 0, void 0, function* () {
2355
+ var _a, _b;
2356
+ const record = yield this.sessions.get(sessionId);
2357
+ if (record === undefined) {
2358
+ throw new AuthNotFoundError(sessionId, callId);
2359
+ }
2360
+ const ownershipAuth = (options === null || options === void 0 ? void 0 : options.asSessionOwner) === true ? record.auth : auth;
2361
+ if (!samePrincipal(record.auth, ownershipAuth)) {
2362
+ throw new SessionAuthError(sessionId);
2363
+ }
2364
+ const pending = (_b = ((_a = record.pendingAuths) !== null && _a !== void 0 ? _a : []).find((item) => item.callId === callId)) !== null && _b !== void 0 ? _b : this.auths.list(sessionId).find((item) => item.callId === callId);
2365
+ if (pending === undefined) {
2366
+ throw new AuthNotFoundError(sessionId, callId);
2367
+ }
2368
+ const resolvedBy = approvalResolverLabel(auth);
2369
+ if (decision === "deny") {
2370
+ yield this.oauthHub.abortCall(sessionId, callId);
2371
+ }
2372
+ if (!this.auths.resolve(sessionId, callId, decision, resolvedBy)) {
2373
+ yield this.removePendingAuth(sessionId, callId);
2374
+ throw new AuthInterruptedError(sessionId, callId);
2375
+ }
2376
+ return pending;
2377
+ });
2378
+ }
2379
+ removePendingAuth(sessionId, callId) {
2380
+ return __awaiter(this, void 0, void 0, function* () {
2381
+ yield this.updateRecord(sessionId, (current) => { var _a; return (Object.assign(Object.assign({}, current), { pendingAuths: ((_a = current.pendingAuths) !== null && _a !== void 0 ? _a : []).filter((item) => item.callId !== callId) })); });
2382
+ });
2383
+ }
2384
+ runWithMcpPark(sessionId, toolName, fn) {
2385
+ return __awaiter(this, void 0, void 0, function* () {
2386
+ const active = this.activeToolContexts.get(sessionId);
2387
+ if (active === undefined) {
2388
+ return fn();
2389
+ }
2390
+ return mcpAuthAls.run({
2391
+ sessionId,
2392
+ turnId: active.turnId,
2393
+ callId: newToolCallId(toolName),
2394
+ toolName,
2395
+ emit: active.emit,
2396
+ }, fn);
2397
+ });
2398
+ }
2059
2399
  // ==========================================================================
2060
2400
  // Direct tool calls (deterministic invocation, no model turn)
2061
2401
  // ==========================================================================
@@ -2070,8 +2410,10 @@ export class SessionEngine {
2070
2410
  * and materialized workspace) and the call is recorded on the session's
2071
2411
  * event stream as `actions.requested` / `action.result` under a
2072
2412
  * per-call `turnId` (the call id), so it shows up in trajectories.
2073
- * Rejected with {@link SessionBusyError} while a model turn is running.
2074
- * Without a session, the tool runs against an **ephemeral** scratch
2413
+ * While a model turn is running, a read-effect call runs alongside it
2414
+ * (the turn's workspace is read as-is) and a write-effect call is
2415
+ * rejected with {@link SessionBusyError}. Without a session, the tool
2416
+ * runs against an **ephemeral** scratch
2075
2417
  * workspace under `<stateRoot>/tool-calls/<callId>` (materialized with
2076
2418
  * seed files, removed after the call returns).
2077
2419
  *
@@ -2111,26 +2453,22 @@ export class SessionEngine {
2111
2453
  throw new InvalidContinuationTokenError(`expected "<channelId>:<key>" (as /v1/sessions lists it), got "${options.continuationToken}"`);
2112
2454
  }
2113
2455
  // A token that maps to a live (or restorable) session takes the
2114
- // session path: same owner check, same turn serialization, same
2115
- // event recording as an explicit sessionId. Skipping those just
2116
- // because the caller addressed the session by token would fail open.
2456
+ // session path: same owner check, same busy semantics, same event
2457
+ // recording as an explicit sessionId. Skipping those just because
2458
+ // the caller addressed the session by token would fail open.
2117
2459
  record = (_b = (yield this.sessions.findByContinuation(continuation.channelId, continuation.key))) !== null && _b !== void 0 ? _b : (yield this.restoreSessionByContinuation(continuation.channelId, continuation.key));
2118
2460
  if (record !== undefined && !samePrincipal(record.auth, auth)) {
2119
2461
  throw new SessionAuthError(record.sessionId);
2120
2462
  }
2121
2463
  }
2122
2464
  if (record !== undefined) {
2123
- // Direct calls respect the same serialization as follow-up sends: a
2124
- // running model turn owns the session workspace and event stream.
2125
- if (this.activeTurns.has(record.sessionId) ||
2126
- this.pendingTurns.has(record.sessionId)) {
2127
- throw new SessionBusyError(record.sessionId);
2128
- }
2129
2465
  return {
2130
2466
  record,
2131
2467
  session: this.sessionInfo(record),
2132
2468
  workspaceDir: this.harnessCwd(record),
2133
2469
  scratchDir: undefined,
2470
+ busy: this.activeTurns.has(record.sessionId) ||
2471
+ this.pendingTurns.has(record.sessionId),
2134
2472
  };
2135
2473
  }
2136
2474
  // No session behind the token (or no token at all): a synthetic
@@ -2150,6 +2488,7 @@ export class SessionEngine {
2150
2488
  },
2151
2489
  workspaceDir: scratchDir,
2152
2490
  scratchDir,
2491
+ busy: false,
2153
2492
  };
2154
2493
  });
2155
2494
  }
@@ -2182,8 +2521,32 @@ export class SessionEngine {
2182
2521
  };
2183
2522
  });
2184
2523
  }
2524
+ /**
2525
+ * One catalog tool by name. Authored tools are local — their schema does
2526
+ * not depend on session identity or connection listings — so a detail
2527
+ * fetch for `ls` must not wait on every advertised MCP server.
2528
+ */
2529
+ describeCatalogTool(toolName_1) {
2530
+ return __awaiter(this, arguments, void 0, function* (toolName, options = {}) {
2531
+ const authored = this.project.agent.tools.find((tool) => tool.name === toolName);
2532
+ if (authored !== undefined) {
2533
+ return { tool: authored };
2534
+ }
2535
+ const catalog = yield this.listCatalogTools(options);
2536
+ const tool = catalog.tools.find((entry) => entry.name === toolName);
2537
+ if (tool === undefined) {
2538
+ return {
2539
+ tool: undefined,
2540
+ availableTools: catalog.tools.map((entry) => entry.name),
2541
+ connectionErrors: catalog.connectionErrors,
2542
+ };
2543
+ }
2544
+ return { tool };
2545
+ });
2546
+ }
2185
2547
  callTool(toolName_1) {
2186
2548
  return __awaiter(this, arguments, void 0, function* (toolName, input = {}, options = {}) {
2549
+ var _a;
2187
2550
  const callId = newToolCallId(toolName);
2188
2551
  // Identity resolves before the tool lookup: advertised connection tools
2189
2552
  // only exist relative to a session (per-session auth), so finding them
@@ -2203,7 +2566,7 @@ export class SessionEngine {
2203
2566
  const advertised = yield resolveAdvertisedTools({
2204
2567
  agent: this.project.agent,
2205
2568
  session,
2206
- hostMcp: this.mcpHost,
2569
+ hostMcp: this.advertiseMcp(),
2207
2570
  });
2208
2571
  tool = advertised.find((t) => t.name === toolName);
2209
2572
  }
@@ -2220,6 +2583,17 @@ export class SessionEngine {
2220
2583
  if (!validation.ok) {
2221
2584
  throw new ToolInputError({ toolName, detail: validation.message });
2222
2585
  }
2586
+ // A running model turn owns the session workspace: a direct write would
2587
+ // race the harness's own edits, so it waits (409). A read runs alongside
2588
+ // the turn — the operator inspecting a live session is the point — and
2589
+ // its events stay separable by their per-call turnId. The effect can
2590
+ // depend on the input, hence deciding after validation; an MCP tool
2591
+ // nothing classifies is a write here, fail-closed.
2592
+ if (identity.busy &&
2593
+ record !== undefined &&
2594
+ directCallEffect(tool, validation.value) !== "read") {
2595
+ throw new SessionBusyError(record.sessionId);
2596
+ }
2223
2597
  let workspaceUnavailable = false;
2224
2598
  try {
2225
2599
  yield mkdir(workspaceDir, { recursive: true });
@@ -2239,28 +2613,50 @@ export class SessionEngine {
2239
2613
  // to write) authored tool may rely on ctx.workspaceDir being the
2240
2614
  // real session workspace, so silently relocating it is not safe —
2241
2615
  // fail typed instead.
2616
+ //
2617
+ // A shared `local.workspaceDir` (change-monitor's phantom /repo) is
2618
+ // that path by design. Relocating it to scratch would flag a false
2619
+ // session-state warning; keep the borrowed cwd and skip the flag.
2242
2620
  const relocatable = tool.advertised === true ||
2243
- resolveEffect(tool.definition.effect, validation.value) === "read";
2621
+ directCallEffect(tool, validation.value) === "read";
2244
2622
  if (!relocatable) {
2245
2623
  throw new WorkspaceUnavailableError(toolName, error);
2246
2624
  }
2247
- this.logger(`[session] tool ${toolName} workspace unavailable (direct ${callId}); using scratch: ${error instanceof Error ? error.message : String(error)}`);
2248
- scratchDir = join(this.stateRoot, "tool-calls", callId);
2249
- workspaceDir = scratchDir;
2250
- workspaceUnavailable = true;
2251
- yield mkdir(workspaceDir, { recursive: true });
2625
+ if (((_a = this.project.agent.local) === null || _a === void 0 ? void 0 : _a.workspaceDir) !== undefined) {
2626
+ this.logger(`[session] tool ${toolName} harness cwd unavailable (direct ${callId}); keeping borrowed workspace`);
2627
+ }
2628
+ else {
2629
+ this.logger(`[session] tool ${toolName} workspace unavailable (direct ${callId}); using scratch: ${error instanceof Error ? error.message : String(error)}`);
2630
+ scratchDir = join(this.stateRoot, "tool-calls", callId);
2631
+ workspaceDir = scratchDir;
2632
+ workspaceUnavailable = true;
2633
+ yield mkdir(workspaceDir, { recursive: true });
2634
+ }
2252
2635
  }
2253
2636
  // Match what a model turn would see: session workspaces get the same
2254
2637
  // scaffold as executeTurn (seeds only before the first turn); scratch
2255
- // workspaces are materialized fresh.
2256
- yield materializeWorkspace({
2257
- agent: this.project.agent,
2258
- workspaceDir: record === undefined || workspaceUnavailable
2259
- ? workspaceDir
2260
- : this.scaffoldDir(record),
2261
- seed: record === undefined || workspaceUnavailable || record.turnCount === 0,
2262
- stateRoot: this.stateRoot,
2263
- });
2638
+ // workspaces are materialized fresh. A read running alongside a turn
2639
+ // that has materialized its workspace leaves it alone — re-seeding
2640
+ // would restore files the agent deleted mid-turn. A session is busy
2641
+ // from admit, though, so a read that lands while the first turn is
2642
+ // still queued or scaffolding materializes the workspace itself, seeds
2643
+ // included (turnCount is already 1 for that pending first turn).
2644
+ const turnMaterialized = record !== undefined &&
2645
+ identity.busy &&
2646
+ this.materializedWorkspaces.has(record.sessionId);
2647
+ if (record === undefined || workspaceUnavailable || !turnMaterialized) {
2648
+ yield materializeWorkspace({
2649
+ agent: this.project.agent,
2650
+ workspaceDir: record === undefined || workspaceUnavailable
2651
+ ? workspaceDir
2652
+ : this.scaffoldDir(record),
2653
+ seed: record === undefined ||
2654
+ workspaceUnavailable ||
2655
+ record.turnCount === 0 ||
2656
+ (identity.busy && record.turnCount === 1),
2657
+ stateRoot: this.stateRoot,
2658
+ });
2659
+ }
2264
2660
  // A per-call turnId keeps these events visible in trajectories (which
2265
2661
  // drop turn-less action events) without attributing them to a model turn.
2266
2662
  if (record !== undefined) {
@@ -2303,7 +2699,7 @@ export class SessionEngine {
2303
2699
  isError = false;
2304
2700
  }
2305
2701
  else {
2306
- result = normalizeToolResult(yield execute(validation.value, {
2702
+ const run = () => execute(validation.value, {
2307
2703
  toolCallId: callId,
2308
2704
  session,
2309
2705
  workspaceDir,
@@ -2321,7 +2717,16 @@ export class SessionEngine {
2321
2717
  : session.auth })),
2322
2718
  getSession: (channelId, sessionId) => this.getChannelSession(channelId, sessionId),
2323
2719
  emit,
2324
- }));
2720
+ });
2721
+ result = normalizeToolResult(record !== undefined && emit !== undefined
2722
+ ? yield mcpAuthAls.run({
2723
+ sessionId: record.sessionId,
2724
+ turnId: callId,
2725
+ callId,
2726
+ toolName,
2727
+ emit,
2728
+ }, run)
2729
+ : yield run());
2325
2730
  isError = isErrorEnvelope(result);
2326
2731
  }
2327
2732
  }
@@ -2688,6 +3093,64 @@ export class SessionEngine {
2688
3093
  };
2689
3094
  });
2690
3095
  }
3096
+ /**
3097
+ * The single newest session visible to `caller` (`GET /v1/sessions?latest=1`).
3098
+ *
3099
+ * Short-circuits the full listing: the local store already holds every
3100
+ * session this host created plus the startup bulk restore, so the common
3101
+ * case answers from memory without a sink round trip. Only when the local
3102
+ * store has nothing for the caller (fresh host, `restore: "off"`) does it
3103
+ * ask the sink — for a bounded newest-written window when the backend
3104
+ * can order by write time, else the full list. A session written by
3105
+ * another replica moments ago can lose to a local one; callers that need
3106
+ * the exact newest row should use the full listing.
3107
+ */
3108
+ latestSessionWithSource(caller, options) {
3109
+ return __awaiter(this, void 0, void 0, function* () {
3110
+ var _a;
3111
+ var _b;
3112
+ const includeAll = (_b = options === null || options === void 0 ? void 0 : options.includeAll) !== null && _b !== void 0 ? _b : false;
3113
+ const newestVisible = (records) => {
3114
+ let newest;
3115
+ for (const record of records) {
3116
+ if (!(includeAll || samePrincipal(record.auth, caller))) {
3117
+ continue;
3118
+ }
3119
+ if (newest === undefined ||
3120
+ record.updatedAt.localeCompare(newest.updatedAt) > 0) {
3121
+ newest = record;
3122
+ }
3123
+ }
3124
+ return newest;
3125
+ };
3126
+ const local = newestVisible(yield this.sessions.list());
3127
+ if (local !== undefined) {
3128
+ return {
3129
+ sessions: this.summarizeSessionRecords([local], caller, includeAll),
3130
+ source: "local",
3131
+ };
3132
+ }
3133
+ if (((_a = this.storage) === null || _a === void 0 ? void 0 : _a.isCursorHosted) === true) {
3134
+ try {
3135
+ const window = yield this.storage.listNewestSessionsFromSink(LATEST_SESSION_SINK_WINDOW);
3136
+ if (window !== undefined) {
3137
+ const stored = newestVisible(window);
3138
+ if (stored !== undefined) {
3139
+ return {
3140
+ sessions: this.summarizeSessionRecords([stored], caller, includeAll),
3141
+ source: "cursor-hosted",
3142
+ };
3143
+ }
3144
+ }
3145
+ }
3146
+ catch (error) {
3147
+ this.logger(`[agent-sdk] cursor-hosted newest-session lookup failed; falling back to the full list: ${describeError(error)}`);
3148
+ }
3149
+ }
3150
+ const listed = yield this.listSessionsWithSource(caller, options);
3151
+ return { sessions: listed.sessions.slice(0, 1), source: listed.source };
3152
+ });
3153
+ }
2691
3154
  /**
2692
3155
  * Durable sink rows plus local records (local wins on the same id when
2693
3156
  * newer — covers debounce windows that have not flushed yet). Newest
@@ -2734,6 +3197,7 @@ export class SessionEngine {
2734
3197
  : undefined,
2735
3198
  createdAt: record.createdAt,
2736
3199
  updatedAt: record.updatedAt,
3200
+ dryRun: record.dryRun === true ? true : undefined,
2737
3201
  continuationToken: record.httpContinuationKey !== undefined
2738
3202
  ? formatContinuationToken(HTTP_CHANNEL_ID, record.httpContinuationKey)
2739
3203
  : record.continuationKey === null
@@ -3006,7 +3470,7 @@ export class SessionEngine {
3006
3470
  this.coalesceDrains.has(sessionId));
3007
3471
  }
3008
3472
  sessionInfo(record) {
3009
- return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ id: record.sessionId, channelId: record.channelId, mode: record.mode, purpose: record.purpose === "eval" ? "eval" : "live" }, (record.dryRun === true ? { dryRun: true } : {})), { auth: record.auth, continuationKey: record.continuationKey }), (record.abs === undefined ? {} : { abs: record.abs })), (record.title === undefined ? {} : { title: record.title })), (record.sdkAgentId === undefined
3473
+ return Object.assign(Object.assign(Object.assign({ id: record.sessionId, channelId: record.channelId, mode: record.mode, purpose: record.purpose === "eval" ? "eval" : "live", dryRun: record.dryRun === true ? true : undefined, auth: record.auth, continuationKey: record.continuationKey }, (record.abs === undefined ? {} : { abs: record.abs })), (record.title === undefined ? {} : { title: record.title })), (record.sdkAgentId === undefined
3010
3474
  ? {}
3011
3475
  : { sdkAgentId: record.sdkAgentId }));
3012
3476
  }
@@ -3291,6 +3755,8 @@ export class SessionEngine {
3291
3755
  var _a, _b, _c, _d, _e;
3292
3756
  (_a = this.stopMemoryTelemetry) === null || _a === void 0 ? void 0 : _a.call(this);
3293
3757
  this.approvals.cancelAll("deny");
3758
+ this.auths.cancelAll("deny");
3759
+ yield this.oauthHub.close();
3294
3760
  yield Promise.allSettled([...this.activeTurns.values()]);
3295
3761
  yield Promise.allSettled([...this.backgroundWork]);
3296
3762
  yield Promise.allSettled([...this.dispatchChains.values()]);
@@ -3307,6 +3773,12 @@ export class SessionEngine {
3307
3773
  }
3308
3774
  /** Parallel event-stream loads during the startup bulk restore. */
3309
3775
  const RESTORE_FETCH_CONCURRENCY = 8;
3776
+ /**
3777
+ * Newest-written sink rows fetched for `GET /v1/sessions?latest=1` when the
3778
+ * local store has nothing for the caller. Wide enough that the caller's own
3779
+ * session is normally inside it even when other principals are busier.
3780
+ */
3781
+ const LATEST_SESSION_SINK_WINDOW = 50;
3310
3782
  /** NDJSON payload for a restored event stream; undefined when empty. */
3311
3783
  function mergePendingImages(current, incoming) {
3312
3784
  if (incoming === undefined) {