@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
@@ -202,13 +202,19 @@ import { resolveAgentV2Hosting } from "./v2-hosting.js";
202
202
  import {
203
203
  buildAgentsMdContent,
204
204
  buildCloudMemorySection,
205
+ buildCloudSessionWorkspaceSection,
205
206
  buildCloudWorkspaceSection,
206
207
  ensureOwnGitRoot,
207
208
  isNestedInGitRepo,
208
209
  materializeWorkspace,
209
210
  SERVER_TOOLS_MCP_SERVER_NAME,
210
211
  } from "./workspace.js";
211
- import { syncWorkspaceToStore } from "./workspace-sync.js";
212
+ import {
213
+ syncSessionWorkspaceFiles,
214
+ syncWorkspaceToStore,
215
+ WorkspaceFileRefusal,
216
+ wipeSessionWorkspace,
217
+ } from "./workspace-sync.js";
212
218
 
213
219
  export const HTTP_CHANNEL_ID = "http";
214
220
  export { SCHEDULE_CHANNEL_PREFIX } from "./prompt-context.js";
@@ -222,6 +228,7 @@ interface CloudStoreTarget {
222
228
  mountPath: string;
223
229
  skillsPrefix: string;
224
230
  workspacePrefix: string;
231
+ sessionWorkspacePrefix: (sessionId: string) => string;
225
232
  label: string;
226
233
  }
227
234
 
@@ -2254,10 +2261,13 @@ export class SessionEngine {
2254
2261
  await this.baselineDeliveredNudges(record);
2255
2262
  const pendingFiles = record.pendingWorkspaceFiles;
2256
2263
  const pendingImages = record.pendingImages;
2257
- if (
2258
- pendingFiles !== undefined &&
2259
- Object.keys(pendingFiles).length > 0
2260
- ) {
2264
+ const runtime = resolveSessionRuntime({
2265
+ agentRuntime: agent.runtime,
2266
+ cloudOverride: record.cloudOverride,
2267
+ });
2268
+ const hasPendingFiles =
2269
+ pendingFiles !== undefined && Object.keys(pendingFiles).length > 0;
2270
+ if (hasPendingFiles && runtime !== "cloud") {
2261
2271
  const root = await this.resolveFileSystem({
2262
2272
  sessionId,
2263
2273
  continuationKey: record.continuationKey ?? undefined,
@@ -2267,13 +2277,14 @@ export class SessionEngine {
2267
2277
  await writeWorkspaceFilesToRoot(root, pendingFiles);
2268
2278
  }
2269
2279
  if (
2270
- (pendingFiles !== undefined &&
2271
- Object.keys(pendingFiles).length > 0) ||
2280
+ (hasPendingFiles && runtime !== "cloud") ||
2272
2281
  (pendingImages !== undefined && pendingImages.length > 0)
2273
2282
  ) {
2274
2283
  await this.updateRecord(sessionId, (current) => ({
2275
2284
  ...current,
2276
2285
  pendingWorkspaceFiles:
2286
+ hasPendingFiles &&
2287
+ runtime !== "cloud" &&
2277
2288
  current.pendingWorkspaceFiles === pendingFiles
2278
2289
  ? undefined
2279
2290
  : current.pendingWorkspaceFiles,
@@ -2441,6 +2452,7 @@ export class SessionEngine {
2441
2452
  data: { message: outcome.errorMessage ?? "task failed" },
2442
2453
  }
2443
2454
  );
2455
+ await this.wipeCloudSessionWorkspace(sessionId);
2444
2456
  } else {
2445
2457
  await this.appendEvent(sessionId, {
2446
2458
  type: "session.waiting",
@@ -2495,6 +2507,68 @@ export class SessionEngine {
2495
2507
  );
2496
2508
  }
2497
2509
 
2510
+ /**
2511
+ * Replace this session's store prefix with the pending `workspaceFiles`.
2512
+ * Undefined when the write did not land; the prompt must not name the
2513
+ * mount, matching ensureCloudWorkspaceSynced.
2514
+ */
2515
+ private async syncCloudSessionWorkspaceFiles(
2516
+ store: CloudStoreTarget,
2517
+ sessionId: string,
2518
+ files: NonNullable<SessionRecord["pendingWorkspaceFiles"]>
2519
+ ): Promise<{ dir: string; relPaths: string[] } | undefined> {
2520
+ const workspacePrefix = store.sessionWorkspacePrefix(sessionId);
2521
+ try {
2522
+ const relPaths = await syncSessionWorkspaceFiles({
2523
+ sink: store.sink,
2524
+ files,
2525
+ workspacePrefix,
2526
+ });
2527
+ return {
2528
+ dir: `${store.mountPath}/${workspacePrefix}`,
2529
+ relPaths,
2530
+ };
2531
+ } catch (error) {
2532
+ this.logger(
2533
+ `[workspace] ${store.label} session files sync failed: ${error instanceof Error ? error.message : String(error)}`
2534
+ );
2535
+ if (error instanceof WorkspaceFileRefusal) {
2536
+ await this.clearPendingWorkspaceFiles(sessionId, files);
2537
+ }
2538
+ return undefined;
2539
+ }
2540
+ }
2541
+
2542
+ private async wipeCloudSessionWorkspace(sessionId: string): Promise<void> {
2543
+ const store = this.cloudStore();
2544
+ if (store === undefined) {
2545
+ return;
2546
+ }
2547
+ try {
2548
+ await wipeSessionWorkspace({
2549
+ sink: store.sink,
2550
+ workspacePrefix: store.sessionWorkspacePrefix(sessionId),
2551
+ });
2552
+ } catch (error) {
2553
+ this.logger(
2554
+ `[workspace] ${store.label} session files wipe failed: ${error instanceof Error ? error.message : String(error)}`
2555
+ );
2556
+ }
2557
+ }
2558
+
2559
+ private async clearPendingWorkspaceFiles(
2560
+ sessionId: string,
2561
+ pending: NonNullable<SessionRecord["pendingWorkspaceFiles"]>
2562
+ ): Promise<void> {
2563
+ await this.updateRecord(sessionId, (current) => ({
2564
+ ...current,
2565
+ pendingWorkspaceFiles:
2566
+ current.pendingWorkspaceFiles === pending
2567
+ ? undefined
2568
+ : current.pendingWorkspaceFiles,
2569
+ }));
2570
+ }
2571
+
2498
2572
  /**
2499
2573
  * With files authored, every first cloud turn re-copies the authored
2500
2574
  * bytes and the memo only dedupes concurrent first turns. The mount is
@@ -2547,6 +2621,8 @@ export class SessionEngine {
2547
2621
  mountPath: DEPLOYMENT_STORE_MOUNT_PATH,
2548
2622
  skillsPrefix: agentStoreKeys.skillsPrefix(),
2549
2623
  workspacePrefix: agentStoreKeys.workspacePrefix(),
2624
+ sessionWorkspacePrefix: (sessionId: string) =>
2625
+ agentStoreKeys.sessionWorkspacePrefix(sessionId),
2550
2626
  label: "deployment store",
2551
2627
  };
2552
2628
  if (v2Hosting.kind === "cursor-managed") {
@@ -2569,6 +2645,11 @@ export class SessionEngine {
2569
2645
  mountPath: USER_STORE_MOUNT_PATH,
2570
2646
  skillsPrefix: agentStoreKeys.userSkillsPrefix(this.project.name),
2571
2647
  workspacePrefix: agentStoreKeys.userWorkspacePrefix(this.project.name),
2648
+ sessionWorkspacePrefix: (sessionId: string) =>
2649
+ agentStoreKeys.userSessionWorkspacePrefix(
2650
+ this.project.name,
2651
+ sessionId
2652
+ ),
2572
2653
  label: "user store",
2573
2654
  };
2574
2655
  }
@@ -2690,34 +2771,39 @@ export class SessionEngine {
2690
2771
  });
2691
2772
  turnPrompt = `${contextBlock}\n\n${prompt}`;
2692
2773
  }
2693
- if (isFirstTurn) {
2694
- if (runtime === "cloud") {
2695
- // Cloud VMs have no materialized workspace: ship the full context,
2696
- // including agent tool script bodies. Authored skills and workspace
2697
- // seeds land on the store mount (skills for native discovery, seeds
2698
- // for the model's file tools), and on hosted deployments the shared
2699
- // memory journal is on that mount too — say where. When the turn
2700
- // carries the session-bound server-tool MCP endpoint, the preamble
2701
- // catalogs server tools under that MCP server name *before* the
2702
- // agent-tool "write the script if missing" recipe, so a model told
2703
- // to "use <server tool>" calls MCP instead of inventing a .sh.
2704
- const preamble = buildAgentsMdContent(agent, {
2774
+ if (runtime === "cloud") {
2775
+ // Cloud VMs have no materialized workspace: ship the full context,
2776
+ // including agent tool script bodies. Authored skills and workspace
2777
+ // seeds land on the store mount (skills for native discovery, seeds
2778
+ // for the model's file tools), per-turn workspaceFiles land under
2779
+ // the session prefix, and on hosted deployments the shared memory
2780
+ // journal is on that mount too — say where. When the turn carries
2781
+ // the session-bound server-tool MCP endpoint, the preamble catalogs
2782
+ // server tools under that MCP server name *before* the agent-tool
2783
+ // "write the script if missing" recipe, so a model told to "use
2784
+ // <server tool>" calls MCP instead of inventing a .sh.
2785
+ const hosted = isCursorHostedFilesAvailable();
2786
+ const store =
2787
+ hosted || isCursorUserStoreFilesAvailable()
2788
+ ? this.cloudStore()
2789
+ : undefined;
2790
+ let preamble = "";
2791
+ const sections: string[] = [];
2792
+ const pending = record.pendingWorkspaceFiles;
2793
+ if (isFirstTurn) {
2794
+ preamble = buildAgentsMdContent(agent, {
2705
2795
  includeScripts: true,
2706
2796
  serverToolsMcpName:
2707
2797
  this.cloudServerToolsConnection === undefined
2708
2798
  ? undefined
2709
2799
  : SERVER_TOOLS_MCP_SERVER_NAME,
2710
2800
  });
2711
- const hosted = isCursorHostedFilesAvailable();
2712
- // Synced whenever a store is reachable, not just when skills or
2713
- // seeds exist: removing the last one has to delete its store copies
2714
- // too.
2715
- const store =
2716
- hosted || isCursorUserStoreFilesAvailable()
2717
- ? this.cloudStore()
2718
- : undefined;
2719
- let sections = "";
2720
- if (store !== undefined) {
2801
+ }
2802
+ if (store !== undefined) {
2803
+ if (isFirstTurn) {
2804
+ // Synced whenever a store is reachable, not just when skills or
2805
+ // seeds exist: removing the last one has to delete its store copies
2806
+ // too.
2721
2807
  const [, seedsSynced] = await Promise.all([
2722
2808
  this.ensureCloudSkillsSynced(store),
2723
2809
  this.ensureCloudWorkspaceSynced(store),
@@ -2725,37 +2811,63 @@ export class SessionEngine {
2725
2811
  // Only name the mount when the bytes landed; a failed sync must
2726
2812
  // not send the model looking for files that are not there.
2727
2813
  if (seedsSynced && agent.seedFiles.length > 0) {
2728
- const dir = `${store.mountPath}/${store.workspacePrefix}`;
2729
- sections += `\n\n${buildCloudWorkspaceSection(dir)}`;
2814
+ sections.push(
2815
+ buildCloudWorkspaceSection(
2816
+ `${store.mountPath}/${store.workspacePrefix}`
2817
+ )
2818
+ );
2730
2819
  }
2731
2820
  }
2732
- if (hosted) {
2733
- sections += `\n\n${buildCloudMemorySection(this.project.name)}`;
2821
+ if (pending !== undefined && Object.keys(pending).length > 0) {
2822
+ const synced = await this.syncCloudSessionWorkspaceFiles(
2823
+ store,
2824
+ record.sessionId,
2825
+ pending
2826
+ );
2827
+ if (synced !== undefined) {
2828
+ sections.push(
2829
+ buildCloudSessionWorkspaceSection(synced.dir, synced.relPaths)
2830
+ );
2831
+ await this.clearPendingWorkspaceFiles(record.sessionId, pending);
2832
+ }
2734
2833
  }
2735
- turnPrompt = `${preamble}${sections}\n\n${turnPrompt}`;
2736
- } else if (runtime === "local" && authoredRoot) {
2737
- // Virtual roots skip materialize, so AGENTS.md never lands on disk.
2738
- // Ship the same preamble the borrowed-cwd local path uses.
2739
- const preamble = buildAgentsMdContent(
2740
- { ...agent, tools: [...agent.tools, ...synthesized] },
2741
- { includeScripts: true }
2742
- );
2743
- const workspaceInfo = `<user_info>\nWorkspace Path: /\n</user_info>`;
2744
- turnPrompt =
2745
- preamble === ""
2746
- ? `${workspaceInfo}\n\n${turnPrompt}`
2747
- : `${workspaceInfo}\n\n${preamble}\n\n${turnPrompt}`;
2748
- } else if (runtime === "local" && instructionsInvisibleToHarness) {
2749
- // Local workspace whose AGENTS.md the harness will not load. With a
2750
- // borrowed harness cwd, agent tool scripts live only in the durable
2751
- // scaffold dir — embed bodies like cloud. Otherwise the catalog can
2752
- // reference on-disk paths under the harness cwd. Grokbot turns are
2753
- // excluded: their instructions ride the request's `instructions`
2754
- // field into the Sand system prompt instead.
2755
- const includeScripts = this.hasBorrowedWorkspace(record);
2756
- const preamble = buildAgentsMdContent(agent, { includeScripts });
2757
- turnPrompt = `${preamble}\n\n${turnPrompt}`;
2834
+ } else if (pending !== undefined && Object.keys(pending).length > 0) {
2835
+ await this.clearPendingWorkspaceFiles(record.sessionId, pending);
2836
+ }
2837
+ if (isFirstTurn && hosted) {
2838
+ sections.push(buildCloudMemorySection(this.project.name));
2839
+ }
2840
+ if (preamble !== "" || sections.length > 0) {
2841
+ turnPrompt = [preamble, ...sections, turnPrompt]
2842
+ .filter((part) => part !== "")
2843
+ .join("\n\n");
2758
2844
  }
2845
+ } else if (isFirstTurn && runtime === "local" && authoredRoot) {
2846
+ // Virtual roots skip materialize, so AGENTS.md never lands on disk.
2847
+ // Ship the same preamble the borrowed-cwd local path uses.
2848
+ const preamble = buildAgentsMdContent(
2849
+ { ...agent, tools: [...agent.tools, ...synthesized] },
2850
+ { includeScripts: true }
2851
+ );
2852
+ const workspaceInfo = `<user_info>\nWorkspace Path: /\n</user_info>`;
2853
+ turnPrompt =
2854
+ preamble === ""
2855
+ ? `${workspaceInfo}\n\n${turnPrompt}`
2856
+ : `${workspaceInfo}\n\n${preamble}\n\n${turnPrompt}`;
2857
+ } else if (
2858
+ isFirstTurn &&
2859
+ runtime === "local" &&
2860
+ instructionsInvisibleToHarness
2861
+ ) {
2862
+ // Local workspace whose AGENTS.md the harness will not load. With a
2863
+ // borrowed harness cwd, agent tool scripts live only in the durable
2864
+ // scaffold dir — embed bodies like cloud. Otherwise the catalog can
2865
+ // reference on-disk paths under the harness cwd. Grokbot turns are
2866
+ // excluded: their instructions ride the request's `instructions`
2867
+ // field into the Sand system prompt instead.
2868
+ const includeScripts = this.hasBorrowedWorkspace(record);
2869
+ const preamble = buildAgentsMdContent(agent, { includeScripts });
2870
+ turnPrompt = `${preamble}\n\n${turnPrompt}`;
2759
2871
  }
2760
2872
  return {
2761
2873
  sessionId: record.sessionId,
@@ -4632,6 +4744,10 @@ export class SessionEngine {
4632
4744
  * conversation whose blobs persisted but whose record (which selects the
4633
4745
  * conversation to hydrate) never reached the sink. No-op without a
4634
4746
  * storage mirror.
4747
+ *
4748
+ * Under a debounced event policy the turn-end flush is a timer, not the
4749
+ * chain's tail, so flush explicitly before waiting on the queue; a no-op
4750
+ * when the chain already flushed.
4635
4751
  */
4636
4752
  async drainSessionDurability(sessionId: string): Promise<void> {
4637
4753
  if (this.storage === undefined) {
@@ -4641,6 +4757,7 @@ export class SessionEngine {
4641
4757
  if (chain !== undefined) {
4642
4758
  await chain.catch(() => {});
4643
4759
  }
4760
+ this.storage.flushSession(sessionId);
4644
4761
  await this.storage.whenIdle();
4645
4762
  }
4646
4763
 
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Mirror an authored file tree onto an Agent Store prefix. Shared by the
3
- * skills and workspace seed syncs so both keep the same write-then-sweep
4
- * discipline and the same path refusals.
3
+ * skills, workspace seed, and session workspaceFile syncs so they keep the
4
+ * same write-then-sweep discipline and the same path refusals.
5
5
  */
6
6
 
7
7
  import { posix } from "node:path";
@@ -109,13 +109,22 @@ export async function postRelaySlackMessage(args: {
109
109
  resolve?: () => RelaySlackPostTarget | undefined;
110
110
  fetchImpl?: typeof fetch;
111
111
  timeoutMs?: number;
112
+ /** Diagnostic sink; defaults to `console.info`. */
113
+ log?: (line: string) => void;
112
114
  }): Promise<RelaySlackPostResult> {
113
115
  const channel = args.channel.trim();
116
+ const log = args.log ?? ((line: string) => console.info(line));
114
117
  if (channel === "") {
118
+ log(
119
+ `[agent-sdk-slack-relay-client] refusing post automationUuid=${args.automationUuid}: empty channel`
120
+ );
115
121
  return { ok: false, channel, status: 0, error: "empty channel" };
116
122
  }
117
123
  const target = (args.resolve ?? resolveRelaySlackTarget)();
118
124
  if (target === undefined) {
125
+ log(
126
+ `[agent-sdk-slack-relay-client] refusing post automationUuid=${args.automationUuid} channel=${channel}: no Cursor credential resolved (CURSOR_API_KEY / pod credential absent)`
127
+ );
119
128
  return {
120
129
  ok: false,
121
130
  channel,
@@ -128,6 +137,11 @@ export async function postRelaySlackMessage(args: {
128
137
  baseUrl: target.relayBaseUrl,
129
138
  automationUuid: args.automationUuid,
130
139
  });
140
+ const messageChars = args.message.length;
141
+ log(
142
+ `[agent-sdk-slack-relay-client] POST ${url} channel=${channel} messageChars=${String(messageChars)} truncated=${String(messageChars > AGENT_SDK_SLACK_RELAY_MAX_MESSAGE_CHARS)} threaded=${String(args.threadTs !== undefined)} timeoutMs=${String(args.timeoutMs ?? DEFAULT_TIMEOUT_MS)}`
143
+ );
144
+ const startedAt = Date.now();
131
145
  let response: Response;
132
146
  try {
133
147
  response = await fetchImpl(url, {
@@ -145,6 +159,9 @@ export async function postRelaySlackMessage(args: {
145
159
  signal: AbortSignal.timeout(args.timeoutMs ?? DEFAULT_TIMEOUT_MS),
146
160
  });
147
161
  } catch (error) {
162
+ log(
163
+ `[agent-sdk-slack-relay-client] request failed before a response channel=${channel} automationUuid=${args.automationUuid} elapsedMs=${String(Date.now() - startedAt)}: ${describeError(error)}`
164
+ );
148
165
  return { ok: false, channel, status: 0, error: describeError(error) };
149
166
  }
150
167
  let body: unknown;
@@ -158,16 +175,23 @@ export async function postRelaySlackMessage(args: {
158
175
  ? (body as Record<string, unknown>)
159
176
  : {};
160
177
  if (!response.ok || record.ok !== true) {
178
+ const error =
179
+ typeof record.error === "string" && record.error !== ""
180
+ ? record.error
181
+ : `relay responded ${response.status}`;
182
+ log(
183
+ `[agent-sdk-slack-relay-client] relay denied channel=${channel} automationUuid=${args.automationUuid} status=${String(response.status)} bodyOk=${String(record.ok)} elapsedMs=${String(Date.now() - startedAt)}: ${error}`
184
+ );
161
185
  return {
162
186
  ok: false,
163
187
  channel,
164
188
  status: response.status,
165
- error:
166
- typeof record.error === "string" && record.error !== ""
167
- ? record.error
168
- : `relay responded ${response.status}`,
189
+ error,
169
190
  };
170
191
  }
192
+ log(
193
+ `[agent-sdk-slack-relay-client] relay posted channel=${typeof record.channel === "string" ? record.channel : channel} automationUuid=${args.automationUuid} status=${String(response.status)} ts=${typeof record.ts === "string" ? record.ts : "<none>"} elapsedMs=${String(Date.now() - startedAt)}`
194
+ );
171
195
  return {
172
196
  ok: true,
173
197
  channel: typeof record.channel === "string" ? record.channel : channel,
@@ -1,10 +1,10 @@
1
1
  /**
2
- * Sync `agent/sandbox/workspace/**` seed files onto an Agent Store so
3
- * cloud-runtime turns read them from the store mount (the first cloud prompt
4
- * names the directory; see `buildCloudWorkspaceSection`). Hosted deployments
5
- * write store-root `workspace/<path>`. Local `serve`/`run` with
6
- * `runtime: "cloud"` writes `agent-serve/<agent>/workspace/<path>` on the
7
- * USER store.
2
+ * Sync workspace trees onto an Agent Store so cloud-runtime turns read
3
+ * them from the store mount. Authored `agent/sandbox/workspace/**` seeds
4
+ * land at store-root `workspace/<path>` (see `buildCloudWorkspaceSection`).
5
+ * Per-turn `workspaceFiles` land at `sessions/<sessionId>/workspace/<path>`
6
+ * (see `buildCloudSessionWorkspaceSection`). Personal-key local+cloud
7
+ * nests both under `agent-serve/<agent>/`.
8
8
  */
9
9
 
10
10
  import { readFile } from "node:fs/promises";
@@ -13,6 +13,15 @@ import { MEMORY_DIR_NAME } from "../memory.js";
13
13
  import type { SeedFile } from "../types.js";
14
14
  import { mirrorToStore, storeRelPath } from "./store-mirror.js";
15
15
 
16
+ export type WorkspaceFileBody =
17
+ | string
18
+ | { encoding: "base64"; data: string; mimeType?: string };
19
+
20
+ /** Path or encoding that can never land. Distinct from a store outage. */
21
+ export class WorkspaceFileRefusal extends Error {
22
+ override readonly name = "WorkspaceFileRefusal";
23
+ }
24
+
16
25
  /**
17
26
  * Mirror the seed tree onto the store at its authored relative paths and
18
27
  * delete keys no seed claims so redeploys drop removed files. Every seed
@@ -30,7 +39,7 @@ export async function syncWorkspaceToStore(args: {
30
39
  for (const seed of args.seedFiles) {
31
40
  desired.set(
32
41
  agentStoreKeys.workspaceFile(
33
- seedRelPath(seed.relativePath),
42
+ workspaceRelPath(seed.relativePath),
34
43
  workspacePrefix
35
44
  ),
36
45
  await readFile(seed.sourcePath)
@@ -39,17 +48,80 @@ export async function syncWorkspaceToStore(args: {
39
48
  await mirrorToStore({ sink: args.sink, prefix: workspacePrefix, desired });
40
49
  }
41
50
 
51
+ /**
52
+ * Replace the session workspace prefix with this turn's `workspaceFiles`.
53
+ * Keys the map does not claim are deleted so a later send cannot leave a
54
+ * stale `pr.json`. Every path validates before the store is touched.
55
+ * Returns the relative paths that landed, in send order.
56
+ */
57
+ export async function syncSessionWorkspaceFiles(args: {
58
+ sink: FileSink;
59
+ files: Record<string, WorkspaceFileBody>;
60
+ workspacePrefix: string;
61
+ }): Promise<string[]> {
62
+ const desired = new Map<string, Uint8Array>();
63
+ const relPaths: string[] = [];
64
+ for (const [relativePath, contents] of Object.entries(args.files)) {
65
+ const rel = workspaceRelPath(relativePath);
66
+ desired.set(
67
+ agentStoreKeys.workspaceFile(rel, args.workspacePrefix),
68
+ workspaceFileBytes(relativePath, contents)
69
+ );
70
+ relPaths.push(rel);
71
+ }
72
+ await mirrorToStore({
73
+ sink: args.sink,
74
+ prefix: args.workspacePrefix,
75
+ desired,
76
+ });
77
+ return relPaths;
78
+ }
79
+
80
+ /** Delete every key under the session workspace prefix. */
81
+ export async function wipeSessionWorkspace(args: {
82
+ sink: FileSink;
83
+ workspacePrefix: string;
84
+ }): Promise<void> {
85
+ await mirrorToStore({
86
+ sink: args.sink,
87
+ prefix: args.workspacePrefix,
88
+ desired: new Map(),
89
+ });
90
+ }
91
+
42
92
  /**
43
93
  * Same reservation as local materialization's `resolveWorkspaceWritePath`:
44
- * `memory/` is the shared cross-session journal, so a seed under it fails
45
- * the sync exactly as it fails a local first turn.
94
+ * `memory/` is the shared cross-session journal, so a seed or send under it
95
+ * fails the sync exactly as it fails a local first turn.
46
96
  */
47
- function seedRelPath(relPath: string): string {
48
- const rel = storeRelPath(relPath, "workspace file");
97
+ function workspaceRelPath(relPath: string): string {
98
+ let rel: string;
99
+ try {
100
+ rel = storeRelPath(relPath, "workspace file");
101
+ } catch (error) {
102
+ throw new WorkspaceFileRefusal(
103
+ error instanceof Error ? error.message : String(error)
104
+ );
105
+ }
49
106
  if (rel.split("/", 1)[0] === MEMORY_DIR_NAME) {
50
- throw new Error(
107
+ throw new WorkspaceFileRefusal(
51
108
  `"${MEMORY_DIR_NAME}" is reserved for shared session memory; refusing to sync ${relPath}`
52
109
  );
53
110
  }
54
111
  return rel;
55
112
  }
113
+
114
+ function workspaceFileBytes(
115
+ relativePath: string,
116
+ contents: WorkspaceFileBody
117
+ ): Uint8Array {
118
+ if (typeof contents === "string") {
119
+ return new TextEncoder().encode(contents);
120
+ }
121
+ if (contents.encoding === "base64") {
122
+ return new Uint8Array(Buffer.from(contents.data, "base64"));
123
+ }
124
+ throw new WorkspaceFileRefusal(
125
+ `Unsupported workspace file encoding for ${relativePath}`
126
+ );
127
+ }
@@ -80,6 +80,26 @@ export function buildCloudWorkspaceSection(dir: string): string {
80
80
  ].join("\n");
81
81
  }
82
82
 
83
+ /**
84
+ * Per-turn preamble for cloud `workspaceFiles` after they land on the
85
+ * session store prefix (see `syncSessionWorkspaceFiles`). Omitted when
86
+ * the sync did not land.
87
+ *
88
+ * Same shared Agent Store mount as authored seeds — the session path is
89
+ * replace-per-send / task-end wipe lifetime, not a separate mount ACL.
90
+ */
91
+ export function buildCloudSessionWorkspaceSection(
92
+ dir: string,
93
+ relPaths: readonly string[]
94
+ ): string {
95
+ const listed = relPaths.map((path) => `\`${path}\``).join(", ");
96
+ return [
97
+ "## Session workspace files",
98
+ "",
99
+ `This turn's workspace files live at \`${dir}\` on this machine (${listed}). Read them with your normal file tools. They sit on the same shared Agent Store mount as authored workspace seeds; the session directory is replaced on the next send for this session, and task sessions remove it when they finish.`,
100
+ ].join("\n");
101
+ }
102
+
83
103
  /** Relative path of an agent-side tool script inside the session workspace. */
84
104
  export function agentToolScriptPath(toolName: string): string {
85
105
  return `.agent-serve/tools/${toolName}.sh`;
package/src/reminders.ts CHANGED
@@ -1,30 +1,48 @@
1
1
  /**
2
- * Runtime reminders — per-session durable wakes.
2
+ * Runtime reminders — per-session durable wakes — and named handlers
3
+ * authored at `agent/reminders/<name>.ts`.
3
4
  *
4
5
  * ```ts
6
+ * import { defineReminder } from "@cursor/july/reminders";
5
7
  * import type { ReminderCreateInput } from "@cursor/july/reminders";
6
8
  *
9
+ * // agent/reminders/verdict_recheck.ts
10
+ * export default defineReminder({
11
+ * async run({ args, followup }) {
12
+ * await followup({ message: `Recheck ${String(args.headSha)}` });
13
+ * return { action: "delivered" };
14
+ * },
15
+ * });
16
+ *
17
+ * import verdictRecheck from "./reminders/verdict_recheck.js";
18
+ *
7
19
  * // Recurring ≈ setInterval; one-shot ≈ setTimeout.
8
20
  * await host.reminders?.create({
9
21
  * purpose: "ci_recheck",
10
22
  * channelId: session.channelId,
11
23
  * continuationToken: session.continuationKey!,
12
24
  * delay: "2h",
13
- * prompt: "Re-check CI. Only act if still failing.",
25
+ * handler: verdictRecheck,
26
+ * args: { headSha: "abc" },
14
27
  * });
15
28
  * ```
16
29
  *
17
30
  * Distinct from `defineSchedule` (deploy-time global cron under
18
31
  * `agent/schedules/`). Reminders are created at runtime and bound to a
19
- * channel continuation. Prefer `run` + function `until` for host policy
20
- * (silent ticks); agents use prompt-form create via tools.
32
+ * channel continuation. Prefer named `handler` + serializable `args` for
33
+ * hosted v2 durability; anonymous `run` remains on local and hosted v1.
21
34
  */
22
35
 
36
+ import { brandDefinition } from "./internal/brand.js";
37
+ import type { ReminderDefinition, ReminderHandlerConfig } from "./types.js";
38
+
23
39
  export type {
24
40
  ReminderBusyPolicy,
25
41
  ReminderCreateInput,
42
+ ReminderDefinition,
26
43
  ReminderFireContext,
27
44
  ReminderFireResult,
45
+ ReminderHandlerConfig,
28
46
  ReminderHostApi,
29
47
  ReminderInfo,
30
48
  ReminderReschedule,
@@ -33,3 +51,16 @@ export type {
33
51
  ReminderTarget,
34
52
  ReminderUntilContext,
35
53
  } from "./types.js";
54
+
55
+ /**
56
+ * Brand a named reminder handler for `agent/reminders/<name>.ts`.
57
+ * The registry name is the module stem (not a field on the definition).
58
+ */
59
+ export function defineReminder(
60
+ config: ReminderHandlerConfig
61
+ ): ReminderDefinition {
62
+ if (typeof config.run !== "function") {
63
+ throw new Error("defineReminder requires a `run` function");
64
+ }
65
+ return brandDefinition("reminder", config);
66
+ }