@cursor/july 0.1.104 → 0.1.107

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 (286) hide show
  1. package/dist/bin/agent-serve.js +1 -8
  2. package/dist/channels/bitbucket/types.d.ts +2 -4
  3. package/dist/channels/bitbucket/types.d.ts.map +1 -1
  4. package/dist/channels/deployments/deployments-channel.d.ts.map +1 -1
  5. package/dist/channels/deployments/deployments-channel.js +1 -0
  6. package/dist/channels/deployments/types.d.ts +7 -8
  7. package/dist/channels/deployments/types.d.ts.map +1 -1
  8. package/dist/channels/github/types.d.ts +2 -4
  9. package/dist/channels/github/types.d.ts.map +1 -1
  10. package/dist/channels/gitlab/types.d.ts +2 -5
  11. package/dist/channels/gitlab/types.d.ts.map +1 -1
  12. package/dist/channels/origin/types.d.ts +2 -3
  13. package/dist/channels/origin/types.d.ts.map +1 -1
  14. package/dist/channels/slack/defaults.d.ts.map +1 -1
  15. package/dist/channels/slack/defaults.js +3 -0
  16. package/dist/channels/slack/dispatch.d.ts.map +1 -1
  17. package/dist/channels/slack/dispatch.js +1 -1
  18. package/dist/channels/slack/live-delivery.d.ts.map +1 -1
  19. package/dist/channels/slack/live-delivery.js +48 -11
  20. package/dist/channels/slack/types.d.ts +4 -3
  21. package/dist/channels/slack/types.d.ts.map +1 -1
  22. package/dist/docs/404.html +3 -3
  23. package/dist/docs/ab.html +6 -6
  24. package/dist/docs/assets/{app.DbMWUyGb.js → app.CtuckIiL.js} +1 -1
  25. package/dist/docs/assets/chunks/@localSearchIndexroot.DXXZxiMv.js +1 -0
  26. package/dist/docs/assets/chunks/{VPLocalSearchBox.DcNeTh9o.js → VPLocalSearchBox.Bkkhnu8K.js} +2 -2
  27. package/dist/docs/assets/chunks/{theme.DMjhY3gl.js → theme.DhpIe0Pa.js} +2 -2
  28. package/dist/docs/assets/guides_grokbot-agents.md.DVqdhBKb.js +9 -0
  29. package/dist/docs/assets/guides_grokbot-agents.md.DVqdhBKb.lean.js +1 -0
  30. package/dist/docs/assets/{guides_opentelemetry.md.COh9jFVg.js → guides_opentelemetry.md.SCRMskno.js} +3 -3
  31. package/dist/docs/assets/{guides_opentelemetry.md.COh9jFVg.lean.js → guides_opentelemetry.md.SCRMskno.lean.js} +1 -1
  32. package/dist/docs/assets/index.md.BW_6tOgR.js +5 -0
  33. package/dist/docs/assets/index.md.BW_6tOgR.lean.js +1 -0
  34. package/dist/docs/assets/{reference_channels.md.DBGcaB7m.js → reference_channels.md.D-qTqwcq.js} +2 -2
  35. package/dist/docs/assets/{reference_channels.md.DBGcaB7m.lean.js → reference_channels.md.D-qTqwcq.lean.js} +1 -1
  36. package/dist/docs/assets/{reference_cli.md.CJNc_xYK.js → reference_cli.md.Dm67hd2D.js} +3 -2
  37. package/dist/docs/assets/{reference_extensions.md.DykHW3qL.js → reference_extensions.md.CGmMLblt.js} +1 -1
  38. package/dist/docs/assets/{skills_otel.md.CqhobH3B.js → skills_otel.md.B5Ddv1xH.js} +1 -1
  39. package/dist/docs/assets/{style.BRuM8477.css → style.DCD_zyg3.css} +1 -1
  40. package/dist/docs/assets/templates_grokbot-agents.md.c1s6Flp9.js +3 -0
  41. package/dist/docs/assets/templates_grokbot-agents.md.c1s6Flp9.lean.js +1 -0
  42. package/dist/docs/building-with-agents.html +6 -6
  43. package/dist/docs/concepts.html +5 -5
  44. package/dist/docs/deployment.html +6 -6
  45. package/dist/docs/evals.html +6 -6
  46. package/dist/docs/guides/agent-to-agent.html +6 -6
  47. package/dist/docs/guides/cloud-agents.html +6 -6
  48. package/dist/docs/guides/convert-automation.html +6 -6
  49. package/dist/docs/guides/github.html +6 -6
  50. package/dist/docs/guides/grokbot-agents.html +35 -0
  51. package/dist/docs/guides/grokbot-agents.md +88 -0
  52. package/dist/docs/guides/human-in-the-loop.html +6 -6
  53. package/dist/docs/guides/mcp-oauth.html +6 -6
  54. package/dist/docs/guides/opentelemetry.html +8 -8
  55. package/dist/docs/guides/opentelemetry.md +3 -4
  56. package/dist/docs/guides/slack.html +6 -6
  57. package/dist/docs/guides/webhooks.html +6 -6
  58. package/dist/docs/hashmap.json +1 -1
  59. package/dist/docs/hillclimbing.html +6 -6
  60. package/dist/docs/index.html +8 -8
  61. package/dist/docs/index.md +6 -1
  62. package/dist/docs/llms-full.txt +178 -30
  63. package/dist/docs/llms.txt +2 -0
  64. package/dist/docs/quickstart.html +6 -6
  65. package/dist/docs/reference/agent-config.html +6 -6
  66. package/dist/docs/reference/artifacts.html +6 -6
  67. package/dist/docs/reference/channels.html +7 -7
  68. package/dist/docs/reference/channels.md +11 -19
  69. package/dist/docs/reference/cli.html +10 -9
  70. package/dist/docs/reference/cli.md +7 -3
  71. package/dist/docs/reference/connections.html +6 -6
  72. package/dist/docs/reference/extensions.html +7 -7
  73. package/dist/docs/reference/extensions.md +2 -0
  74. package/dist/docs/reference/hooks.html +6 -6
  75. package/dist/docs/reference/http-api.html +6 -6
  76. package/dist/docs/reference/instructions.html +6 -6
  77. package/dist/docs/reference/playground.html +5 -5
  78. package/dist/docs/reference/project-layout.html +6 -6
  79. package/dist/docs/reference/prompt.html +6 -6
  80. package/dist/docs/reference/result.html +6 -6
  81. package/dist/docs/reference/schedules.html +6 -6
  82. package/dist/docs/reference/sessions.html +5 -5
  83. package/dist/docs/reference/skills.html +6 -6
  84. package/dist/docs/reference/subagents.html +6 -6
  85. package/dist/docs/reference/tools.html +6 -6
  86. package/dist/docs/scaffolding-agents.html +5 -5
  87. package/dist/docs/skills/ab.html +6 -6
  88. package/dist/docs/skills/create-agent.html +6 -6
  89. package/dist/docs/skills/debug.html +5 -5
  90. package/dist/docs/skills/evals.html +6 -6
  91. package/dist/docs/skills/framework-map.html +5 -5
  92. package/dist/docs/skills/github.html +6 -6
  93. package/dist/docs/skills/hillclimb.html +6 -6
  94. package/dist/docs/skills/index.html +5 -5
  95. package/dist/docs/skills/mcp-auth.html +6 -6
  96. package/dist/docs/skills/otel.html +8 -8
  97. package/dist/docs/skills/otel.md +3 -3
  98. package/dist/docs/skills/setup-slack.html +6 -6
  99. package/dist/docs/storage.html +6 -6
  100. package/dist/docs/templates/agentic-owners.html +6 -6
  101. package/dist/docs/templates/agents-md.html +6 -6
  102. package/dist/docs/templates/code-wiki.html +6 -6
  103. package/dist/docs/templates/demo.html +6 -6
  104. package/dist/docs/templates/grokbot-agents.html +29 -0
  105. package/dist/docs/templates/grokbot-agents.md +48 -0
  106. package/dist/docs/templates/pr-autofixer.html +6 -6
  107. package/dist/docs/templates/security-help.html +6 -6
  108. package/dist/docs/templates/security-reviewer.html +6 -6
  109. package/dist/docs/templates/triage.html +6 -6
  110. package/dist/docs/troubleshooting.html +5 -5
  111. package/dist/files.d.ts +7 -2
  112. package/dist/files.d.ts.map +1 -1
  113. package/dist/files.js +7 -2
  114. package/dist/index.d.ts +4 -5
  115. package/dist/index.d.ts.map +1 -1
  116. package/dist/index.js +3 -4
  117. package/dist/internal/cli-deploy.d.ts.map +1 -1
  118. package/dist/internal/cli-deploy.js +23 -11
  119. package/dist/internal/cursor/backend-client.d.ts +25 -0
  120. package/dist/internal/cursor/backend-client.d.ts.map +1 -1
  121. package/dist/internal/cursor/backend-client.js +57 -2
  122. package/dist/internal/cursor-agent-template.d.ts +1 -1
  123. package/dist/internal/cursor-agent-template.d.ts.map +1 -1
  124. package/dist/internal/cursor-agent-template.js +1 -0
  125. package/dist/internal/deploy-client.d.ts +8 -0
  126. package/dist/internal/deploy-client.d.ts.map +1 -1
  127. package/dist/internal/deploy-client.js +14 -0
  128. package/dist/internal/deploy-manifest.d.ts +3 -0
  129. package/dist/internal/deploy-manifest.d.ts.map +1 -1
  130. package/dist/internal/deploy-manifest.js +15 -12
  131. package/dist/internal/discovery/agent.d.ts.map +1 -1
  132. package/dist/internal/discovery/agent.js +1 -0
  133. package/dist/internal/discovery/info.d.ts.map +1 -1
  134. package/dist/internal/discovery/info.js +7 -3
  135. package/dist/internal/discovery/modules.d.ts +1 -1
  136. package/dist/internal/discovery/modules.d.ts.map +1 -1
  137. package/dist/internal/discovery/modules.js +1 -0
  138. package/dist/internal/discovery/project.d.ts.map +1 -1
  139. package/dist/internal/discovery/project.js +3 -0
  140. package/dist/internal/discovery/reminders.d.ts +7 -0
  141. package/dist/internal/discovery/reminders.d.ts.map +1 -0
  142. package/dist/internal/discovery/reminders.js +65 -0
  143. package/dist/internal/discovery/subagent.js +1 -0
  144. package/dist/internal/distribution.d.ts +5 -0
  145. package/dist/internal/distribution.d.ts.map +1 -1
  146. package/dist/internal/distribution.js +17 -0
  147. package/dist/internal/framework-file-storage.d.ts +3 -1
  148. package/dist/internal/framework-file-storage.d.ts.map +1 -1
  149. package/dist/internal/framework-file-storage.js +3 -1
  150. package/dist/internal/framework-storage-selection.d.ts +14 -3
  151. package/dist/internal/framework-storage-selection.d.ts.map +1 -1
  152. package/dist/internal/framework-storage-selection.js +36 -6
  153. package/dist/internal/guest-network.d.ts.map +1 -1
  154. package/dist/internal/guest-network.js +2 -0
  155. package/dist/internal/hosted-delivery-protocol.d.ts +48 -0
  156. package/dist/internal/hosted-delivery-protocol.d.ts.map +1 -1
  157. package/dist/internal/hosted-delivery-protocol.js +60 -0
  158. package/dist/internal/hosted-delivery.d.ts +17 -0
  159. package/dist/internal/hosted-delivery.d.ts.map +1 -1
  160. package/dist/internal/hosted-delivery.js +112 -19
  161. package/dist/internal/hosted-managed-automation.d.ts.map +1 -1
  162. package/dist/internal/hosted-managed-automation.js +27 -2
  163. package/dist/internal/init-project.d.ts.map +1 -1
  164. package/dist/internal/init-project.js +4 -1
  165. package/dist/internal/local-control-plane.d.ts +18 -2
  166. package/dist/internal/local-control-plane.d.ts.map +1 -1
  167. package/dist/internal/local-control-plane.js +199 -17
  168. package/dist/internal/reminder-control-plane-protocol.d.ts +187 -0
  169. package/dist/internal/reminder-control-plane-protocol.d.ts.map +1 -0
  170. package/dist/internal/reminder-control-plane-protocol.js +244 -0
  171. package/dist/internal/reminder-runner.d.ts +48 -6
  172. package/dist/internal/reminder-runner.d.ts.map +1 -1
  173. package/dist/internal/reminder-runner.js +336 -48
  174. package/dist/internal/reminder-schedule.d.ts +5 -2
  175. package/dist/internal/reminder-schedule.d.ts.map +1 -1
  176. package/dist/internal/reminder-schedule.js +8 -6
  177. package/dist/internal/reminder-store.d.ts +24 -7
  178. package/dist/internal/reminder-store.d.ts.map +1 -1
  179. package/dist/internal/reminder-store.js +6 -5
  180. package/dist/internal/request-error-status.d.ts +2 -0
  181. package/dist/internal/request-error-status.d.ts.map +1 -1
  182. package/dist/internal/request-error-status.js +22 -3
  183. package/dist/internal/scm/pr-url.d.ts +40 -0
  184. package/dist/internal/scm/pr-url.d.ts.map +1 -1
  185. package/dist/internal/scm/pr-url.js +93 -14
  186. package/dist/internal/sdk-runner.d.ts +12 -0
  187. package/dist/internal/sdk-runner.d.ts.map +1 -1
  188. package/dist/internal/sdk-runner.js +18 -0
  189. package/dist/internal/server.d.ts.map +1 -1
  190. package/dist/internal/server.js +78 -30
  191. package/dist/internal/session-engine.d.ts +12 -0
  192. package/dist/internal/session-engine.d.ts.map +1 -1
  193. package/dist/internal/session-engine.js +137 -52
  194. package/dist/internal/store-mirror.d.ts +2 -2
  195. package/dist/internal/store-mirror.js +2 -2
  196. package/dist/internal/tenant-slack/relay.d.ts +2 -0
  197. package/dist/internal/tenant-slack/relay.d.ts.map +1 -1
  198. package/dist/internal/tenant-slack/relay.js +18 -8
  199. package/dist/internal/workspace-sync.d.ts +31 -6
  200. package/dist/internal/workspace-sync.d.ts.map +1 -1
  201. package/dist/internal/workspace-sync.js +67 -12
  202. package/dist/internal/workspace.d.ts +9 -0
  203. package/dist/internal/workspace.d.ts.map +1 -1
  204. package/dist/internal/workspace.js +16 -0
  205. package/dist/playground/assets/index-Bq2HpEQB.js +67 -0
  206. package/dist/playground/assets/index-CZKKNlmb.css +1 -0
  207. package/dist/playground/index.html +2 -2
  208. package/dist/reminders.d.ts +24 -5
  209. package/dist/reminders.d.ts.map +1 -1
  210. package/dist/reminders.js +28 -5
  211. package/dist/types.d.ts +98 -28
  212. package/dist/types.d.ts.map +1 -1
  213. package/docs/README.md +6 -1
  214. package/docs/guides/grokbot-agents.md +93 -0
  215. package/docs/guides/opentelemetry.md +3 -4
  216. package/docs/reference/channels.md +11 -19
  217. package/docs/reference/cli.md +7 -3
  218. package/docs/reference/extensions.md +2 -0
  219. package/docs/templates/grokbot-agents.md +53 -0
  220. package/package.json +12 -3
  221. package/skills/otel/SKILL.md +3 -3
  222. package/src/bin/agent-serve.ts +1 -9
  223. package/src/channels/bitbucket/types.ts +2 -4
  224. package/src/channels/deployments/deployments-channel.ts +1 -0
  225. package/src/channels/deployments/types.ts +7 -8
  226. package/src/channels/github/types.ts +2 -4
  227. package/src/channels/gitlab/types.ts +2 -5
  228. package/src/channels/origin/types.ts +2 -3
  229. package/src/channels/slack/defaults.ts +3 -0
  230. package/src/channels/slack/dispatch.ts +1 -0
  231. package/src/channels/slack/live-delivery.ts +53 -12
  232. package/src/channels/slack/types.ts +4 -3
  233. package/src/files.ts +9 -2
  234. package/src/index.ts +6 -3
  235. package/src/internal/cli-deploy.ts +30 -12
  236. package/src/internal/cursor/backend-client.ts +90 -2
  237. package/src/internal/cursor-agent-template.ts +1 -0
  238. package/src/internal/deploy-client.ts +16 -0
  239. package/src/internal/deploy-manifest.ts +16 -11
  240. package/src/internal/discovery/agent.ts +1 -0
  241. package/src/internal/discovery/info.ts +6 -0
  242. package/src/internal/discovery/modules.ts +2 -4
  243. package/src/internal/discovery/project.ts +3 -0
  244. package/src/internal/discovery/reminders.ts +65 -0
  245. package/src/internal/discovery/subagent.ts +1 -0
  246. package/src/internal/distribution.ts +20 -0
  247. package/src/internal/framework-file-storage.ts +3 -1
  248. package/src/internal/framework-storage-selection.ts +43 -6
  249. package/src/internal/guest-network.ts +3 -0
  250. package/src/internal/hosted-delivery-protocol.ts +96 -0
  251. package/src/internal/hosted-delivery.ts +207 -28
  252. package/src/internal/hosted-managed-automation.ts +49 -5
  253. package/src/internal/init-project.ts +4 -1
  254. package/src/internal/local-control-plane.ts +281 -22
  255. package/src/internal/reminder-control-plane-protocol.ts +499 -0
  256. package/src/internal/reminder-runner.ts +421 -48
  257. package/src/internal/reminder-schedule.ts +11 -6
  258. package/src/internal/reminder-store.ts +27 -7
  259. package/src/internal/request-error-status.ts +27 -3
  260. package/src/internal/scm/pr-url.ts +167 -20
  261. package/src/internal/sdk-runner.ts +19 -0
  262. package/src/internal/server.ts +85 -16
  263. package/src/internal/session-engine.ts +173 -56
  264. package/src/internal/store-mirror.ts +2 -2
  265. package/src/internal/tenant-slack/relay.ts +28 -4
  266. package/src/internal/workspace-sync.ts +84 -12
  267. package/src/internal/workspace.ts +20 -0
  268. package/src/reminders.ts +35 -4
  269. package/src/types.ts +104 -28
  270. package/templates/grokbot-agents/README.md +26 -0
  271. package/templates/grokbot-agents/agent/agent.ts +6 -0
  272. package/templates/grokbot-agents/agent/extensions/grokbot.ts +5 -0
  273. package/templates/grokbot-agents/agent/instructions.md +7 -0
  274. package/templates/grokbot-agents/evals/evals.config.ts +5 -0
  275. package/templates/grokbot-agents/evals/grokbot-agents.eval.ts +18 -0
  276. package/templates/grokbot-agents/init.json +18 -0
  277. package/templates/grokbot-agents/package.json +17 -0
  278. package/templates/grokbot-agents/tsconfig.json +12 -0
  279. package/dist/docs/assets/chunks/@localSearchIndexroot.Bqsulb9K.js +0 -1
  280. package/dist/docs/assets/index.md.D75GWhPl.js +0 -5
  281. package/dist/docs/assets/index.md.D75GWhPl.lean.js +0 -1
  282. package/dist/playground/assets/index-8YiozU89.js +0 -67
  283. package/dist/playground/assets/index-MbMxXg-y.css +0 -1
  284. /package/dist/docs/assets/{reference_cli.md.CJNc_xYK.lean.js → reference_cli.md.Dm67hd2D.lean.js} +0 -0
  285. /package/dist/docs/assets/{reference_extensions.md.DykHW3qL.lean.js → reference_extensions.md.CGmMLblt.lean.js} +0 -0
  286. /package/dist/docs/assets/{skills_otel.md.CqhobH3B.lean.js → skills_otel.md.B5Ddv1xH.lean.js} +0 -0
@@ -0,0 +1,499 @@
1
+ /**
2
+ * Versioned internal reminder control-plane protocol.
3
+ *
4
+ * CP owns reminder id, status, fireCount, nextFireAt, claim, and
5
+ * completion/rearm. The guest validates create input and executes an
6
+ * immutable fire descriptor, returning a structured outcome. Cursor-hosted
7
+ * HTTP CP is not implemented here; {@link ReminderControlPlane} is the
8
+ * surface the next backend PR will speak.
9
+ *
10
+ * Not part of the public package API — keep behind the internal boundary.
11
+ */
12
+
13
+ import { randomBytes } from "node:crypto";
14
+ import type {
15
+ JsonObject,
16
+ ReminderBusyPolicy,
17
+ ReminderScheduleKind,
18
+ } from "../types.js";
19
+ import { parseDuration } from "./duration.js";
20
+ import {
21
+ nextCronFireMs,
22
+ ReminderValidationError,
23
+ type ResolvedSchedule,
24
+ resolveSchedule,
25
+ } from "./reminder-schedule.js";
26
+ import type { ReminderRecord } from "./reminder-store.js";
27
+
28
+ /** Wire / persistence version for this control-plane protocol. */
29
+ export const REMINDER_CP_PROTOCOL_VERSION = 1 as const;
30
+
31
+ export type ReminderCpProtocolVersion = typeof REMINDER_CP_PROTOCOL_VERSION;
32
+
33
+ /**
34
+ * Normalized raw schedule for create intents (JSON-safe).
35
+ * CP resolves this into nextFireAtMs / intervalMs — guests must not send
36
+ * authoritative fire times.
37
+ */
38
+ export type ReminderScheduleSpec =
39
+ | { kind: "every"; every: string }
40
+ | { kind: "cron"; cron: string }
41
+ | { kind: "delay"; delay: string | number }
42
+ | { kind: "at"; at: string };
43
+
44
+ /**
45
+ * Guest → CP create intent. No id/status/fireCount/nextFireAt — CP assigns
46
+ * those after resolving {@link ReminderScheduleSpec}.
47
+ */
48
+ export interface ReminderCreateIntent {
49
+ protocolVersion: ReminderCpProtocolVersion;
50
+ purpose: string;
51
+ channelId: string;
52
+ continuationToken: string;
53
+ target?: Record<string, unknown>;
54
+ schedule: ReminderScheduleSpec;
55
+ prompt?: string;
56
+ handler?: string;
57
+ args?: JsonObject;
58
+ hasRunHandler: boolean;
59
+ hasUntilFn: boolean;
60
+ untilPrompt?: string;
61
+ busyPolicy: ReminderBusyPolicy;
62
+ }
63
+
64
+ /**
65
+ * Immutable fire descriptor issued after CP claim. Guest execution must not
66
+ * mutate authoritative status/fireCount; it returns {@link ReminderExecutionOutcome}.
67
+ */
68
+ export interface ReminderFireDescriptor {
69
+ protocolVersion: ReminderCpProtocolVersion;
70
+ id: string;
71
+ /** Fire index being executed (equals record.fireCount at claim time). */
72
+ fireCount: number;
73
+ /** CAS generation captured at claim; cancel/rekey bumps it. */
74
+ generation: number;
75
+ purpose: string;
76
+ channelId: string;
77
+ continuationToken: string;
78
+ target?: Record<string, unknown>;
79
+ scheduleKind: ReminderScheduleKind;
80
+ scheduleValue: string;
81
+ intervalMs?: number;
82
+ prompt?: string;
83
+ handler?: string;
84
+ args?: JsonObject;
85
+ hasRunHandler: boolean;
86
+ hasUntilFn: boolean;
87
+ untilPrompt?: string;
88
+ busyPolicy: ReminderBusyPolicy;
89
+ /** Due time that triggered this claim. */
90
+ nextFireAtMs: number;
91
+ claimedAtMs: number;
92
+ }
93
+
94
+ /** Claim token used to apply an outcome exactly once for (id, fireCount, generation). */
95
+ export interface ReminderFireClaim {
96
+ id: string;
97
+ fireCount: number;
98
+ generation: number;
99
+ }
100
+
101
+ /**
102
+ * Wire-JSON-safe guest execution outcome. Retry is explicit and keyed by
103
+ * (id, fireCount) — `"retry"` does not increment fireCount.
104
+ * `reschedule.at` is an ISO-8601 string (never a Date).
105
+ */
106
+ export type ReminderExecutionOutcome =
107
+ | {
108
+ kind: "delivered";
109
+ /** Optional one-shot reschedule override (same as handler reschedule). */
110
+ reschedule?: ReminderWireReschedule;
111
+ }
112
+ | {
113
+ kind: "skipped";
114
+ reschedule?: ReminderWireReschedule;
115
+ }
116
+ | { kind: "stop"; reason: string }
117
+ | {
118
+ /** Re-arm the same fireCount after an explicit, idempotent retry. */
119
+ kind: "retry";
120
+ delayMs?: number;
121
+ reason?: string;
122
+ };
123
+
124
+ /** JSON-safe reschedule override for outcomes / hosted wire. */
125
+ export interface ReminderWireReschedule {
126
+ delay?: string | number;
127
+ /** ISO-8601 timestamp — never a Date instance. */
128
+ at?: string;
129
+ }
130
+
131
+ export type ReminderOutcomeApplyResult =
132
+ | { applied: true; record: ReminderRecord }
133
+ | {
134
+ applied: false;
135
+ reason:
136
+ | "missing"
137
+ | "stale_generation"
138
+ | "stale_fire_count"
139
+ | "not_claimed"
140
+ | "duplicate";
141
+ record?: ReminderRecord;
142
+ };
143
+
144
+ /** Guest executor callback invoked by the CP after a successful claim. */
145
+ export type ReminderGuestExecutor = (
146
+ descriptor: ReminderFireDescriptor
147
+ ) => Promise<ReminderExecutionOutcome>;
148
+
149
+ /**
150
+ * Control-plane reminder API (local disk today; Cursor-hosted HTTP later).
151
+ * Extends document CRUD with create-intent, cancel/rekey CAS, and outcome apply.
152
+ */
153
+ export interface ReminderControlPlane {
154
+ readonly protocolVersion: ReminderCpProtocolVersion;
155
+ createReminder(intent: ReminderCreateIntent): Promise<ReminderRecord>;
156
+ getReminder(id: string): Promise<ReminderRecord | undefined>;
157
+ listReminders(): Promise<ReminderRecord[]>;
158
+ cancelReminder(
159
+ id: string,
160
+ reason?: string
161
+ ): Promise<ReminderRecord | undefined>;
162
+ /**
163
+ * Re-key armed (and in-flight) reminders for a continuation rotation.
164
+ * Bumps generation so an in-flight outcome cannot land on the old token.
165
+ */
166
+ rekeyReminders(
167
+ channelId: string,
168
+ oldToken: string,
169
+ newToken: string
170
+ ): Promise<number>;
171
+ applyReminderOutcome(
172
+ claim: ReminderFireClaim,
173
+ outcome: ReminderExecutionOutcome
174
+ ): Promise<ReminderOutcomeApplyResult>;
175
+ }
176
+
177
+ export function newReminderId(): string {
178
+ return `rem_${randomBytes(8).toString("hex")}`;
179
+ }
180
+
181
+ export function recordGeneration(record: ReminderRecord): number {
182
+ return record.generation ?? 0;
183
+ }
184
+
185
+ /** Map a JSON-safe schedule spec into resolveSchedule create fields. */
186
+ export function scheduleSpecToCreateFields(schedule: ReminderScheduleSpec): {
187
+ every?: string;
188
+ cron?: string;
189
+ delay?: string | number;
190
+ at?: string;
191
+ } {
192
+ switch (schedule.kind) {
193
+ case "every":
194
+ return { every: schedule.every };
195
+ case "cron":
196
+ return { cron: schedule.cron };
197
+ case "delay":
198
+ return { delay: schedule.delay };
199
+ case "at":
200
+ return { at: schedule.at };
201
+ }
202
+ }
203
+
204
+ /**
205
+ * Build a wire-safe schedule spec from guest create input.
206
+ * Normalizes `at` Dates/numbers to ISO strings.
207
+ */
208
+ export function scheduleSpecFromCreateInput(input: {
209
+ every?: string;
210
+ cron?: string;
211
+ delay?: string | number;
212
+ at?: Date | string | number;
213
+ }): ReminderScheduleSpec {
214
+ const shapes = [
215
+ input.every !== undefined,
216
+ input.cron !== undefined,
217
+ input.delay !== undefined,
218
+ input.at !== undefined,
219
+ ].filter(Boolean);
220
+ if (shapes.length !== 1) {
221
+ throw new ReminderValidationError(
222
+ "reminders.create requires exactly one of every, cron, delay, or at"
223
+ );
224
+ }
225
+ if (input.every !== undefined) {
226
+ return { kind: "every", every: input.every };
227
+ }
228
+ if (input.cron !== undefined) {
229
+ return { kind: "cron", cron: input.cron };
230
+ }
231
+ if (input.delay !== undefined) {
232
+ return { kind: "delay", delay: input.delay };
233
+ }
234
+ return { kind: "at", at: normalizeScheduleAt(input.at!) };
235
+ }
236
+
237
+ /**
238
+ * Normalize a guest-facing `at` value to an ISO string for the wire intent.
239
+ */
240
+ export function normalizeScheduleAt(at: Date | string | number): string {
241
+ if (at instanceof Date) {
242
+ if (!Number.isFinite(at.getTime())) {
243
+ throw new ReminderValidationError(`Invalid at time: ${String(at)}`);
244
+ }
245
+ return at.toISOString();
246
+ }
247
+ if (typeof at === "number") {
248
+ if (!Number.isFinite(at)) {
249
+ throw new ReminderValidationError(`Invalid at time: ${String(at)}`);
250
+ }
251
+ return new Date(at).toISOString();
252
+ }
253
+ const ms = Date.parse(at);
254
+ if (!Number.isFinite(ms)) {
255
+ throw new ReminderValidationError(`Invalid at time: ${String(at)}`);
256
+ }
257
+ // Preserve caller string when it already parses; still wire-safe.
258
+ return at;
259
+ }
260
+
261
+ /** Normalize handler reschedule into a wire-JSON-safe shape. */
262
+ export function toWireReschedule(reschedule: {
263
+ delay?: string | number;
264
+ at?: Date | string;
265
+ }): ReminderWireReschedule {
266
+ const out: ReminderWireReschedule = {};
267
+ if (reschedule.delay !== undefined) {
268
+ out.delay = reschedule.delay;
269
+ }
270
+ if (reschedule.at !== undefined) {
271
+ out.at =
272
+ reschedule.at instanceof Date
273
+ ? reschedule.at.toISOString()
274
+ : String(reschedule.at);
275
+ }
276
+ return out;
277
+ }
278
+
279
+ /**
280
+ * Build a durable record from a create intent. CP owns schedule resolution
281
+ * via {@link resolveSchedule}; pass `nowMs` for deterministic tests.
282
+ */
283
+ export function intentToNewRecord(
284
+ intent: ReminderCreateIntent,
285
+ nowMs: number = Date.now()
286
+ ): ReminderRecord {
287
+ if (intent.protocolVersion !== REMINDER_CP_PROTOCOL_VERSION) {
288
+ throw new ReminderValidationError(
289
+ `Unsupported reminder CP protocol version ${String(intent.protocolVersion)}`
290
+ );
291
+ }
292
+ const resolved: ResolvedSchedule = resolveSchedule(
293
+ scheduleSpecToCreateFields(intent.schedule),
294
+ nowMs
295
+ );
296
+ const nowIso = new Date(nowMs).toISOString();
297
+ return {
298
+ version: 1,
299
+ id: newReminderId(),
300
+ purpose: intent.purpose,
301
+ channelId: intent.channelId,
302
+ continuationToken: intent.continuationToken,
303
+ target: intent.target,
304
+ scheduleKind: resolved.kind,
305
+ scheduleValue: resolved.value,
306
+ intervalMs: resolved.intervalMs,
307
+ prompt: intent.prompt,
308
+ handler: intent.handler,
309
+ args: intent.args,
310
+ hasRunHandler: intent.hasRunHandler,
311
+ hasUntilFn: intent.hasUntilFn,
312
+ untilPrompt: intent.untilPrompt,
313
+ busyPolicy: intent.busyPolicy,
314
+ status: "armed",
315
+ fireCount: 0,
316
+ nextFireAtMs: resolved.nextFireAtMs,
317
+ generation: 0,
318
+ createdAt: nowIso,
319
+ updatedAt: nowIso,
320
+ };
321
+ }
322
+
323
+ export function toFireDescriptor(
324
+ record: ReminderRecord,
325
+ claimedAtMs: number
326
+ ): ReminderFireDescriptor {
327
+ return {
328
+ protocolVersion: REMINDER_CP_PROTOCOL_VERSION,
329
+ id: record.id,
330
+ fireCount: record.fireCount,
331
+ generation: recordGeneration(record),
332
+ purpose: record.purpose,
333
+ channelId: record.channelId,
334
+ continuationToken: record.continuationToken,
335
+ target: record.target,
336
+ scheduleKind: record.scheduleKind,
337
+ scheduleValue: record.scheduleValue,
338
+ intervalMs: record.intervalMs,
339
+ prompt: record.prompt,
340
+ handler: record.handler,
341
+ args: record.args,
342
+ hasRunHandler: record.hasRunHandler,
343
+ hasUntilFn: record.hasUntilFn,
344
+ untilPrompt: record.untilPrompt,
345
+ busyPolicy: record.busyPolicy,
346
+ nextFireAtMs: record.nextFireAtMs,
347
+ claimedAtMs,
348
+ };
349
+ }
350
+
351
+ export function claimFromDescriptor(
352
+ descriptor: ReminderFireDescriptor
353
+ ): ReminderFireClaim {
354
+ return {
355
+ id: descriptor.id,
356
+ fireCount: descriptor.fireCount,
357
+ generation: descriptor.generation,
358
+ };
359
+ }
360
+
361
+ function resolveRescheduleMs(
362
+ reschedule: ReminderWireReschedule,
363
+ nowMs: number
364
+ ): number {
365
+ if (reschedule.delay !== undefined) {
366
+ return nowMs + parseDuration(reschedule.delay);
367
+ }
368
+ if (reschedule.at !== undefined) {
369
+ const atMs = Date.parse(reschedule.at);
370
+ if (!Number.isFinite(atMs)) {
371
+ throw new ReminderValidationError("Invalid reminder reschedule.at");
372
+ }
373
+ return atMs;
374
+ }
375
+ throw new ReminderValidationError("reminder reschedule requires delay or at");
376
+ }
377
+
378
+ /** Next fire time from the record's recurring schedule (CP-owned). */
379
+ export function nextScheduleFireAtMs(
380
+ record: ReminderRecord,
381
+ afterMs: number
382
+ ): number {
383
+ if (record.scheduleKind === "every" && record.intervalMs !== undefined) {
384
+ return afterMs + record.intervalMs;
385
+ }
386
+ if (record.scheduleKind === "cron") {
387
+ return nextCronFireMs(record.scheduleValue, afterMs);
388
+ }
389
+ throw new ReminderValidationError(
390
+ `Cannot advance schedule for reminder "${record.id}" (kind=${record.scheduleKind})`
391
+ );
392
+ }
393
+
394
+ export function isOneShotSchedule(kind: ReminderScheduleKind): boolean {
395
+ return kind === "delay" || kind === "at";
396
+ }
397
+
398
+ /**
399
+ * Pure CAS apply of a guest outcome onto a reminder record.
400
+ * Callers persist the returned record when `applied: true`.
401
+ * Requires `status === "firing"` — an armed record was never claimed.
402
+ */
403
+ export function applyOutcomeToRecord(
404
+ record: ReminderRecord,
405
+ claim: ReminderFireClaim,
406
+ outcome: ReminderExecutionOutcome,
407
+ nowMs: number = Date.now()
408
+ ): ReminderOutcomeApplyResult {
409
+ if (record.id !== claim.id) {
410
+ return { applied: false, reason: "missing", record };
411
+ }
412
+ // Prefer duplicate over generation mismatch: a successful prior apply
413
+ // bumps both fireCount and generation, and retries of (id, fireCount)
414
+ // must stay idempotent no-ops.
415
+ if (record.fireCount > claim.fireCount) {
416
+ return { applied: false, reason: "duplicate", record };
417
+ }
418
+ if (record.fireCount < claim.fireCount) {
419
+ return { applied: false, reason: "stale_fire_count", record };
420
+ }
421
+ if (recordGeneration(record) !== claim.generation) {
422
+ return { applied: false, reason: "stale_generation", record };
423
+ }
424
+ // Only a claimed (firing) reminder can accept an outcome.
425
+ if (record.status !== "firing") {
426
+ return { applied: false, reason: "not_claimed", record };
427
+ }
428
+
429
+ const nowIso = new Date(nowMs).toISOString();
430
+
431
+ if (outcome.kind === "retry") {
432
+ const delayMs = outcome.delayMs ?? 0;
433
+ const updated: ReminderRecord = {
434
+ ...record,
435
+ status: "armed",
436
+ nextFireAtMs: nowMs + Math.max(0, delayMs),
437
+ // Same fireCount; bump generation so a second apply of the old claim is stale.
438
+ generation: recordGeneration(record) + 1,
439
+ updatedAt: nowIso,
440
+ };
441
+ return { applied: true, record: updated };
442
+ }
443
+
444
+ if (outcome.kind === "stop") {
445
+ const updated: ReminderRecord = {
446
+ ...record,
447
+ status: "completed",
448
+ disarmReason: outcome.reason,
449
+ fireCount: record.fireCount + 1,
450
+ generation: recordGeneration(record) + 1,
451
+ updatedAt: nowIso,
452
+ };
453
+ return { applied: true, record: updated };
454
+ }
455
+
456
+ const reschedule = outcome.reschedule;
457
+ if (reschedule !== undefined) {
458
+ const nextFireAtMs = resolveRescheduleMs(reschedule, nowMs);
459
+ const updated: ReminderRecord = {
460
+ ...record,
461
+ status: "armed",
462
+ fireCount: record.fireCount + 1,
463
+ nextFireAtMs,
464
+ scheduleKind: reschedule.delay !== undefined ? "delay" : "at",
465
+ scheduleValue:
466
+ reschedule.delay !== undefined
467
+ ? String(reschedule.delay)
468
+ : String(reschedule.at),
469
+ intervalMs: undefined,
470
+ generation: recordGeneration(record) + 1,
471
+ updatedAt: nowIso,
472
+ };
473
+ return { applied: true, record: updated };
474
+ }
475
+
476
+ if (isOneShotSchedule(record.scheduleKind)) {
477
+ const updated: ReminderRecord = {
478
+ ...record,
479
+ status: "completed",
480
+ disarmReason:
481
+ outcome.kind === "skipped" ? "skipped_one_shot" : "one_shot_complete",
482
+ fireCount: record.fireCount + 1,
483
+ generation: recordGeneration(record) + 1,
484
+ updatedAt: nowIso,
485
+ };
486
+ return { applied: true, record: updated };
487
+ }
488
+
489
+ const nextFireAtMs = nextScheduleFireAtMs(record, nowMs);
490
+ const updated: ReminderRecord = {
491
+ ...record,
492
+ status: "armed",
493
+ fireCount: record.fireCount + 1,
494
+ nextFireAtMs,
495
+ generation: recordGeneration(record) + 1,
496
+ updatedAt: nowIso,
497
+ };
498
+ return { applied: true, record: updated };
499
+ }