@cursor/july 0.1.16 → 0.1.18

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 (273) hide show
  1. package/README.md +9 -4
  2. package/dist/bin/agent-serve.js +60 -2
  3. package/dist/channels/slack/api.d.ts.map +1 -1
  4. package/dist/channels/slack/api.js +13 -3
  5. package/dist/channels/slack/auth.d.ts +7 -1
  6. package/dist/channels/slack/auth.d.ts.map +1 -1
  7. package/dist/channels/slack/auth.js +14 -0
  8. package/dist/channels/slack/blocks.d.ts +61 -0
  9. package/dist/channels/slack/blocks.d.ts.map +1 -0
  10. package/dist/channels/slack/blocks.js +90 -0
  11. package/dist/channels/slack/cursor-account.d.ts.map +1 -1
  12. package/dist/channels/slack/cursor-account.js +1 -1
  13. package/dist/channels/slack/defaults.d.ts +2 -0
  14. package/dist/channels/slack/defaults.d.ts.map +1 -1
  15. package/dist/channels/slack/defaults.js +29 -3
  16. package/dist/channels/slack/dispatch.d.ts +3 -17
  17. package/dist/channels/slack/dispatch.d.ts.map +1 -1
  18. package/dist/channels/slack/dispatch.js +4 -1
  19. package/dist/channels/slack/inbound.d.ts.map +1 -1
  20. package/dist/channels/slack/inbound.js +6 -1
  21. package/dist/channels/slack/index.d.ts +2 -0
  22. package/dist/channels/slack/index.d.ts.map +1 -1
  23. package/dist/channels/slack/index.js +2 -0
  24. package/dist/channels/slack/init.d.ts.map +1 -1
  25. package/dist/channels/slack/init.js +4 -1
  26. package/dist/channels/slack/interactive.d.ts +39 -3
  27. package/dist/channels/slack/interactive.d.ts.map +1 -1
  28. package/dist/channels/slack/interactive.js +153 -7
  29. package/dist/channels/slack/manifest.d.ts +13 -0
  30. package/dist/channels/slack/manifest.d.ts.map +1 -1
  31. package/dist/channels/slack/manifest.js +36 -14
  32. package/dist/channels/slack/message-body.d.ts +13 -0
  33. package/dist/channels/slack/message-body.d.ts.map +1 -0
  34. package/dist/channels/slack/message-body.js +82 -0
  35. package/dist/channels/slack/setup.d.ts +4 -1
  36. package/dist/channels/slack/setup.d.ts.map +1 -1
  37. package/dist/channels/slack/setup.js +16 -2
  38. package/dist/channels/slack/slack-channel.d.ts.map +1 -1
  39. package/dist/channels/slack/slack-channel.js +24 -3
  40. package/dist/channels/slack/thread-context.d.ts +0 -2
  41. package/dist/channels/slack/thread-context.d.ts.map +1 -1
  42. package/dist/channels/slack/thread-context.js +1 -65
  43. package/dist/channels/slack/types.d.ts +93 -1
  44. package/dist/channels/slack/types.d.ts.map +1 -1
  45. package/dist/connections.d.ts +4 -1
  46. package/dist/connections.d.ts.map +1 -1
  47. package/dist/connections.js +4 -1
  48. package/dist/docs/404.html +2 -2
  49. package/dist/docs/ab.html +3 -3
  50. package/dist/docs/assets/{app.DjHC-Ggf.js → app.BR5EdVSq.js} +1 -1
  51. package/dist/docs/assets/chunks/@localSearchIndexroot.BOzkeUi8.js +1 -0
  52. package/dist/docs/assets/chunks/{VPLocalSearchBox.DwviwmIq.js → VPLocalSearchBox.CNCzfUKC.js} +1 -1
  53. package/dist/docs/assets/chunks/{theme.BIpeYSon.js → theme.CwKvMSWv.js} +2 -2
  54. package/dist/docs/assets/{example-agents_security-reviewer.md.BZSNycMf.js → example-agents_security-reviewer.md.Dhj_m7_B.js} +1 -1
  55. package/dist/docs/assets/{guides_mcp-oauth.md.DqmEOQY1.js → guides_mcp-oauth.md.Dd8EgSem.js} +3 -2
  56. package/dist/docs/assets/{guides_mcp-oauth.md.DqmEOQY1.lean.js → guides_mcp-oauth.md.Dd8EgSem.lean.js} +1 -1
  57. package/dist/docs/assets/{guides_slack.md.FWale6Ip.js → guides_slack.md.D_xFKlnF.js} +1 -1
  58. package/dist/docs/assets/{reference_connections.md.CsSr3rDz.js → reference_connections.md.B9Q3TOve.js} +12 -5
  59. package/dist/docs/assets/{reference_connections.md.CsSr3rDz.lean.js → reference_connections.md.B9Q3TOve.lean.js} +1 -1
  60. package/dist/docs/building-with-agents.html +3 -3
  61. package/dist/docs/concepts.html +3 -3
  62. package/dist/docs/deployment.html +3 -3
  63. package/dist/docs/evals.html +3 -3
  64. package/dist/docs/example-agents/approval-buddy.html +3 -3
  65. package/dist/docs/example-agents/benny.html +3 -3
  66. package/dist/docs/example-agents/bugbot.html +3 -3
  67. package/dist/docs/example-agents/codebase-wiki.html +3 -3
  68. package/dist/docs/example-agents/codeowners-review.html +3 -3
  69. package/dist/docs/example-agents/concierge.html +3 -3
  70. package/dist/docs/example-agents/fsd.html +3 -3
  71. package/dist/docs/example-agents/index.html +3 -3
  72. package/dist/docs/example-agents/knowledge-base.html +3 -3
  73. package/dist/docs/example-agents/oncall.html +3 -3
  74. package/dist/docs/example-agents/security-reviewer.html +5 -5
  75. package/dist/docs/example-agents/slack-agent.html +3 -3
  76. package/dist/docs/example-agents/weather-agent.html +3 -3
  77. package/dist/docs/guides/agent-to-agent.html +3 -3
  78. package/dist/docs/guides/cloud-runtime.html +3 -3
  79. package/dist/docs/guides/github.html +3 -3
  80. package/dist/docs/guides/human-in-the-loop.html +3 -3
  81. package/dist/docs/guides/mcp-oauth.html +6 -5
  82. package/dist/docs/guides/slack.html +5 -5
  83. package/dist/docs/guides/webhooks.html +3 -3
  84. package/dist/docs/hashmap.json +1 -1
  85. package/dist/docs/hillclimbing.html +3 -3
  86. package/dist/docs/index.html +3 -3
  87. package/dist/docs/quickstart.html +3 -3
  88. package/dist/docs/reference/agent-config.html +3 -3
  89. package/dist/docs/reference/channels.html +3 -3
  90. package/dist/docs/reference/cli.html +3 -3
  91. package/dist/docs/reference/connections.html +14 -7
  92. package/dist/docs/reference/hooks.html +3 -3
  93. package/dist/docs/reference/http-api.html +3 -3
  94. package/dist/docs/reference/instructions.html +3 -3
  95. package/dist/docs/reference/playground.html +3 -3
  96. package/dist/docs/reference/project-layout.html +3 -3
  97. package/dist/docs/reference/prompt.html +3 -3
  98. package/dist/docs/reference/schedules.html +3 -3
  99. package/dist/docs/reference/sessions.html +3 -3
  100. package/dist/docs/reference/skills.html +3 -3
  101. package/dist/docs/reference/subagents.html +3 -3
  102. package/dist/docs/reference/tools.html +3 -3
  103. package/dist/docs/scaffolding-agents.html +3 -3
  104. package/dist/docs/storage.html +3 -3
  105. package/dist/docs/troubleshooting.html +3 -3
  106. package/dist/evals/assertions.d.ts +190 -0
  107. package/dist/evals/assertions.d.ts.map +1 -0
  108. package/dist/evals/assertions.js +461 -0
  109. package/dist/evals/expect.d.ts +95 -0
  110. package/dist/evals/expect.d.ts.map +1 -0
  111. package/dist/evals/expect.js +194 -0
  112. package/dist/evals/judge.d.ts +133 -0
  113. package/dist/evals/judge.d.ts.map +1 -0
  114. package/dist/evals/judge.js +228 -0
  115. package/dist/evals/loaders.d.ts +51 -0
  116. package/dist/evals/loaders.d.ts.map +1 -0
  117. package/dist/evals/loaders.js +124 -0
  118. package/dist/evals/matchers.d.ts +95 -0
  119. package/dist/evals/matchers.d.ts.map +1 -0
  120. package/dist/evals/matchers.js +145 -0
  121. package/dist/evals/reporters.d.ts +70 -0
  122. package/dist/evals/reporters.d.ts.map +1 -0
  123. package/dist/evals/reporters.js +221 -0
  124. package/dist/evals/results.d.ts +103 -0
  125. package/dist/evals/results.d.ts.map +1 -0
  126. package/dist/evals/results.js +28 -0
  127. package/dist/evals/run-facts.d.ts +77 -0
  128. package/dist/evals/run-facts.d.ts.map +1 -0
  129. package/dist/evals/run-facts.js +187 -0
  130. package/dist/evals.d.ts +118 -31
  131. package/dist/evals.d.ts.map +1 -1
  132. package/dist/evals.js +46 -12
  133. package/dist/index.d.ts +1 -1
  134. package/dist/index.d.ts.map +1 -1
  135. package/dist/index.js +1 -1
  136. package/dist/internal/builtin-tools/index.d.ts +10 -0
  137. package/dist/internal/builtin-tools/index.d.ts.map +1 -0
  138. package/dist/internal/builtin-tools/index.js +27 -0
  139. package/dist/internal/builtin-tools/reminders.d.ts +9 -0
  140. package/dist/internal/builtin-tools/reminders.d.ts.map +1 -0
  141. package/dist/internal/builtin-tools/reminders.js +138 -0
  142. package/dist/internal/cli-ax.d.ts +27 -0
  143. package/dist/internal/cli-ax.d.ts.map +1 -1
  144. package/dist/internal/cli-ax.js +90 -12
  145. package/dist/internal/cli-mcp.d.ts +8 -0
  146. package/dist/internal/cli-mcp.d.ts.map +1 -1
  147. package/dist/internal/cli-mcp.js +98 -0
  148. package/dist/internal/cli-slack.d.ts +29 -1
  149. package/dist/internal/cli-slack.d.ts.map +1 -1
  150. package/dist/internal/cli-slack.js +799 -13
  151. package/dist/internal/cursor-event-relay.d.ts +30 -0
  152. package/dist/internal/cursor-event-relay.d.ts.map +1 -1
  153. package/dist/internal/cursor-event-relay.js +37 -0
  154. package/dist/internal/deploy-client.d.ts +7 -0
  155. package/dist/internal/deploy-client.d.ts.map +1 -1
  156. package/dist/internal/deploy-client.js +9 -0
  157. package/dist/internal/discovery.d.ts.map +1 -1
  158. package/dist/internal/discovery.js +70 -9
  159. package/dist/internal/distribution.d.ts.map +1 -1
  160. package/dist/internal/distribution.js +2 -0
  161. package/dist/internal/eval-judge-model.d.ts +59 -0
  162. package/dist/internal/eval-judge-model.d.ts.map +1 -0
  163. package/dist/internal/eval-judge-model.js +131 -0
  164. package/dist/internal/eval-run-store.d.ts.map +1 -1
  165. package/dist/internal/eval-run-store.js +12 -20
  166. package/dist/internal/eval-runner.d.ts +31 -25
  167. package/dist/internal/eval-runner.d.ts.map +1 -1
  168. package/dist/internal/eval-runner.js +329 -126
  169. package/dist/internal/evals-client.d.ts.map +1 -1
  170. package/dist/internal/evals-client.js +4 -1
  171. package/dist/internal/local-env.d.ts +8 -0
  172. package/dist/internal/local-env.d.ts.map +1 -0
  173. package/dist/internal/local-env.js +47 -0
  174. package/dist/internal/playground/static.d.ts +3 -0
  175. package/dist/internal/playground/static.d.ts.map +1 -1
  176. package/dist/internal/playground/static.js +3 -0
  177. package/dist/internal/reminder-runner.d.ts +7 -0
  178. package/dist/internal/reminder-runner.d.ts.map +1 -1
  179. package/dist/internal/reminder-runner.js +21 -0
  180. package/dist/internal/run-client.d.ts +15 -0
  181. package/dist/internal/run-client.d.ts.map +1 -1
  182. package/dist/internal/run-client.js +3 -2
  183. package/dist/internal/sdk-runner.d.ts +51 -1
  184. package/dist/internal/sdk-runner.d.ts.map +1 -1
  185. package/dist/internal/sdk-runner.js +176 -36
  186. package/dist/internal/server.d.ts.map +1 -1
  187. package/dist/internal/server.js +11 -1
  188. package/dist/internal/session-engine.d.ts +18 -5
  189. package/dist/internal/session-engine.d.ts.map +1 -1
  190. package/dist/internal/session-engine.js +81 -19
  191. package/dist/internal/slack-provision-client.d.ts +133 -0
  192. package/dist/internal/slack-provision-client.d.ts.map +1 -0
  193. package/dist/internal/slack-provision-client.js +286 -0
  194. package/dist/playground/assets/cursor-icons-16-f_W_ogc-.woff2 +0 -0
  195. package/dist/playground/assets/index-DRjS5klQ.js +85 -0
  196. package/dist/playground/assets/index-TCDs-UhZ.css +1 -0
  197. package/dist/playground/index.html +2 -2
  198. package/dist/types.d.ts +98 -4
  199. package/dist/types.d.ts.map +1 -1
  200. package/dist/types.js +26 -0
  201. package/docs/example-agents/security-reviewer.md +1 -1
  202. package/docs/guides/mcp-oauth.md +10 -5
  203. package/docs/guides/slack.md +3 -2
  204. package/docs/reference/connections.md +28 -18
  205. package/package.json +20 -2
  206. package/skills/create-agent/SKILL.md +15 -7
  207. package/skills/debug/SKILL.md +3 -1
  208. package/skills/mcp-auth/SKILL.md +1 -1
  209. package/skills/setup-slack/SKILL.md +177 -11
  210. package/src/bin/agent-serve.ts +63 -2
  211. package/src/channels/slack/api.ts +16 -3
  212. package/src/channels/slack/auth.ts +16 -1
  213. package/src/channels/slack/blocks.ts +139 -0
  214. package/src/channels/slack/cursor-account.ts +1 -0
  215. package/src/channels/slack/defaults.ts +32 -4
  216. package/src/channels/slack/dispatch.ts +6 -19
  217. package/src/channels/slack/inbound.ts +6 -1
  218. package/src/channels/slack/index.ts +2 -0
  219. package/src/channels/slack/init.ts +3 -0
  220. package/src/channels/slack/interactive.ts +184 -19
  221. package/src/channels/slack/manifest.ts +37 -6
  222. package/src/channels/slack/message-body.ts +90 -0
  223. package/src/channels/slack/setup.ts +16 -2
  224. package/src/channels/slack/slack-channel.ts +37 -6
  225. package/src/channels/slack/thread-context.ts +1 -69
  226. package/src/channels/slack/types.ts +105 -1
  227. package/src/connections.ts +4 -1
  228. package/src/evals/assertions.ts +704 -0
  229. package/src/evals/expect.ts +272 -0
  230. package/src/evals/judge.ts +346 -0
  231. package/src/evals/loaders.ts +113 -0
  232. package/src/evals/matchers.ts +246 -0
  233. package/src/evals/reporters.ts +274 -0
  234. package/src/evals/results.ts +132 -0
  235. package/src/evals/run-facts.ts +274 -0
  236. package/src/evals.ts +205 -42
  237. package/src/index.ts +2 -0
  238. package/src/internal/builtin-tools/index.ts +40 -0
  239. package/src/internal/builtin-tools/reminders.ts +158 -0
  240. package/src/internal/cli-ax.ts +134 -15
  241. package/src/internal/cli-mcp.ts +118 -5
  242. package/src/internal/cli-slack.ts +1017 -15
  243. package/src/internal/cursor-event-relay.ts +67 -0
  244. package/src/internal/deploy-client.ts +10 -0
  245. package/src/internal/discovery.ts +83 -5
  246. package/src/internal/distribution.ts +2 -0
  247. package/src/internal/eval-judge-model.ts +148 -0
  248. package/src/internal/eval-run-store.ts +10 -14
  249. package/src/internal/eval-runner.ts +460 -214
  250. package/src/internal/evals-client.ts +5 -0
  251. package/src/internal/local-env.ts +53 -0
  252. package/src/internal/playground/static.ts +3 -0
  253. package/src/internal/reminder-runner.ts +30 -0
  254. package/src/internal/run-client.ts +17 -1
  255. package/src/internal/sdk-runner.ts +234 -35
  256. package/src/internal/server.ts +12 -0
  257. package/src/internal/session-engine.ts +103 -21
  258. package/src/internal/slack-provision-client.ts +442 -0
  259. package/src/types.ts +125 -4
  260. package/dist/channels/slack/post-update-delivery.d.ts +0 -85
  261. package/dist/channels/slack/post-update-delivery.d.ts.map +0 -1
  262. package/dist/docs/assets/chunks/@localSearchIndexroot.DZtu0ZIc.js +0 -1
  263. package/dist/internal/json-dir-store.d.ts +0 -32
  264. package/dist/internal/json-dir-store.d.ts.map +0 -1
  265. package/dist/internal/persistence-coordinator.d.ts +0 -127
  266. package/dist/internal/persistence-coordinator.d.ts.map +0 -1
  267. package/dist/persistence.d.ts +0 -184
  268. package/dist/persistence.d.ts.map +0 -1
  269. package/dist/playground/assets/cursor-icons-16-CQ50JpfO.woff2 +0 -0
  270. package/dist/playground/assets/index-Bwl4Onx4.js +0 -85
  271. package/dist/playground/assets/index-ByV4nfch.css +0 -1
  272. /package/dist/docs/assets/{example-agents_security-reviewer.md.BZSNycMf.lean.js → example-agents_security-reviewer.md.Dhj_m7_B.lean.js} +0 -0
  273. /package/dist/docs/assets/{guides_slack.md.FWale6Ip.lean.js → guides_slack.md.D_xFKlnF.lean.js} +0 -0
@@ -39,6 +39,7 @@ import {
39
39
  type PendingApproval,
40
40
  type ReceiveInput,
41
41
  type ResolvedAgent,
42
+ type RunnerSubagent,
42
43
  type RunnerTurnRequest,
43
44
  type SendMessageFn,
44
45
  type SendMessageOptions,
@@ -957,13 +958,18 @@ export class SessionEngine {
957
958
  }
958
959
 
959
960
  /**
960
- * Local harness cwd for a session: per-send override, else
961
- * `<agent.local.cwd>/<sessionId>`, else the durable session workspace.
961
+ * Local harness cwd for a session: per-send override, else the shared
962
+ * `agent.local.workspaceDir`, else `<agent.local.cwd>/<sessionId>`, else
963
+ * the durable session workspace.
962
964
  */
963
965
  harnessCwd(record: SessionRecord): string {
964
966
  if (record.workspaceOverride !== undefined) {
965
967
  return record.workspaceOverride;
966
968
  }
969
+ const shared = this.project.agent.local?.workspaceDir;
970
+ if (shared !== undefined) {
971
+ return shared;
972
+ }
967
973
  const agentCwd = this.project.agent.local?.cwd;
968
974
  if (agentCwd !== undefined) {
969
975
  return join(agentCwd, record.sessionId);
@@ -972,12 +978,64 @@ export class SessionEngine {
972
978
  }
973
979
 
974
980
  /**
975
- * Directory that receives AGENTS.md / skills / agent tools. When the
976
- * harness cwd is a one-off override (e.g. PR worktree), scaffold stays in
977
- * the durable session workspace; otherwise scaffold lands in the harness.
981
+ * True when the harness cwd is a directory the session does not own — a
982
+ * per-send override (e.g. a PR worktree) or a workspace shared by every
983
+ * session. Agent scaffold has to stay out of those, so it lands in the
984
+ * durable session workspace and the instructions travel inline instead.
985
+ */
986
+ private hasBorrowedWorkspace(record: SessionRecord): boolean {
987
+ return (
988
+ record.workspaceOverride !== undefined ||
989
+ this.project.agent.local?.workspaceDir !== undefined
990
+ );
991
+ }
992
+
993
+ /**
994
+ * Resolve the shared harness workspace during startup, so the first session
995
+ * is not the one that waits for it. Only possible with
996
+ * `local.workspaceDir`: every other layout gives a session its own cwd,
997
+ * which is unknowable until that session exists.
998
+ *
999
+ * Best effort — warming is an optimization, and a turn rebuilds on its own.
978
1000
  */
1001
+ async prewarmSharedWorkspace(): Promise<void> {
1002
+ const agent = this.project.agent;
1003
+ const workspaceDir = agent.local?.workspaceDir;
1004
+ if (
1005
+ workspaceDir === undefined ||
1006
+ agent.runtime !== "local" ||
1007
+ this.runner.prewarm === undefined
1008
+ ) {
1009
+ return;
1010
+ }
1011
+ const startedAt = Date.now();
1012
+ try {
1013
+ const outcome = await this.runner.prewarm({
1014
+ workspaceDir,
1015
+ runnerStateDir: join(this.stateRoot, "runner"),
1016
+ sandbox: agent.local?.sandbox === true,
1017
+ mcpServers: this.buildMcpServers("local"),
1018
+ subagents: buildRunnerSubagents(agent),
1019
+ ...(agent.model === undefined ? {} : { model: agent.model }),
1020
+ });
1021
+ // Report what happened rather than that the call returned: a runner
1022
+ // that cannot warm resolves cleanly, and logging success there would
1023
+ // tell an operator the workspace is hot when nothing touched it.
1024
+ this.logger(
1025
+ outcome.warmed
1026
+ ? `[agentkit] warmed ${workspaceDir} in ${Date.now() - startedAt}ms`
1027
+ : `[agentkit] did not warm ${workspaceDir}: ${outcome.reason}`
1028
+ );
1029
+ } catch (error) {
1030
+ this.logger(
1031
+ `[agentkit] could not warm ${workspaceDir}: ${describeError(error)}`
1032
+ );
1033
+ }
1034
+ }
1035
+
1036
+ /** Directory that receives AGENTS.md / skills / agent tools. */
979
1037
  private scaffoldDir(record: SessionRecord): string {
980
- if (record.workspaceOverride !== undefined) {
1038
+ if (this.hasBorrowedWorkspace(record)) {
981
1039
  return this.sessions.workspaceDir(record.sessionId);
982
1040
  }
983
1041
  return this.harnessCwd(record);
@@ -1053,13 +1111,13 @@ export class SessionEngine {
1053
1111
  });
1054
1112
  }
1055
1113
  // The harness only loads the scaffolded AGENTS.md when the session
1056
- // workspace is its own project root. With a per-session cwd override
1057
- // (PR worktree) the scaffold lands elsewhere, and a workspace nested
1058
- // inside a git repo re-roots harness rules at that repo — in both
1059
- // cases deliver instructions inline on the first turn instead.
1114
+ // workspace is its own project root. When the harness cwd is borrowed
1115
+ // (PR worktree, shared checkout) the scaffold lands elsewhere, and a
1116
+ // workspace nested inside a git repo re-roots harness rules at that
1117
+ // repo — in both cases deliver instructions inline on the first turn.
1060
1118
  const instructionsInvisibleToHarness =
1061
1119
  isFirstTurn &&
1062
- (record.workspaceOverride !== undefined ||
1120
+ (this.hasBorrowedWorkspace(record) ||
1063
1121
  (await isNestedInGitRepo(harnessCwd)));
1064
1122
  const request = this.buildTurnRequest(
1065
1123
  record,
@@ -1201,10 +1259,10 @@ export class SessionEngine {
1201
1259
  turnPrompt = `${preamble}\n\n${turnPrompt}`;
1202
1260
  } else if (instructionsInvisibleToHarness) {
1203
1261
  // Local workspace whose AGENTS.md the harness will not load. With a
1204
- // per-session harness cwd override, agent tool scripts live only in
1205
- // the durable scaffold dir — embed bodies like cloud. Otherwise the
1206
- // catalog can reference on-disk paths under the harness cwd.
1207
- const includeScripts = record.workspaceOverride !== undefined;
1262
+ // borrowed harness cwd, agent tool scripts live only in the durable
1263
+ // scaffold dir — embed bodies like cloud. Otherwise the catalog can
1264
+ // reference on-disk paths under the harness cwd.
1265
+ const includeScripts = this.hasBorrowedWorkspace(record);
1208
1266
  const preamble = buildAgentsMdContent(agent, { includeScripts });
1209
1267
  turnPrompt = `${preamble}\n\n${turnPrompt}`;
1210
1268
  }
@@ -1215,6 +1273,7 @@ export class SessionEngine {
1215
1273
  ? {}
1216
1274
  : { sdkAgentId: record.sdkAgentId }),
1217
1275
  workspaceDir: this.harnessCwd(record),
1276
+ sandbox: this.project.agent.local?.sandbox === true,
1218
1277
  runnerStateDir: join(this.stateRoot, "runner"),
1219
1278
  prompt: turnPrompt,
1220
1279
  isFirstTurn,
@@ -1224,12 +1283,7 @@ export class SessionEngine {
1224
1283
  customTools:
1225
1284
  runtime === "local" ? this.buildCustomTools(record, turnId, emit) : {},
1226
1285
  mcpServers: this.buildMcpServers(runtime),
1227
- subagents: agent.subagents.map((subagent) => ({
1228
- name: subagent.name,
1229
- description: subagent.description ?? subagent.name,
1230
- prompt: subagentPrompt(subagent),
1231
- ...(subagent.model === undefined ? {} : { model: subagent.model }),
1232
- })),
1286
+ subagents: buildRunnerSubagents(agent),
1233
1287
  onUpdate,
1234
1288
  signal,
1235
1289
  // Local follow-ups expire any lingering active run so a preempted turn
@@ -2094,10 +2148,24 @@ export class SessionEngine {
2094
2148
  sessionId: string,
2095
2149
  token: string | null
2096
2150
  ): Promise<void> {
2151
+ const previous = await this.sessions.get(sessionId);
2097
2152
  await this.updateRecord(sessionId, (current) => ({
2098
2153
  ...current,
2099
2154
  continuationKey: token,
2100
2155
  }));
2156
+ // Keep armed reminders addressed at the conversation across rotation
2157
+ // (the built-in HTTP channel rotates after every follow-up).
2158
+ if (
2159
+ previous?.continuationKey != null &&
2160
+ token !== null &&
2161
+ token !== previous.continuationKey
2162
+ ) {
2163
+ await this.reminderApi?.rekeyContinuation(
2164
+ previous.channelId,
2165
+ previous.continuationKey,
2166
+ token
2167
+ );
2168
+ }
2101
2169
  }
2102
2170
 
2103
2171
  isTurnRunning(sessionId: string): boolean {
@@ -2509,6 +2577,20 @@ function subagentPrompt(subagent: ResolvedAgent): string {
2509
2577
  return subagent.description ?? subagent.name;
2510
2578
  }
2511
2579
 
2580
+ /**
2581
+ * Subagents as the runner takes them. Shared by turns and by workspace
2582
+ * warming, which has to describe the same agent a turn would to reach the
2583
+ * executor that turn will run on.
2584
+ */
2585
+ function buildRunnerSubagents(agent: ResolvedAgent): RunnerSubagent[] {
2586
+ return agent.subagents.map((subagent) => ({
2587
+ name: subagent.name,
2588
+ description: subagent.description ?? subagent.name,
2589
+ prompt: subagentPrompt(subagent),
2590
+ ...(subagent.model === undefined ? {} : { model: subagent.model }),
2591
+ }));
2592
+ }
2593
+
2512
2594
  function describeError(error: unknown): string {
2513
2595
  return error instanceof Error ? error.message : String(error);
2514
2596
  }
@@ -0,0 +1,442 @@
1
+ /**
2
+ * Thin client for the Agent Serve Slack provisioning API
3
+ * (`/internal/agent-serve/slack` on the Cursor backend — "Add to Slack").
4
+ * Pure request/response + error mapping — no console I/O, no polling
5
+ * loops; rendering lives in `cli-slack.ts`.
6
+ *
7
+ * Auth is the user API key sent directly as the bearer (the routes are
8
+ * `wrapUserApiRoute` on the backend); there is no token exchange here.
9
+ * The endpoints are gated on the `agent_serve_slack_provisioning` Statsig
10
+ * gate and 404 when it is off — the error mapping says so instead of
11
+ * "not found". Expected provisioning failures carry a stable JSON `code`
12
+ * (`not_connected`, `insufficient_scopes`, `manager_reauth_required`,
13
+ * `app_conflict`, `app_cap_reached`, `ratelimited`, `invalid_app_id`, …)
14
+ * which flows through on {@link SlackProvisionApiError.code} so the CLI
15
+ * can branch.
16
+ */
17
+
18
+ import { z } from "zod";
19
+
20
+ export type SlackAppEnv = "dev" | "prod";
21
+
22
+ /** One manager-connected Slack workspace from `GET /status`. */
23
+ export interface SlackConnectedWorkspace {
24
+ slackTeamId: string;
25
+ /** Workspace name; absent on older backends, null on pre-existing rows. */
26
+ slackTeamName?: string | null;
27
+ slackUserId: string;
28
+ connectedAt: string;
29
+ }
30
+
31
+ /** One provisioned managed Slack app from `GET /status` (no token material). */
32
+ export interface SlackManagedAppInfo {
33
+ slackAppId: string;
34
+ slackTeamId: string;
35
+ slug: string;
36
+ env: string;
37
+ installStatus: string;
38
+ createdAt: string;
39
+ updatedAt: string;
40
+ }
41
+
42
+ export interface SlackProvisionStatus {
43
+ workspaces: SlackConnectedWorkspace[];
44
+ apps: SlackManagedAppInfo[];
45
+ }
46
+
47
+ export type SlackAppIconContentType = "image/png" | "image/jpeg" | "image/gif";
48
+
49
+ /** Icon source for `setAppIcon`: an https image URL or a base64 file. */
50
+ export type SlackAppIconInput =
51
+ | { url: string }
52
+ | { base64: string; contentType: SlackAppIconContentType };
53
+
54
+ /**
55
+ * Outcome of `POST /apps`. Credentials are returned exactly once and never
56
+ * persisted server-side — the caller owns storing them. `appToken` can be
57
+ * absent when Slack's install response omitted the app-level token.
58
+ * `oauthAuthorizeUrl` is only minted on a fresh app create, so an update
59
+ * that lands in admin approval omits it.
60
+ */
61
+ export type SlackAppProvisionResult =
62
+ | {
63
+ status: "installed";
64
+ appId: string;
65
+ botToken: string;
66
+ appToken?: string;
67
+ /** Present when the provision requested an icon (best-effort outcome). */
68
+ iconStatus?: "set" | "failed";
69
+ }
70
+ | {
71
+ status: "pending_admin_approval";
72
+ appId: string;
73
+ oauthAuthorizeUrl?: string;
74
+ iconStatus?: "set" | "failed";
75
+ };
76
+
77
+ export class SlackProvisionApiError extends Error {
78
+ readonly status: number;
79
+ /** Stable backend error code (`not_connected`, `ratelimited`, …). */
80
+ readonly code?: string;
81
+ /** Seconds to wait before retrying (from 429 responses). */
82
+ readonly retryAfterSeconds?: number;
83
+ /** Drifted manifest paths (from `manifest_drift` 409 bodies). */
84
+ readonly changedPaths?: string[];
85
+
86
+ constructor(
87
+ message: string,
88
+ options: {
89
+ status: number;
90
+ code?: string;
91
+ retryAfterSeconds?: number;
92
+ changedPaths?: string[];
93
+ }
94
+ ) {
95
+ super(message);
96
+ this.name = "SlackProvisionApiError";
97
+ this.status = options.status;
98
+ this.code = options.code;
99
+ this.retryAfterSeconds = options.retryAfterSeconds;
100
+ this.changedPaths = options.changedPaths;
101
+ }
102
+ }
103
+
104
+ // ============================================================================
105
+ // Response shapes (backend agentServeSlackEndpoints route bodies).
106
+ // Interfaces are hand-written (the package compiles with
107
+ // --isolatedDeclarations, so exported types cannot be z.infer of an
108
+ // unannotated schema); the zod schemas below stay module-internal.
109
+ // ============================================================================
110
+
111
+ // All response schemas are `.passthrough()` so unknown fields from a newer
112
+ // backend flow through untouched instead of breaking the parse.
113
+ const statusResponseSchema = z
114
+ .object({
115
+ workspaces: z.array(
116
+ z
117
+ .object({
118
+ slackTeamId: z.string(),
119
+ slackTeamName: z.string().nullish(),
120
+ slackUserId: z.string(),
121
+ connectedAt: z.string(),
122
+ })
123
+ .passthrough()
124
+ ),
125
+ apps: z.array(
126
+ z
127
+ .object({
128
+ slackAppId: z.string(),
129
+ slackTeamId: z.string(),
130
+ slug: z.string(),
131
+ env: z.string(),
132
+ installStatus: z.string(),
133
+ createdAt: z.string(),
134
+ updatedAt: z.string(),
135
+ })
136
+ .passthrough()
137
+ ),
138
+ })
139
+ .passthrough();
140
+
141
+ const connectResponseSchema = z
142
+ .object({
143
+ url: z.string().min(1),
144
+ })
145
+ .passthrough();
146
+
147
+ const provisionResponseSchema = z.discriminatedUnion("status", [
148
+ z
149
+ .object({
150
+ status: z.literal("installed"),
151
+ appId: z.string(),
152
+ botToken: z.string(),
153
+ appToken: z.string().optional(),
154
+ iconStatus: z.enum(["set", "failed"]).optional(),
155
+ })
156
+ .passthrough(),
157
+ z
158
+ .object({
159
+ status: z.literal("pending_admin_approval"),
160
+ appId: z.string(),
161
+ oauthAuthorizeUrl: z.string().optional(),
162
+ iconStatus: z.enum(["set", "failed"]).optional(),
163
+ })
164
+ .passthrough(),
165
+ ]);
166
+
167
+ const deleteResponseSchema = z
168
+ .object({
169
+ deleted: z.boolean(),
170
+ })
171
+ .passthrough();
172
+
173
+ const setIconResponseSchema = z
174
+ .object({
175
+ ok: z.boolean(),
176
+ })
177
+ .passthrough();
178
+
179
+ // ============================================================================
180
+ // Client
181
+ // ============================================================================
182
+
183
+ export interface AgentServeSlackClientOptions {
184
+ backendUrl: string;
185
+ apiKey: string;
186
+ /** Test seam; defaults to global fetch. */
187
+ fetchImpl?: typeof fetch;
188
+ }
189
+
190
+ export class AgentServeSlackClient {
191
+ private readonly backendUrl: string;
192
+ private readonly apiKey: string;
193
+ private readonly fetchImpl: typeof fetch;
194
+
195
+ constructor(options: AgentServeSlackClientOptions) {
196
+ this.backendUrl = options.backendUrl.endsWith("/")
197
+ ? options.backendUrl.slice(0, -1)
198
+ : options.backendUrl;
199
+ this.apiKey = options.apiKey;
200
+ this.fetchImpl = options.fetchImpl ?? fetch;
201
+ }
202
+
203
+ /** Manager-connected workspaces + the managed-app inventory. */
204
+ async status(teamId: number): Promise<SlackProvisionStatus> {
205
+ const raw = await this.request({
206
+ method: "GET",
207
+ path: `/internal/agent-serve/slack/status?teamId=${teamId}`,
208
+ context: { verb: "read Slack provisioning status" },
209
+ });
210
+ return statusResponseSchema.parse(raw);
211
+ }
212
+
213
+ /**
214
+ * Mint the browser consent URL for the Slack manager grant. After the
215
+ * user approves it, the workspace shows up in {@link status}.
216
+ */
217
+ async connect(teamId: number): Promise<string> {
218
+ const raw = await this.request({
219
+ method: "POST",
220
+ path: "/internal/agent-serve/slack/connect",
221
+ body: { teamId },
222
+ context: { verb: "start the Slack workspace connect" },
223
+ });
224
+ return connectResponseSchema.parse(raw).url;
225
+ }
226
+
227
+ /**
228
+ * Provision (create or update) and install one managed Slack app for
229
+ * (slackTeamId, slug, env). Expected failures throw
230
+ * {@link SlackProvisionApiError} with a stable `code`.
231
+ */
232
+ async provisionApp(args: {
233
+ teamId: number;
234
+ slackTeamId: string;
235
+ slug: string;
236
+ env: SlackAppEnv;
237
+ /** Full Slack app manifest JSON string. */
238
+ manifest: string;
239
+ /** Icon URL, applied best-effort by the backend after the install. */
240
+ icon?: { url: string };
241
+ /** Overwrite manifest edits made in Slack (skips the drift check). */
242
+ force?: boolean;
243
+ }): Promise<SlackAppProvisionResult> {
244
+ const raw = await this.request({
245
+ method: "POST",
246
+ path: "/internal/agent-serve/slack/apps",
247
+ body: {
248
+ teamId: args.teamId,
249
+ slackTeamId: args.slackTeamId,
250
+ slug: args.slug,
251
+ env: args.env,
252
+ manifest: args.manifest,
253
+ icon: args.icon,
254
+ force: args.force,
255
+ },
256
+ context: { verb: "provision the Slack app" },
257
+ });
258
+ return provisionResponseSchema.parse(raw);
259
+ }
260
+
261
+ /** Delete the Slack app and its inventory row. */
262
+ async deleteApp(args: { teamId: number; slackAppId: string }): Promise<void> {
263
+ const raw = await this.request({
264
+ method: "DELETE",
265
+ path: `/internal/agent-serve/slack/apps/${encodeURIComponent(args.slackAppId)}?teamId=${args.teamId}`,
266
+ context: { verb: "delete the Slack app" },
267
+ });
268
+ deleteResponseSchema.parse(raw);
269
+ }
270
+
271
+ /** Set the app's icon: an https image URL or a base64 png/jpeg/gif ≤512KB. */
272
+ async setAppIcon(args: {
273
+ teamId: number;
274
+ slackAppId: string;
275
+ icon: SlackAppIconInput;
276
+ }): Promise<void> {
277
+ const raw = await this.request({
278
+ method: "POST",
279
+ path: `/internal/agent-serve/slack/apps/${encodeURIComponent(args.slackAppId)}/icon`,
280
+ body: { teamId: args.teamId, icon: args.icon },
281
+ context: { verb: "set the Slack app icon" },
282
+ });
283
+ setIconResponseSchema.parse(raw);
284
+ }
285
+
286
+ private async request(args: {
287
+ method: "GET" | "POST" | "DELETE";
288
+ path: string;
289
+ body?: unknown;
290
+ context: { verb: string };
291
+ }): Promise<unknown> {
292
+ const headers: Record<string, string> = {
293
+ accept: "application/json",
294
+ authorization: `Bearer ${this.apiKey}`,
295
+ };
296
+ if (args.body !== undefined) {
297
+ headers["content-type"] = "application/json";
298
+ }
299
+ let response: Response;
300
+ try {
301
+ response = await this.fetchImpl(`${this.backendUrl}${args.path}`, {
302
+ method: args.method,
303
+ headers,
304
+ body: args.body === undefined ? undefined : JSON.stringify(args.body),
305
+ signal: AbortSignal.timeout(60_000),
306
+ });
307
+ } catch (error) {
308
+ throw new SlackProvisionApiError(
309
+ `Could not reach the Cursor backend at ${this.backendUrl}: ${
310
+ error instanceof Error ? error.message : String(error)
311
+ }`,
312
+ { status: 0 }
313
+ );
314
+ }
315
+ const text = await response.text();
316
+ if (!response.ok) {
317
+ throw mapErrorResponse(response, text, args.context);
318
+ }
319
+ try {
320
+ return JSON.parse(text) as unknown;
321
+ } catch {
322
+ throw new SlackProvisionApiError(
323
+ `The Cursor backend returned a non-JSON response to ${args.context.verb}.`,
324
+ { status: response.status }
325
+ );
326
+ }
327
+ }
328
+ }
329
+
330
+ // ============================================================================
331
+ // Error mapping (HTTP → actionable human messages, stable codes kept)
332
+ // ============================================================================
333
+
334
+ function parseErrorBody(bodyText: string): {
335
+ error?: string;
336
+ code?: string;
337
+ retryAfterSeconds?: number;
338
+ changedPaths?: string[];
339
+ } {
340
+ let parsed: {
341
+ error?: unknown;
342
+ code?: unknown;
343
+ retryAfterSeconds?: unknown;
344
+ changedPaths?: unknown;
345
+ };
346
+ try {
347
+ parsed = JSON.parse(bodyText) as typeof parsed;
348
+ } catch {
349
+ return {};
350
+ }
351
+ return {
352
+ error:
353
+ typeof parsed.error === "string" && parsed.error !== ""
354
+ ? parsed.error
355
+ : undefined,
356
+ code:
357
+ typeof parsed.code === "string" && parsed.code !== ""
358
+ ? parsed.code
359
+ : undefined,
360
+ retryAfterSeconds:
361
+ typeof parsed.retryAfterSeconds === "number"
362
+ ? parsed.retryAfterSeconds
363
+ : undefined,
364
+ changedPaths:
365
+ Array.isArray(parsed.changedPaths) &&
366
+ parsed.changedPaths.every(
367
+ (path): path is string => typeof path === "string"
368
+ )
369
+ ? parsed.changedPaths
370
+ : undefined,
371
+ };
372
+ }
373
+
374
+ function mapErrorResponse(
375
+ response: Response,
376
+ bodyText: string,
377
+ context: { verb: string }
378
+ ): SlackProvisionApiError {
379
+ const body = parseErrorBody(bodyText);
380
+ const serverError = body.error;
381
+ switch (response.status) {
382
+ case 401:
383
+ return new SlackProvisionApiError(
384
+ "The Cursor API key was rejected. Run `agentkit login` again or update CURSOR_API_KEY.",
385
+ { status: 401 }
386
+ );
387
+ case 403:
388
+ return new SlackProvisionApiError(
389
+ `${serverError ?? "Not authorized"} — Slack provisioning needs team-admin permission and the cloud-agent entitlement on that team.`,
390
+ { status: 403 }
391
+ );
392
+ case 404: {
393
+ // Fastify's route-not-found envelope means we hit the wrong host
394
+ // (api2 vs api.cursor.com), not a closed gate.
395
+ if (/Route \S+ not found/i.test(bodyText)) {
396
+ return new SlackProvisionApiError(
397
+ `Agent Serve Slack API not found on this host — use the default api.cursor.com (unset CURSOR_API_BASE_URL), or point CURSOR_API_BASE_URL at a backend that mounts /internal/agent-serve/*.`,
398
+ { status: 404 }
399
+ );
400
+ }
401
+ // The gate-off body is exactly {error: "Not found"}; anything else
402
+ // (e.g. "App not found" on delete) is a real not-found to surface.
403
+ if (serverError !== undefined && serverError !== "Not found") {
404
+ return new SlackProvisionApiError(serverError, {
405
+ status: 404,
406
+ code: body.code,
407
+ });
408
+ }
409
+ return new SlackProvisionApiError(
410
+ `Not found — Cursor's "Add to Slack" provisioning (the agent_serve_slack_provisioning feature gate) is not enabled for this account yet.`,
411
+ { status: 404 }
412
+ );
413
+ }
414
+ case 429: {
415
+ const retryAfterHeader = response.headers.get("retry-after");
416
+ const retryAfterSeconds =
417
+ retryAfterHeader !== null && /^\d+$/.test(retryAfterHeader)
418
+ ? Number(retryAfterHeader)
419
+ : body.retryAfterSeconds;
420
+ return new SlackProvisionApiError(
421
+ `${serverError ?? "Rate limited"}${
422
+ retryAfterSeconds === undefined
423
+ ? ""
424
+ : ` — retry in ${retryAfterSeconds}s`
425
+ }`,
426
+ { status: 429, code: body.code ?? "ratelimited", retryAfterSeconds }
427
+ );
428
+ }
429
+ default:
430
+ return new SlackProvisionApiError(
431
+ serverError ??
432
+ `The ${context.verb} request failed (${response.status}): ${
433
+ bodyText.slice(0, 300) || "(empty body)"
434
+ }`,
435
+ {
436
+ status: response.status,
437
+ code: body.code,
438
+ changedPaths: body.changedPaths,
439
+ }
440
+ );
441
+ }
442
+ }