@cursor/july 0.1.107 → 0.1.108

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 (302) hide show
  1. package/README.md +10 -5
  2. package/dist/bin/agent-serve.js +30 -8
  3. package/dist/channels/github/github-channel.js +8 -5
  4. package/dist/channels/github/types.d.ts +5 -1
  5. package/dist/channels/github/types.d.ts.map +1 -1
  6. package/dist/channels/origin/origin-channel.d.ts.map +1 -1
  7. package/dist/channels/origin/origin-channel.js +27 -13
  8. package/dist/channels/origin/types.d.ts +5 -1
  9. package/dist/channels/origin/types.d.ts.map +1 -1
  10. package/dist/channels.d.ts +45 -0
  11. package/dist/channels.d.ts.map +1 -1
  12. package/dist/channels.js +106 -7
  13. package/dist/connections.d.ts +2 -1
  14. package/dist/connections.d.ts.map +1 -1
  15. package/dist/connections.js +2 -1
  16. package/dist/docs/404.html +2 -2
  17. package/dist/docs/ab.html +4 -4
  18. package/dist/docs/assets/{app.CtuckIiL.js → app.BASKM3M4.js} +1 -1
  19. package/dist/docs/assets/chunks/@localSearchIndexroot.BHZYsNVi.js +1 -0
  20. package/dist/docs/assets/chunks/{VPLocalSearchBox.Bkkhnu8K.js → VPLocalSearchBox.BOKxlYGP.js} +1 -1
  21. package/dist/docs/assets/chunks/{theme.DhpIe0Pa.js → theme.DbDZW-zb.js} +2 -2
  22. package/dist/docs/assets/{deployment.md.MFPKqsqI.js → deployment.md.JenwuCCB.js} +4 -4
  23. package/dist/docs/assets/{deployment.md.MFPKqsqI.lean.js → deployment.md.JenwuCCB.lean.js} +1 -1
  24. package/dist/docs/assets/{evals.md.CbMoebP1.js → evals.md.C1ekS3k2.js} +1 -1
  25. package/dist/docs/assets/guides_cloud-agents.md.BsloyHdY.js +9 -0
  26. package/dist/docs/assets/{guides_cloud-agents.md.DS8RIjwx.lean.js → guides_cloud-agents.md.BsloyHdY.lean.js} +1 -1
  27. package/dist/docs/assets/{guides_github.md.c0gdGJ-D.js → guides_github.md.BH33UEBJ.js} +1 -1
  28. package/dist/docs/assets/{guides_grokbot-agents.md.DVqdhBKb.js → guides_grokbot-agents.md.DyV-WArv.js} +1 -1
  29. package/dist/docs/assets/guides_improve.md.BGVk32bK.js +14 -0
  30. package/dist/docs/assets/guides_improve.md.BGVk32bK.lean.js +1 -0
  31. package/dist/docs/assets/{guides_slack.md.9oHPye9o.js → guides_slack.md.D4RVMM4G.js} +3 -3
  32. package/dist/docs/assets/{guides_slack.md.9oHPye9o.lean.js → guides_slack.md.D4RVMM4G.lean.js} +1 -1
  33. package/dist/docs/assets/index.md.BjH1w2ZW.js +5 -0
  34. package/dist/docs/assets/{reference_agent-config.md.CHNpiyp4.js → reference_agent-config.md.CvoL6pof.js} +1 -1
  35. package/dist/docs/assets/{reference_cli.md.Dm67hd2D.js → reference_cli.md.Ca26u0Es.js} +6 -6
  36. package/dist/docs/assets/{reference_cli.md.Dm67hd2D.lean.js → reference_cli.md.Ca26u0Es.lean.js} +1 -1
  37. package/dist/docs/assets/{reference_connections.md.Di6jJAXF.js → reference_connections.md.BojkC6c5.js} +1 -1
  38. package/dist/docs/assets/{reference_extensions.md.CGmMLblt.js → reference_extensions.md.CPWt00ds.js} +1 -1
  39. package/dist/docs/assets/{scaffolding-agents.md.em43xlY1.js → scaffolding-agents.md.Kctn3OVb.js} +1 -1
  40. package/dist/docs/assets/{skills_create-agent.md.BVoWPcan.js → skills_create-agent.md.Q3h6Je-e.js} +1 -1
  41. package/dist/docs/assets/{skills_debug.md.CDbPhHfg.js → skills_debug.md.CVjCXMFF.js} +1 -1
  42. package/dist/docs/assets/{skills_debug.md.CDbPhHfg.lean.js → skills_debug.md.CVjCXMFF.lean.js} +1 -1
  43. package/dist/docs/assets/{skills_evals.md.723kpUmA.js → skills_evals.md.BhovOvrl.js} +1 -1
  44. package/dist/docs/assets/skills_framework-map.md.D-tFZhFS.js +1 -0
  45. package/dist/docs/assets/{skills_framework-map.md.haibFyoB.lean.js → skills_framework-map.md.D-tFZhFS.lean.js} +1 -1
  46. package/dist/docs/assets/{skills_github.md.D0JahM8c.js → skills_github.md.hgFX_oKY.js} +1 -1
  47. package/dist/docs/assets/troubleshooting.md.CYEAO9bM.js +1 -0
  48. package/dist/docs/building-with-agents.html +4 -4
  49. package/dist/docs/concepts.html +4 -4
  50. package/dist/docs/deployment.html +7 -7
  51. package/dist/docs/deployment.md +7 -2
  52. package/dist/docs/evals.html +6 -6
  53. package/dist/docs/evals.md +3 -2
  54. package/dist/docs/guides/agent-to-agent.html +4 -4
  55. package/dist/docs/guides/cloud-agents.html +6 -6
  56. package/dist/docs/guides/cloud-agents.md +4 -3
  57. package/dist/docs/guides/convert-automation.html +4 -4
  58. package/dist/docs/guides/github.html +5 -5
  59. package/dist/docs/guides/github.md +2 -1
  60. package/dist/docs/guides/grokbot-agents.html +6 -6
  61. package/dist/docs/guides/grokbot-agents.md +2 -2
  62. package/dist/docs/guides/human-in-the-loop.html +4 -4
  63. package/dist/docs/guides/improve.html +40 -0
  64. package/dist/docs/guides/improve.md +91 -0
  65. package/dist/docs/guides/mcp-oauth.html +4 -4
  66. package/dist/docs/guides/opentelemetry.html +5 -5
  67. package/dist/docs/guides/slack.html +6 -6
  68. package/dist/docs/guides/slack.md +2 -1
  69. package/dist/docs/guides/webhooks.html +4 -4
  70. package/dist/docs/hashmap.json +1 -1
  71. package/dist/docs/hillclimbing.html +4 -4
  72. package/dist/docs/index.html +6 -6
  73. package/dist/docs/index.md +4 -0
  74. package/dist/docs/llms-full.txt +158 -33
  75. package/dist/docs/llms.txt +1 -0
  76. package/dist/docs/quickstart.html +4 -4
  77. package/dist/docs/reference/agent-config.html +6 -6
  78. package/dist/docs/reference/agent-config.md +5 -2
  79. package/dist/docs/reference/artifacts.html +4 -4
  80. package/dist/docs/reference/channels.html +4 -4
  81. package/dist/docs/reference/cli.html +10 -10
  82. package/dist/docs/reference/cli.md +18 -11
  83. package/dist/docs/reference/connections.html +6 -6
  84. package/dist/docs/reference/connections.md +2 -1
  85. package/dist/docs/reference/extensions.html +6 -6
  86. package/dist/docs/reference/extensions.md +2 -0
  87. package/dist/docs/reference/hooks.html +4 -4
  88. package/dist/docs/reference/http-api.html +4 -4
  89. package/dist/docs/reference/instructions.html +4 -4
  90. package/dist/docs/reference/playground.html +4 -4
  91. package/dist/docs/reference/project-layout.html +4 -4
  92. package/dist/docs/reference/prompt.html +4 -4
  93. package/dist/docs/reference/result.html +4 -4
  94. package/dist/docs/reference/schedules.html +4 -4
  95. package/dist/docs/reference/sessions.html +4 -4
  96. package/dist/docs/reference/skills.html +4 -4
  97. package/dist/docs/reference/subagents.html +4 -4
  98. package/dist/docs/reference/tools.html +4 -4
  99. package/dist/docs/scaffolding-agents.html +5 -5
  100. package/dist/docs/scaffolding-agents.md +2 -1
  101. package/dist/docs/skills/ab.html +4 -4
  102. package/dist/docs/skills/create-agent.html +6 -6
  103. package/dist/docs/skills/create-agent.md +1 -1
  104. package/dist/docs/skills/debug.html +5 -5
  105. package/dist/docs/skills/debug.md +1 -1
  106. package/dist/docs/skills/evals.html +6 -6
  107. package/dist/docs/skills/evals.md +1 -1
  108. package/dist/docs/skills/framework-map.html +5 -5
  109. package/dist/docs/skills/framework-map.md +4 -2
  110. package/dist/docs/skills/github.html +6 -6
  111. package/dist/docs/skills/github.md +1 -1
  112. package/dist/docs/skills/hillclimb.html +4 -4
  113. package/dist/docs/skills/index.html +4 -4
  114. package/dist/docs/skills/mcp-auth.html +4 -4
  115. package/dist/docs/skills/otel.html +4 -4
  116. package/dist/docs/skills/setup-slack.html +4 -4
  117. package/dist/docs/storage.html +4 -4
  118. package/dist/docs/templates/agentic-owners.html +4 -4
  119. package/dist/docs/templates/agents-md.html +4 -4
  120. package/dist/docs/templates/code-wiki.html +4 -4
  121. package/dist/docs/templates/demo.html +4 -4
  122. package/dist/docs/templates/grokbot-agents.html +4 -4
  123. package/dist/docs/templates/pr-autofixer.html +4 -4
  124. package/dist/docs/templates/security-help.html +4 -4
  125. package/dist/docs/templates/security-reviewer.html +4 -4
  126. package/dist/docs/templates/triage.html +4 -4
  127. package/dist/docs/troubleshooting.html +5 -5
  128. package/dist/docs/troubleshooting.md +1 -1
  129. package/dist/extensions/improve/extension.d.ts +46 -0
  130. package/dist/extensions/improve/extension.d.ts.map +1 -0
  131. package/dist/extensions/improve/extension.js +41 -0
  132. package/dist/extensions/improve/skills/yourself.d.ts +4 -0
  133. package/dist/extensions/improve/skills/yourself.d.ts.map +1 -0
  134. package/dist/extensions/improve/skills/yourself.js +43 -0
  135. package/dist/internal/builtin-tools/artifacts.d.ts.map +1 -1
  136. package/dist/internal/builtin-tools/artifacts.js +2 -3
  137. package/dist/internal/channel-route-dispatch.d.ts +13 -0
  138. package/dist/internal/channel-route-dispatch.d.ts.map +1 -0
  139. package/dist/internal/channel-route-dispatch.js +62 -0
  140. package/dist/internal/channel-state.d.ts +17 -0
  141. package/dist/internal/channel-state.d.ts.map +1 -0
  142. package/dist/internal/channel-state.js +78 -0
  143. package/dist/internal/cli-ax.d.ts +7 -0
  144. package/dist/internal/cli-ax.d.ts.map +1 -1
  145. package/dist/internal/cli-ax.js +99 -1
  146. package/dist/internal/cli-cursor.d.ts.map +1 -1
  147. package/dist/internal/cli-cursor.js +2 -0
  148. package/dist/internal/cli-deploy.d.ts.map +1 -1
  149. package/dist/internal/cli-deploy.js +38 -14
  150. package/dist/internal/cli-mcp-oauth.d.ts.map +1 -1
  151. package/dist/internal/cli-mcp-oauth.js +15 -0
  152. package/dist/internal/cli-mcp.d.ts.map +1 -1
  153. package/dist/internal/cli-mcp.js +12 -0
  154. package/dist/internal/cli-slack.d.ts.map +1 -1
  155. package/dist/internal/cli-slack.js +8 -2
  156. package/dist/internal/cursor/credentials.d.ts +8 -2
  157. package/dist/internal/cursor/credentials.d.ts.map +1 -1
  158. package/dist/internal/cursor/credentials.js +27 -5
  159. package/dist/internal/deploy-client.d.ts +27 -0
  160. package/dist/internal/deploy-client.d.ts.map +1 -1
  161. package/dist/internal/deploy-client.js +32 -0
  162. package/dist/internal/deploy-manifest.d.ts +23 -0
  163. package/dist/internal/deploy-manifest.d.ts.map +1 -1
  164. package/dist/internal/deploy-manifest.js +84 -1
  165. package/dist/internal/describe-error.d.ts +1 -0
  166. package/dist/internal/describe-error.d.ts.map +1 -1
  167. package/dist/internal/describe-error.js +25 -0
  168. package/dist/internal/guest-network.d.ts +4 -10
  169. package/dist/internal/guest-network.d.ts.map +1 -1
  170. package/dist/internal/guest-network.js +42 -26
  171. package/dist/internal/hosted-admission-adapter.d.ts +2 -0
  172. package/dist/internal/hosted-admission-adapter.d.ts.map +1 -1
  173. package/dist/internal/hosted-catch-protocol.d.ts +51 -0
  174. package/dist/internal/hosted-catch-protocol.d.ts.map +1 -0
  175. package/dist/internal/hosted-catch-protocol.js +103 -0
  176. package/dist/internal/hosted-catch.d.ts +40 -0
  177. package/dist/internal/hosted-catch.d.ts.map +1 -0
  178. package/dist/internal/hosted-catch.js +149 -0
  179. package/dist/internal/hosted-delivery-protocol.d.ts +41 -0
  180. package/dist/internal/hosted-delivery-protocol.d.ts.map +1 -1
  181. package/dist/internal/hosted-delivery-protocol.js +249 -0
  182. package/dist/internal/hosted-delivery.d.ts +10 -1
  183. package/dist/internal/hosted-delivery.d.ts.map +1 -1
  184. package/dist/internal/hosted-delivery.js +100 -22
  185. package/dist/internal/hosted-execution-diag.d.ts +4 -1
  186. package/dist/internal/hosted-execution-diag.d.ts.map +1 -1
  187. package/dist/internal/hosted-execution-diag.js +13 -4
  188. package/dist/internal/hosted-execution-flush.d.ts +3 -0
  189. package/dist/internal/hosted-execution-flush.d.ts.map +1 -1
  190. package/dist/internal/hosted-execution-flush.js +2 -4
  191. package/dist/internal/http-control-plane-session.d.ts +30 -0
  192. package/dist/internal/http-control-plane-session.d.ts.map +1 -0
  193. package/dist/internal/http-control-plane-session.js +83 -0
  194. package/dist/internal/legacy-command-guard.d.ts +22 -0
  195. package/dist/internal/legacy-command-guard.d.ts.map +1 -0
  196. package/dist/internal/legacy-command-guard.js +51 -0
  197. package/dist/internal/platform-timers.d.ts +7 -0
  198. package/dist/internal/platform-timers.d.ts.map +1 -1
  199. package/dist/internal/platform-timers.js +139 -0
  200. package/dist/internal/reminder-control-plane-protocol.d.ts +2 -0
  201. package/dist/internal/reminder-control-plane-protocol.d.ts.map +1 -1
  202. package/dist/internal/reminder-control-plane-protocol.js +9 -2
  203. package/dist/internal/reminder-runner.d.ts +8 -0
  204. package/dist/internal/reminder-runner.d.ts.map +1 -1
  205. package/dist/internal/reminder-runner.js +36 -9
  206. package/dist/internal/resolve-prod-target.d.ts +2 -0
  207. package/dist/internal/resolve-prod-target.d.ts.map +1 -1
  208. package/dist/internal/resolve-prod-target.js +13 -0
  209. package/dist/internal/sdk-runner.d.ts.map +1 -1
  210. package/dist/internal/sdk-runner.js +4 -3
  211. package/dist/internal/server.d.ts.map +1 -1
  212. package/dist/internal/server.js +57 -56
  213. package/dist/internal/session-engine.d.ts +2 -6
  214. package/dist/internal/session-engine.d.ts.map +1 -1
  215. package/dist/internal/session-engine.js +95 -67
  216. package/dist/playground/assets/{index-Bq2HpEQB.js → index-BMDqAeXu.js} +28 -28
  217. package/dist/playground/assets/index-LUgJoWdL.css +1 -0
  218. package/dist/playground/index.html +2 -2
  219. package/dist/storage-backends/cursor-hosted.d.ts +6 -0
  220. package/dist/storage-backends/cursor-hosted.d.ts.map +1 -1
  221. package/dist/storage-backends/cursor-hosted.js +29 -1
  222. package/dist/types.d.ts +33 -5
  223. package/dist/types.d.ts.map +1 -1
  224. package/docs/README.md +4 -0
  225. package/docs/deployment.md +7 -2
  226. package/docs/evals.md +3 -2
  227. package/docs/guides/cloud-agents.md +4 -3
  228. package/docs/guides/github.md +2 -1
  229. package/docs/guides/grokbot-agents.md +2 -2
  230. package/docs/guides/improve.md +96 -0
  231. package/docs/guides/slack.md +2 -1
  232. package/docs/reference/agent-config.md +5 -2
  233. package/docs/reference/cli.md +18 -11
  234. package/docs/reference/connections.md +2 -1
  235. package/docs/reference/extensions.md +2 -0
  236. package/docs/scaffolding-agents.md +2 -1
  237. package/docs/troubleshooting.md +1 -1
  238. package/package.json +8 -1
  239. package/skills/create-agent/SKILL.md +1 -1
  240. package/skills/debug/SKILL.md +1 -1
  241. package/skills/evals/SKILL.md +1 -1
  242. package/skills/framework-map/SKILL.md +4 -2
  243. package/skills/github/SKILL.md +1 -1
  244. package/src/bin/agent-serve.ts +27 -2
  245. package/src/channels/github/github-channel.ts +8 -8
  246. package/src/channels/github/types.ts +5 -0
  247. package/src/channels/origin/origin-channel.ts +29 -13
  248. package/src/channels/origin/types.ts +5 -0
  249. package/src/channels.ts +157 -10
  250. package/src/connections.ts +2 -1
  251. package/src/extensions/improve/extension.ts +70 -0
  252. package/src/extensions/improve/skills/yourself.ts +50 -0
  253. package/src/internal/builtin-tools/artifacts.ts +2 -3
  254. package/src/internal/channel-route-dispatch.ts +66 -0
  255. package/src/internal/channel-state.ts +96 -0
  256. package/src/internal/cli-ax.ts +110 -1
  257. package/src/internal/cli-cursor.ts +4 -1
  258. package/src/internal/cli-deploy.ts +50 -10
  259. package/src/internal/cli-mcp-oauth.ts +18 -0
  260. package/src/internal/cli-mcp.ts +11 -0
  261. package/src/internal/cli-slack.ts +15 -2
  262. package/src/internal/cursor/credentials.ts +35 -7
  263. package/src/internal/deploy-client.ts +54 -0
  264. package/src/internal/deploy-manifest.ts +115 -1
  265. package/src/internal/describe-error.ts +28 -0
  266. package/src/internal/guest-network.ts +43 -29
  267. package/src/internal/hosted-admission-adapter.ts +2 -0
  268. package/src/internal/hosted-catch-protocol.ts +130 -0
  269. package/src/internal/hosted-catch.ts +192 -0
  270. package/src/internal/hosted-delivery-protocol.ts +387 -0
  271. package/src/internal/hosted-delivery.ts +155 -22
  272. package/src/internal/hosted-execution-diag.ts +21 -3
  273. package/src/internal/hosted-execution-flush.ts +6 -3
  274. package/src/internal/http-control-plane-session.ts +104 -0
  275. package/src/internal/legacy-command-guard.ts +59 -0
  276. package/src/internal/platform-timers.ts +191 -0
  277. package/src/internal/reminder-control-plane-protocol.ts +15 -2
  278. package/src/internal/reminder-runner.ts +60 -9
  279. package/src/internal/resolve-prod-target.ts +15 -0
  280. package/src/internal/sdk-runner.ts +2 -1
  281. package/src/internal/server.ts +89 -79
  282. package/src/internal/session-engine.ts +141 -83
  283. package/src/storage-backends/cursor-hosted.ts +40 -1
  284. package/src/types.ts +39 -6
  285. package/dist/docs/assets/chunks/@localSearchIndexroot.DXXZxiMv.js +0 -1
  286. package/dist/docs/assets/guides_cloud-agents.md.DS8RIjwx.js +0 -9
  287. package/dist/docs/assets/index.md.BW_6tOgR.js +0 -5
  288. package/dist/docs/assets/skills_framework-map.md.haibFyoB.js +0 -1
  289. package/dist/docs/assets/troubleshooting.md.Cus_YZga.js +0 -1
  290. package/dist/playground/assets/index-CZKKNlmb.css +0 -1
  291. /package/dist/docs/assets/{evals.md.CbMoebP1.lean.js → evals.md.C1ekS3k2.lean.js} +0 -0
  292. /package/dist/docs/assets/{guides_github.md.c0gdGJ-D.lean.js → guides_github.md.BH33UEBJ.lean.js} +0 -0
  293. /package/dist/docs/assets/{guides_grokbot-agents.md.DVqdhBKb.lean.js → guides_grokbot-agents.md.DyV-WArv.lean.js} +0 -0
  294. /package/dist/docs/assets/{index.md.BW_6tOgR.lean.js → index.md.BjH1w2ZW.lean.js} +0 -0
  295. /package/dist/docs/assets/{reference_agent-config.md.CHNpiyp4.lean.js → reference_agent-config.md.CvoL6pof.lean.js} +0 -0
  296. /package/dist/docs/assets/{reference_connections.md.Di6jJAXF.lean.js → reference_connections.md.BojkC6c5.lean.js} +0 -0
  297. /package/dist/docs/assets/{reference_extensions.md.CGmMLblt.lean.js → reference_extensions.md.CPWt00ds.lean.js} +0 -0
  298. /package/dist/docs/assets/{scaffolding-agents.md.em43xlY1.lean.js → scaffolding-agents.md.Kctn3OVb.lean.js} +0 -0
  299. /package/dist/docs/assets/{skills_create-agent.md.BVoWPcan.lean.js → skills_create-agent.md.Q3h6Je-e.lean.js} +0 -0
  300. /package/dist/docs/assets/{skills_evals.md.723kpUmA.lean.js → skills_evals.md.BhovOvrl.lean.js} +0 -0
  301. /package/dist/docs/assets/{skills_github.md.D0JahM8c.lean.js → skills_github.md.hgFX_oKY.lean.js} +0 -0
  302. /package/dist/docs/assets/{troubleshooting.md.Cus_YZga.lean.js → troubleshooting.md.CYEAO9bM.lean.js} +0 -0
@@ -8,6 +8,14 @@
8
8
  * executes fires.
9
9
  */
10
10
 
11
+ import {
12
+ REMINDER_CP_PROTOCOL_VERSION,
13
+ type ReminderControlPlane,
14
+ type ReminderCreateIntent,
15
+ type ReminderExecutionOutcome,
16
+ type ReminderFireClaim,
17
+ type ReminderOutcomeApplyResult,
18
+ } from "./reminder-control-plane-protocol.js";
11
19
  import { isReminderRecord, type ReminderRecord } from "./reminder-store.js";
12
20
 
13
21
  const DEFAULT_CURSOR_EXTERNAL_API_URL = "https://api.cursor.com";
@@ -171,3 +179,186 @@ export function createCursorHostedPlatformTimers(
171
179
  },
172
180
  };
173
181
  }
182
+
183
+ /** v2 reminder CP is factory ECS (`/internal/agentsdk/*`), not timed-work. */
184
+ const AGENTSDK_REMINDERS_PREFIX = "/internal/agentsdk";
185
+
186
+ /**
187
+ * Bounded retries for idempotent CP outcome writes after the guest handler
188
+ * has already run. Delivery is at-least-once: a transient apply failure may
189
+ * re-enter the handler on the next Temporal admission. Do not claim
190
+ * exactly-once handler execution.
191
+ */
192
+ const APPLY_REMINDER_OUTCOME_MAX_ATTEMPTS = 3;
193
+ const APPLY_REMINDER_OUTCOME_RETRY_DELAY_MS = 50;
194
+
195
+ async function sleepMs(ms: number): Promise<void> {
196
+ await new Promise((resolve) => setTimeout(resolve, ms));
197
+ }
198
+
199
+ async function withBoundedApplyRetries<T>(run: () => Promise<T>): Promise<T> {
200
+ let lastError: unknown;
201
+ for (
202
+ let attempt = 1;
203
+ attempt <= APPLY_REMINDER_OUTCOME_MAX_ATTEMPTS;
204
+ attempt++
205
+ ) {
206
+ try {
207
+ return await run();
208
+ } catch (error) {
209
+ lastError = error;
210
+ if (attempt >= APPLY_REMINDER_OUTCOME_MAX_ATTEMPTS) {
211
+ break;
212
+ }
213
+ await sleepMs(APPLY_REMINDER_OUTCOME_RETRY_DELAY_MS * attempt);
214
+ }
215
+ }
216
+ throw lastError;
217
+ }
218
+
219
+ async function timedWorkJsonFetch(
220
+ options: CursorHostedPlatformTimersOptions,
221
+ method: "GET" | "POST" | "PUT" | "DELETE",
222
+ path: string,
223
+ body?: unknown
224
+ ): Promise<Response> {
225
+ const fetchImpl = options.fetch ?? globalThis.fetch;
226
+ if (typeof fetchImpl !== "function") {
227
+ throw new Error("reminder control plane requires fetch");
228
+ }
229
+ return fetchImpl(`${resolveBaseUrl(options)}${path}`, {
230
+ method,
231
+ headers: {
232
+ Authorization: `Bearer ${resolveApiKey(options)}`,
233
+ ...(body === undefined ? {} : { "Content-Type": "application/json" }),
234
+ },
235
+ ...(body === undefined ? {} : { body: JSON.stringify(body) }),
236
+ });
237
+ }
238
+
239
+ /**
240
+ * Cursor-hosted HTTP ReminderControlPlane for architecture v2.
241
+ * Speaks factory `/internal/agentsdk/reminders` (ELB → factory ECS).
242
+ * CP owns ids and time. v1 PUT/DELETE reminders stay on timed-work.
243
+ */
244
+ export function createCursorHostedReminderControlPlane(
245
+ options: CursorHostedPlatformTimersOptions = {}
246
+ ): ReminderControlPlane {
247
+ return {
248
+ protocolVersion: REMINDER_CP_PROTOCOL_VERSION,
249
+ async createReminder(intent: ReminderCreateIntent) {
250
+ const response = await timedWorkJsonFetch(
251
+ options,
252
+ "POST",
253
+ `${AGENTSDK_REMINDERS_PREFIX}/reminders`,
254
+ intent
255
+ );
256
+ if (!response.ok) {
257
+ const text = await response.text();
258
+ throw new Error(
259
+ `reminder CP create failed: HTTP ${response.status} ${text}`
260
+ );
261
+ }
262
+ const parsed: unknown = await response.json();
263
+ if (!isReminderRecord(parsed)) {
264
+ throw new Error("reminder CP create returned invalid record");
265
+ }
266
+ return parsed;
267
+ },
268
+ async getReminder(id) {
269
+ const response = await timedWorkJsonFetch(
270
+ options,
271
+ "GET",
272
+ `${AGENTSDK_REMINDERS_PREFIX}/reminders/${encodeURIComponent(id)}`
273
+ );
274
+ if (response.status === 404) {
275
+ return undefined;
276
+ }
277
+ if (!response.ok) {
278
+ throw new Error(`reminder CP get failed: HTTP ${response.status}`);
279
+ }
280
+ const parsed: unknown = await response.json();
281
+ return isReminderRecord(parsed) ? parsed : undefined;
282
+ },
283
+ async listReminders() {
284
+ const response = await timedWorkJsonFetch(
285
+ options,
286
+ "GET",
287
+ `${AGENTSDK_REMINDERS_PREFIX}/reminders`
288
+ );
289
+ if (!response.ok) {
290
+ throw new Error(`reminder CP list failed: HTTP ${response.status}`);
291
+ }
292
+ const parsed: unknown = await response.json();
293
+ if (
294
+ parsed === null ||
295
+ typeof parsed !== "object" ||
296
+ !("reminders" in parsed) ||
297
+ !Array.isArray((parsed as { reminders: unknown }).reminders)
298
+ ) {
299
+ return [];
300
+ }
301
+ return (parsed as { reminders: unknown[] }).reminders.filter(
302
+ isReminderRecord
303
+ );
304
+ },
305
+ async cancelReminder(id, reason) {
306
+ const response = await timedWorkJsonFetch(
307
+ options,
308
+ "POST",
309
+ `${AGENTSDK_REMINDERS_PREFIX}/reminders/${encodeURIComponent(id)}/cancel`,
310
+ reason === undefined ? {} : { reason }
311
+ );
312
+ if (response.status === 404) {
313
+ return undefined;
314
+ }
315
+ if (!response.ok) {
316
+ throw new Error(`reminder CP cancel failed: HTTP ${response.status}`);
317
+ }
318
+ const parsed: unknown = await response.json();
319
+ return isReminderRecord(parsed) ? parsed : undefined;
320
+ },
321
+ async rekeyReminders(channelId, oldToken, newToken) {
322
+ const response = await timedWorkJsonFetch(
323
+ options,
324
+ "POST",
325
+ `${AGENTSDK_REMINDERS_PREFIX}/reminders/rekey`,
326
+ { channelId, oldToken, newToken }
327
+ );
328
+ if (!response.ok) {
329
+ throw new Error(`reminder CP rekey failed: HTTP ${response.status}`);
330
+ }
331
+ const parsed: unknown = await response.json();
332
+ if (
333
+ parsed === null ||
334
+ typeof parsed !== "object" ||
335
+ typeof (parsed as { count?: unknown }).count !== "number"
336
+ ) {
337
+ throw new Error("reminder CP rekey returned invalid body");
338
+ }
339
+ return (parsed as { count: number }).count;
340
+ },
341
+ async applyReminderOutcome(
342
+ claim: ReminderFireClaim,
343
+ outcome: ReminderExecutionOutcome
344
+ ): Promise<ReminderOutcomeApplyResult> {
345
+ // Handler side effects may already have run; retry only the idempotent
346
+ // CP write. Exhausted failures stay thrown so Temporal can re-admit
347
+ // (at-least-once — not exactly-once).
348
+ return await withBoundedApplyRetries(async () => {
349
+ const response = await timedWorkJsonFetch(
350
+ options,
351
+ "POST",
352
+ `${AGENTSDK_REMINDERS_PREFIX}/reminders/outcome`,
353
+ { claim, outcome }
354
+ );
355
+ if (!response.ok) {
356
+ throw new Error(
357
+ `reminder CP outcome apply failed: HTTP ${response.status}`
358
+ );
359
+ }
360
+ return (await response.json()) as ReminderOutcomeApplyResult;
361
+ });
362
+ },
363
+ };
364
+ }
@@ -27,6 +27,8 @@ import type { ReminderRecord } from "./reminder-store.js";
27
27
 
28
28
  /** Wire / persistence version for this control-plane protocol. */
29
29
  export const REMINDER_CP_PROTOCOL_VERSION = 1 as const;
30
+ /** Each recurring/retry fire can spend a model turn and create hosted work. */
31
+ export const REMINDER_CP_MIN_RECURRING_INTERVAL_MS = 60_000 as const;
30
32
 
31
33
  export type ReminderCpProtocolVersion = typeof REMINDER_CP_PROTOCOL_VERSION;
32
34
 
@@ -293,6 +295,14 @@ export function intentToNewRecord(
293
295
  scheduleSpecToCreateFields(intent.schedule),
294
296
  nowMs
295
297
  );
298
+ if (
299
+ resolved.kind === "every" &&
300
+ (resolved.intervalMs ?? 0) < REMINDER_CP_MIN_RECURRING_INTERVAL_MS
301
+ ) {
302
+ throw new ReminderValidationError(
303
+ `every must be >= 1m (got ${intent.schedule.kind === "every" ? intent.schedule.every : ""})`
304
+ );
305
+ }
296
306
  const nowIso = new Date(nowMs).toISOString();
297
307
  return {
298
308
  version: 1,
@@ -429,11 +439,14 @@ export function applyOutcomeToRecord(
429
439
  const nowIso = new Date(nowMs).toISOString();
430
440
 
431
441
  if (outcome.kind === "retry") {
432
- const delayMs = outcome.delayMs ?? 0;
442
+ const delayMs = Math.max(
443
+ REMINDER_CP_MIN_RECURRING_INTERVAL_MS,
444
+ outcome.delayMs ?? REMINDER_CP_MIN_RECURRING_INTERVAL_MS
445
+ );
433
446
  const updated: ReminderRecord = {
434
447
  ...record,
435
448
  status: "armed",
436
- nextFireAtMs: nowMs + Math.max(0, delayMs),
449
+ nextFireAtMs: nowMs + delayMs,
437
450
  // Same fireCount; bump generation so a second apply of the old claim is stale.
438
451
  generation: recordGeneration(record) + 1,
439
452
  updatedAt: nowIso,
@@ -18,6 +18,11 @@ import type {
18
18
  ReminderStatus,
19
19
  ReminderUntilContext,
20
20
  } from "../types.js";
21
+ import {
22
+ channelContinuationIdentity,
23
+ httpContinuationIdentity,
24
+ } from "./continuation-channel.js";
25
+ import { continuationSource } from "./continuation-identity.js";
21
26
  import { NAME_PATTERN } from "./discovery/names.js";
22
27
  import { parseDuration } from "./duration.js";
23
28
  import type { PlatformTimers } from "./platform-timers.js";
@@ -39,7 +44,12 @@ import {
39
44
  } from "./reminder-schedule.js";
40
45
  import type { ReminderBusyPolicy, ReminderRecord } from "./reminder-store.js";
41
46
  import { APP_AUTH } from "./schedule-runner.js";
42
- import { SessionBusyError, type SessionEngine } from "./session-engine.js";
47
+ import {
48
+ type EngineSendOptions,
49
+ HTTP_CHANNEL_ID,
50
+ SessionBusyError,
51
+ type SessionEngine,
52
+ } from "./session-engine.js";
43
53
 
44
54
  export { ReminderValidationError } from "./reminder-schedule.js";
45
55
 
@@ -1040,16 +1050,56 @@ export class ReminderRunner {
1040
1050
  return this.sendFollowup(record, buildFirePrompt(record), auth);
1041
1051
  }
1042
1052
 
1053
+ /**
1054
+ * Join the reminder's existing session. Control-plane (`/v2`) channels
1055
+ * refuse authored `continuationToken`; pass the stored key as
1056
+ * {@link EngineSendOptions.mailboxContinuation} so the engine reuses the
1057
+ * CP-selected identity without channel admission.
1058
+ */
1059
+ private followupSendOptions(
1060
+ record: ReminderRecord,
1061
+ auth: AuthContext | null
1062
+ ): EngineSendOptions {
1063
+ if (this.channelUsesControlPlaneContinuation(record.channelId)) {
1064
+ return {
1065
+ auth,
1066
+ mailboxContinuation: { key: record.continuationToken },
1067
+ };
1068
+ }
1069
+ return {
1070
+ auth,
1071
+ continuationToken: record.continuationToken,
1072
+ };
1073
+ }
1074
+
1075
+ private channelUsesControlPlaneContinuation(channelId: string): boolean {
1076
+ const project = this.engine.project;
1077
+ // Test doubles may omit `project`; keep the legacy continuationToken path.
1078
+ if (project === undefined) {
1079
+ return false;
1080
+ }
1081
+ const identity =
1082
+ channelId === HTTP_CHANNEL_ID
1083
+ ? httpContinuationIdentity(project.httpChannel)
1084
+ : channelContinuationIdentity(
1085
+ project.channels.find((channel) => channel.id === channelId)
1086
+ ?.definition
1087
+ );
1088
+ return continuationSource(identity) === "control-plane";
1089
+ }
1090
+
1043
1091
  private async sendFollowup(
1044
1092
  record: ReminderRecord,
1045
1093
  message: string,
1046
1094
  auth: AuthContext | null
1047
1095
  ): Promise<ReminderFireResult> {
1096
+ const sendOptions = this.followupSendOptions(record, auth);
1048
1097
  try {
1049
- const session = await this.engine.send(record.channelId, message, {
1050
- auth,
1051
- continuationToken: record.continuationToken,
1052
- });
1098
+ const session = await this.engine.send(
1099
+ record.channelId,
1100
+ message,
1101
+ sendOptions
1102
+ );
1053
1103
  this.engine.trackBackground(this.engine.waitForCompletion(session.id));
1054
1104
  return { action: "delivered" };
1055
1105
  } catch (error) {
@@ -1059,10 +1109,11 @@ export class ReminderRunner {
1059
1109
  }
1060
1110
  await this.whenSessionIdle(record.channelId, record.continuationToken);
1061
1111
  try {
1062
- const session = await this.engine.send(record.channelId, message, {
1063
- auth,
1064
- continuationToken: record.continuationToken,
1065
- });
1112
+ const session = await this.engine.send(
1113
+ record.channelId,
1114
+ message,
1115
+ sendOptions
1116
+ );
1066
1117
  this.engine.trackBackground(
1067
1118
  this.engine.waitForCompletion(session.id)
1068
1119
  );
@@ -16,6 +16,7 @@ import {
16
16
  validateDeploymentSlug,
17
17
  } from "./deploy-client.js";
18
18
  import { CLI_COMMAND_NAME as CLI } from "./distribution.js";
19
+ import { rejectV2ForLegacyCommand } from "./legacy-command-guard.js";
19
20
  import { sleep as defaultSleep } from "./sleep.js";
20
21
  import { stderrPalette } from "./terminal-style.js";
21
22
 
@@ -38,6 +39,8 @@ export interface ResolveProdTargetOptions {
38
39
  apiKey?: string;
39
40
  fetchImpl?: typeof fetch;
40
41
  err?: (text: string) => void;
42
+ /** Skip when the caller already ran {@link rejectV2ForLegacyCommand}. */
43
+ skipLegacyArchitectureCheck?: boolean;
41
44
  /** Test seam for reachability-retry backoff. */
42
45
  sleep?: (ms: number) => Promise<void>;
43
46
  }
@@ -95,6 +98,13 @@ export async function resolveProdTarget(
95
98
  `Could not resolve Cursor credentials/team for the hosted deployment (see above).`
96
99
  );
97
100
  }
101
+ if (options.skipLegacyArchitectureCheck !== true) {
102
+ await rejectV2ForLegacyCommand({
103
+ client: auth.client,
104
+ teamId: auth.teamId,
105
+ slug,
106
+ });
107
+ }
98
108
 
99
109
  // A hibernated engine can briefly omit engineAccess while getPod fails
100
110
  // during wake. Retry a few times before surfacing "not reachable yet".
@@ -198,6 +208,11 @@ export async function resolveProdAliasPlaygroundUrl(
198
208
  `Could not resolve Cursor credentials/team for the hosted deployment (see above).`
199
209
  );
200
210
  }
211
+ await rejectV2ForLegacyCommand({
212
+ client: auth.client,
213
+ teamId: auth.teamId,
214
+ slug,
215
+ });
201
216
 
202
217
  let detail: DeploymentDetail;
203
218
  try {
@@ -518,7 +518,7 @@ export class CursorSdkRunner implements AgentRunner {
518
518
  const resolved = resolveApiKeySync({ apiKey: this.options.apiKey });
519
519
  if (resolved === undefined) {
520
520
  throw new Error(
521
- `The Cursor SDK runner requires a Cursor credential: run \`${CLI} login\`, set CURSOR_API_KEY or CURSOR_API_KEY_FILE, or pass serve({ apiKey }).`
521
+ `The Cursor SDK runner requires a Cursor credential: run \`${CLI} login\`, set CURSOR_API_KEY, CURSOR_SERVICE_ACCOUNT_KEY, or CURSOR_API_KEY_FILE, or pass serve({ apiKey }).`
522
522
  );
523
523
  }
524
524
  return resolved.apiKey;
@@ -805,6 +805,7 @@ function toTurnOutcome(result: RunResult): TurnOutcome {
805
805
  ...(result.status === "error"
806
806
  ? { errorMessage: runErrorMessage(result) }
807
807
  : {}),
808
+ errorCode: result.status === "error" ? result.error?.code : undefined,
808
809
  ...(usage === undefined ? {} : { usage }),
809
810
  };
810
811
  }
@@ -66,6 +66,7 @@ import type {
66
66
  ReminderInfo,
67
67
  ServeOptions,
68
68
  } from "../types.js";
69
+ import { invokeChannelRoute } from "./channel-route-dispatch.js";
69
70
  import { selectConversationMirror } from "./conversation-mirror.js";
70
71
  import {
71
72
  type CursorAccountConnectionRef,
@@ -129,6 +130,7 @@ import {
129
130
  } from "./handleAgentServeTrigger.js";
130
131
  import { createHostPlatforms, warmupHostGitHubAuth } from "./host-platforms.js";
131
132
  import { registerHostedDeliveryRoute } from "./hosted-delivery.js";
133
+ import type { HostedReminderFireDescriptorPayload } from "./hosted-delivery-protocol.js";
132
134
  import {
133
135
  HOSTED_DELIVERY_HEADER,
134
136
  HOSTED_GUEST_RETRY_TERMINAL,
@@ -143,7 +145,6 @@ import {
143
145
  readJsonObject,
144
146
  requireSessionOwner,
145
147
  } from "./http-channel.js";
146
- import { validateToolInput } from "./json-schema.js";
147
148
  import { DEFAULT_SERVE_PORT, listenHttpServer } from "./listen-port.js";
148
149
  import {
149
150
  createLocalControlPlane,
@@ -176,7 +177,10 @@ import {
176
177
  PLATFORM_SCHEDULE_CATALOG_FILENAME,
177
178
  syncHostedPlatformSchedules,
178
179
  } from "./platform-schedule-sync.js";
179
- import { createCursorHostedPlatformTimers } from "./platform-timers.js";
180
+ import {
181
+ createCursorHostedPlatformTimers,
182
+ createCursorHostedReminderControlPlane,
183
+ } from "./platform-timers.js";
180
184
  import {
181
185
  type AgentIndexEntry,
182
186
  renderAgentsIndexHtml,
@@ -193,6 +197,11 @@ import {
193
197
  collectMemoryHealthFields,
194
198
  sumEventLogStats,
195
199
  } from "./process-memory-telemetry.js";
200
+ import {
201
+ claimFromDescriptor,
202
+ type ReminderControlPlane,
203
+ type ReminderFireDescriptor,
204
+ } from "./reminder-control-plane-protocol.js";
196
205
  import {
197
206
  ReminderRunner,
198
207
  ReminderValidationError,
@@ -210,6 +219,7 @@ import { RuntimeDispatchingRunner } from "./runtime-dispatch-runner.js";
210
219
  import { ScheduleRunner, UnknownScheduleError } from "./schedule-runner.js";
211
220
  import { type AgentRunner, CursorSdkRunner } from "./sdk-runner.js";
212
221
  import {
222
+ HTTP_CHANNEL_ID,
213
223
  InvalidContinuationTokenError,
214
224
  SessionEngine,
215
225
  ToolInputError,
@@ -252,6 +262,11 @@ interface MountedRuntime {
252
262
  scheduleRunner: ScheduleRunner;
253
263
  reminderRunner: ReminderRunner;
254
264
  controlPlane?: LocalControlPlane;
265
+ /**
266
+ * Hosted (or local) reminder CP client for architecture v2 descriptor
267
+ * execution. Undefined when the project is not on the CP protocol.
268
+ */
269
+ reminderControlPlane?: ReminderControlPlane;
255
270
  /** Playground eval batches (`/v1/dev/evals`); loopbackUrl set after listen. */
256
271
  evalRuns: EvalRunStore;
257
272
  }
@@ -325,9 +340,12 @@ export async function startServer(
325
340
  noControlPlane?: boolean;
326
341
  /** `--cursor-scm-proxy`: every cursorAccount SCM pack routes via the backend forwarder. */
327
342
  cursorScmProxy?: boolean;
343
+ /** Deprecated alias (`--cursor-github-proxy`); same effect as cursorScmProxy. */
344
+ cursorGithubProxy?: boolean;
328
345
  };
329
346
  const noControlPlane = hosted.noControlPlane === true;
330
- const scmProxyRequested = hosted.cursorScmProxy === true;
347
+ const scmProxyRequested =
348
+ hosted.cursorScmProxy === true || hosted.cursorGithubProxy === true;
331
349
  const runSchedules = options.schedules ?? !dev;
332
350
  const runReminders = options.reminders ?? !dev;
333
351
 
@@ -371,13 +389,15 @@ export async function startServer(
371
389
  // One resolution for every Cursor credential consumer (SDK runner, account
372
390
  // MCP bridge, SCM event relay, GitHub credentials): explicit option →
373
391
  // CURSOR_API_KEY → CURSOR_API_KEY_FILE (or the hosted default path) →
374
- // stored `agent-sdk login`. SDK / GitHub / account MCP re-read later;
375
- // Slack and deployment-event relays capture the key at start.
392
+ // CURSOR_SERVICE_ACCOUNT_KEY → stored `agent-sdk login`. SDK / GitHub /
393
+ // account MCP re-read later; Slack and deployment-event relays capture
394
+ // the key at start.
376
395
  const accountKey = await resolveApiKey(
377
396
  options.apiKey === undefined ? {} : { apiKey: options.apiKey }
378
397
  );
379
398
  // First present source wins (we define the order): option → env → file →
380
- // stored login. Re-read so bind can drop env or file onto a listening process.
399
+ // service account → stored login. Re-read so bind can drop env or file
400
+ // onto a listening process.
381
401
  const liveCursorApiKey = (): string | undefined =>
382
402
  resolveApiKeySync(
383
403
  options.apiKey === undefined ? {} : { apiKey: options.apiKey }
@@ -850,7 +870,7 @@ export async function startServer(
850
870
  const scheduleRunner = new ScheduleRunner(engine, project, logger);
851
871
  let reminderRunner!: ReminderRunner;
852
872
  const architecture = project.agent.architecture;
853
- const useReminderCpProtocol = !noControlPlane && architecture === "v2";
873
+ const useReminderCpProtocol = architecture === "v2";
854
874
  const localControlPlane = noControlPlane
855
875
  ? undefined
856
876
  : await createLocalControlPlane({
@@ -873,6 +893,13 @@ export async function startServer(
873
893
  const timerClient = noControlPlane
874
894
  ? createCursorHostedPlatformTimers()
875
895
  : localControlPlane;
896
+ const hostedReminderControlPlane =
897
+ noControlPlane && useReminderCpProtocol
898
+ ? createCursorHostedReminderControlPlane()
899
+ : undefined;
900
+ const reminderControlPlane = useReminderCpProtocol
901
+ ? (localControlPlane ?? hostedReminderControlPlane)
902
+ : undefined;
876
903
  reminderRunner = new ReminderRunner(engine, logger, timerClient, {
877
904
  namedHandlers: project.reminders ?? [],
878
905
  // Architecture v2 rejects anonymous run/until everywhere (local + hosted).
@@ -881,7 +908,7 @@ export async function startServer(
881
908
  architecture,
882
909
  }),
883
910
  architecture,
884
- controlPlane: useReminderCpProtocol ? localControlPlane : undefined,
911
+ controlPlane: reminderControlPlane,
885
912
  });
886
913
  const evalRuns = new EvalRunStore(
887
914
  project.rootDir,
@@ -920,6 +947,7 @@ export async function startServer(
920
947
  ...(localControlPlane === undefined
921
948
  ? {}
922
949
  : { controlPlane: localControlPlane }),
950
+ ...(reminderControlPlane === undefined ? {} : { reminderControlPlane }),
923
951
  };
924
952
  runtimes.push(runtime);
925
953
  const accountRefs = accountRefsBySlug.get(slug);
@@ -1479,7 +1507,14 @@ function mountAgentRoutes(
1479
1507
  evictSdkSession?: (sessionId: string) => void;
1480
1508
  }
1481
1509
  ): void {
1482
- const { slug, project, engine, scheduleRunner, reminderRunner } = runtime;
1510
+ const {
1511
+ slug,
1512
+ project,
1513
+ engine,
1514
+ scheduleRunner,
1515
+ reminderRunner,
1516
+ reminderControlPlane,
1517
+ } = runtime;
1483
1518
  const {
1484
1519
  dev,
1485
1520
  playground,
@@ -1538,7 +1573,12 @@ function mountAgentRoutes(
1538
1573
  registerHostedDeliveryRoute({
1539
1574
  router,
1540
1575
  base,
1541
- channels: project.channels,
1576
+ // The built-in http channel is not in `project.channels`; the control
1577
+ // plane's session start (`agentsdk.session`) is delivered to it.
1578
+ channels: [
1579
+ ...project.channels,
1580
+ { id: HTTP_CHANNEL_ID, definition: project.httpChannel },
1581
+ ],
1542
1582
  buildHandlerArgs: (channelId, hostedSessionId) =>
1543
1583
  buildChannelHandlerArgs({
1544
1584
  runtime,
@@ -1547,6 +1587,33 @@ function mountAgentRoutes(
1547
1587
  }),
1548
1588
  fireReminder: (reminderId, fireCount) =>
1549
1589
  reminderRunner.dispatch(reminderId, { expectedFireCount: fireCount }),
1590
+ executeReminderDescriptor: async (descriptor) => {
1591
+ if (reminderControlPlane === undefined) {
1592
+ throw new Error("hosted reminder CP client is not configured");
1593
+ }
1594
+ const fireDescriptor = descriptor as unknown as ReminderFireDescriptor;
1595
+ const outcome = await reminderRunner.executeFire(fireDescriptor);
1596
+ // applyReminderOutcome is idempotent and internally retries transient
1597
+ // CP failures. Handler execution is at-least-once across Temporal
1598
+ // re-admission if apply ultimately fails.
1599
+ const apply = await reminderControlPlane.applyReminderOutcome(
1600
+ claimFromDescriptor(fireDescriptor),
1601
+ outcome
1602
+ );
1603
+ // Cancel/rekey winning the race is a successful no-op from the guest's
1604
+ // perspective (fired work may have run; lifecycle authority stayed CP).
1605
+ if (!apply.applied && apply.reason === "missing") {
1606
+ return {
1607
+ reminderId: descriptor.id,
1608
+ outcome: "noop" as const,
1609
+ reason: apply.reason,
1610
+ };
1611
+ }
1612
+ return {
1613
+ reminderId: descriptor.id,
1614
+ outcome: "fired" as const,
1615
+ };
1616
+ },
1550
1617
  drainSessionDurability: (sessionId) =>
1551
1618
  engine.drainSessionDurability(sessionId),
1552
1619
  flushOtel: args.flushOtel,
@@ -2360,75 +2427,18 @@ function mountAgentRoutes(
2360
2427
  method: channelRoute.method,
2361
2428
  pattern: `${base}/v1/channels/${channel.id}${channelRoute.path}`,
2362
2429
  auth: channelAuth,
2363
- handler: async (request, ctx) => {
2364
- const handlerArgs = buildChannelHandlerArgs({
2365
- runtime,
2366
- channelId: channel.id,
2367
- params: ctx.params,
2368
- requestIp: ctx.requestIp,
2369
- auth: ctx.auth,
2370
- });
2371
-
2372
- if (channelRoute.querySchema !== undefined) {
2373
- const rawQuery = Object.fromEntries(
2374
- new URL(request.url).searchParams.entries()
2375
- );
2376
- const queryValidation = validateToolInput(
2377
- channelRoute.querySchema,
2378
- rawQuery
2379
- );
2380
- if (!queryValidation.ok) {
2381
- return jsonResponse(
2382
- { error: queryValidation.message },
2383
- { status: 400 }
2384
- );
2385
- }
2386
- handlerArgs.query = queryValidation.value;
2387
- }
2388
-
2389
- if (channelRoute.bodySchema !== undefined) {
2390
- // Buffer once so handlers that need the raw body (e.g. GitHub
2391
- // HMAC) can still read `request.text()` / `request.json()`.
2392
- let bodyText: string;
2393
- try {
2394
- bodyText = await request.text();
2395
- } catch {
2396
- return jsonResponse(
2397
- { error: "Failed to read request body" },
2398
- { status: 400 }
2399
- );
2400
- }
2401
- let rawBody: unknown;
2402
- try {
2403
- rawBody = bodyText === "" ? {} : JSON.parse(bodyText);
2404
- } catch {
2405
- return jsonResponse(
2406
- { error: "Request body must be valid JSON" },
2407
- { status: 400 }
2408
- );
2409
- }
2410
- const bodyValidation = validateToolInput(
2411
- channelRoute.bodySchema,
2412
- rawBody
2413
- );
2414
- if (!bodyValidation.ok) {
2415
- return jsonResponse(
2416
- { error: bodyValidation.message },
2417
- { status: 400 }
2418
- );
2419
- }
2420
- handlerArgs.body = bodyValidation.value;
2421
- request = new Request(request.url, {
2422
- method: request.method,
2423
- headers: request.headers,
2424
- body: bodyText === "" ? undefined : bodyText,
2425
- // node: undici / fetch may require duplex for streamed bodies;
2426
- // a string body does not.
2427
- });
2428
- }
2429
-
2430
- return channelRoute.handler(request, handlerArgs);
2431
- },
2430
+ handler: (request, ctx) =>
2431
+ invokeChannelRoute({
2432
+ route: channelRoute,
2433
+ request,
2434
+ handlerArgs: buildChannelHandlerArgs({
2435
+ runtime,
2436
+ channelId: channel.id,
2437
+ params: ctx.params,
2438
+ requestIp: ctx.requestIp,
2439
+ auth: ctx.auth,
2440
+ }),
2441
+ }),
2432
2442
  });
2433
2443
  }
2434
2444
  }