@cursor/july 0.1.17 → 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 (135) hide show
  1. package/dist/channels/slack/cursor-account.d.ts.map +1 -1
  2. package/dist/channels/slack/cursor-account.js +1 -1
  3. package/dist/channels/slack/dispatch.d.ts.map +1 -1
  4. package/dist/channels/slack/dispatch.js +4 -1
  5. package/dist/channels/slack/inbound.d.ts.map +1 -1
  6. package/dist/channels/slack/inbound.js +6 -1
  7. package/dist/channels/slack/index.d.ts +1 -0
  8. package/dist/channels/slack/index.d.ts.map +1 -1
  9. package/dist/channels/slack/index.js +1 -0
  10. package/dist/channels/slack/init.d.ts.map +1 -1
  11. package/dist/channels/slack/init.js +4 -1
  12. package/dist/channels/slack/interactive.d.ts.map +1 -1
  13. package/dist/channels/slack/interactive.js +1 -0
  14. package/dist/channels/slack/manifest.d.ts +10 -0
  15. package/dist/channels/slack/manifest.d.ts.map +1 -1
  16. package/dist/channels/slack/manifest.js +33 -14
  17. package/dist/channels/slack/message-body.d.ts +13 -0
  18. package/dist/channels/slack/message-body.d.ts.map +1 -0
  19. package/dist/channels/slack/message-body.js +82 -0
  20. package/dist/channels/slack/thread-context.d.ts +0 -2
  21. package/dist/channels/slack/thread-context.d.ts.map +1 -1
  22. package/dist/channels/slack/thread-context.js +1 -65
  23. package/dist/channels/slack/types.d.ts +16 -1
  24. package/dist/channels/slack/types.d.ts.map +1 -1
  25. package/dist/docs/404.html +2 -2
  26. package/dist/docs/ab.html +3 -3
  27. package/dist/docs/assets/{app.BbEXr2KO.js → app.BR5EdVSq.js} +1 -1
  28. package/dist/docs/assets/chunks/@localSearchIndexroot.BOzkeUi8.js +1 -0
  29. package/dist/docs/assets/chunks/{VPLocalSearchBox.DBEi4HDp.js → VPLocalSearchBox.CNCzfUKC.js} +1 -1
  30. package/dist/docs/assets/chunks/{theme.DGfptcHw.js → theme.CwKvMSWv.js} +2 -2
  31. package/dist/docs/assets/{example-agents_security-reviewer.md.BZSNycMf.js → example-agents_security-reviewer.md.Dhj_m7_B.js} +1 -1
  32. package/dist/docs/building-with-agents.html +3 -3
  33. package/dist/docs/concepts.html +3 -3
  34. package/dist/docs/deployment.html +3 -3
  35. package/dist/docs/evals.html +3 -3
  36. package/dist/docs/example-agents/approval-buddy.html +3 -3
  37. package/dist/docs/example-agents/benny.html +3 -3
  38. package/dist/docs/example-agents/bugbot.html +3 -3
  39. package/dist/docs/example-agents/codebase-wiki.html +3 -3
  40. package/dist/docs/example-agents/codeowners-review.html +3 -3
  41. package/dist/docs/example-agents/concierge.html +3 -3
  42. package/dist/docs/example-agents/fsd.html +3 -3
  43. package/dist/docs/example-agents/index.html +3 -3
  44. package/dist/docs/example-agents/knowledge-base.html +3 -3
  45. package/dist/docs/example-agents/oncall.html +3 -3
  46. package/dist/docs/example-agents/security-reviewer.html +5 -5
  47. package/dist/docs/example-agents/slack-agent.html +3 -3
  48. package/dist/docs/example-agents/weather-agent.html +3 -3
  49. package/dist/docs/guides/agent-to-agent.html +3 -3
  50. package/dist/docs/guides/cloud-runtime.html +3 -3
  51. package/dist/docs/guides/github.html +3 -3
  52. package/dist/docs/guides/human-in-the-loop.html +3 -3
  53. package/dist/docs/guides/mcp-oauth.html +3 -3
  54. package/dist/docs/guides/slack.html +3 -3
  55. package/dist/docs/guides/webhooks.html +3 -3
  56. package/dist/docs/hashmap.json +1 -1
  57. package/dist/docs/hillclimbing.html +3 -3
  58. package/dist/docs/index.html +3 -3
  59. package/dist/docs/quickstart.html +3 -3
  60. package/dist/docs/reference/agent-config.html +3 -3
  61. package/dist/docs/reference/channels.html +3 -3
  62. package/dist/docs/reference/cli.html +3 -3
  63. package/dist/docs/reference/connections.html +3 -3
  64. package/dist/docs/reference/hooks.html +3 -3
  65. package/dist/docs/reference/http-api.html +3 -3
  66. package/dist/docs/reference/instructions.html +3 -3
  67. package/dist/docs/reference/playground.html +3 -3
  68. package/dist/docs/reference/project-layout.html +3 -3
  69. package/dist/docs/reference/prompt.html +3 -3
  70. package/dist/docs/reference/schedules.html +3 -3
  71. package/dist/docs/reference/sessions.html +3 -3
  72. package/dist/docs/reference/skills.html +3 -3
  73. package/dist/docs/reference/subagents.html +3 -3
  74. package/dist/docs/reference/tools.html +3 -3
  75. package/dist/docs/scaffolding-agents.html +3 -3
  76. package/dist/docs/storage.html +3 -3
  77. package/dist/docs/troubleshooting.html +3 -3
  78. package/dist/internal/builtin-tools/index.d.ts +10 -0
  79. package/dist/internal/builtin-tools/index.d.ts.map +1 -0
  80. package/dist/internal/builtin-tools/index.js +27 -0
  81. package/dist/internal/builtin-tools/reminders.d.ts +9 -0
  82. package/dist/internal/builtin-tools/reminders.d.ts.map +1 -0
  83. package/dist/internal/builtin-tools/reminders.js +138 -0
  84. package/dist/internal/cli-slack.d.ts.map +1 -1
  85. package/dist/internal/cli-slack.js +36 -15
  86. package/dist/internal/cursor-event-relay.d.ts +30 -0
  87. package/dist/internal/cursor-event-relay.d.ts.map +1 -1
  88. package/dist/internal/cursor-event-relay.js +37 -0
  89. package/dist/internal/discovery.d.ts.map +1 -1
  90. package/dist/internal/discovery.js +41 -0
  91. package/dist/internal/reminder-runner.d.ts +7 -0
  92. package/dist/internal/reminder-runner.d.ts.map +1 -1
  93. package/dist/internal/reminder-runner.js +21 -0
  94. package/dist/internal/sdk-runner.d.ts +15 -0
  95. package/dist/internal/sdk-runner.d.ts.map +1 -1
  96. package/dist/internal/sdk-runner.js +113 -13
  97. package/dist/internal/server.d.ts.map +1 -1
  98. package/dist/internal/server.js +1 -0
  99. package/dist/internal/session-engine.d.ts.map +1 -1
  100. package/dist/internal/session-engine.js +9 -0
  101. package/dist/internal/slack-provision-client.d.ts +6 -3
  102. package/dist/internal/slack-provision-client.d.ts.map +1 -1
  103. package/dist/internal/slack-provision-client.js +5 -3
  104. package/dist/playground/assets/{index-DNqirsrK.js → index-DRjS5klQ.js} +42 -42
  105. package/dist/playground/assets/index-TCDs-UhZ.css +1 -0
  106. package/dist/playground/index.html +2 -2
  107. package/dist/types.d.ts +28 -0
  108. package/dist/types.d.ts.map +1 -1
  109. package/docs/example-agents/security-reviewer.md +1 -1
  110. package/package.json +1 -1
  111. package/skills/setup-slack/SKILL.md +4 -0
  112. package/src/channels/slack/cursor-account.ts +1 -0
  113. package/src/channels/slack/dispatch.ts +3 -1
  114. package/src/channels/slack/inbound.ts +6 -1
  115. package/src/channels/slack/index.ts +1 -0
  116. package/src/channels/slack/init.ts +3 -0
  117. package/src/channels/slack/interactive.ts +1 -0
  118. package/src/channels/slack/manifest.ts +34 -6
  119. package/src/channels/slack/message-body.ts +90 -0
  120. package/src/channels/slack/thread-context.ts +1 -69
  121. package/src/channels/slack/types.ts +16 -1
  122. package/src/internal/builtin-tools/index.ts +40 -0
  123. package/src/internal/builtin-tools/reminders.ts +158 -0
  124. package/src/internal/cli-slack.ts +38 -14
  125. package/src/internal/cursor-event-relay.ts +67 -0
  126. package/src/internal/discovery.ts +52 -0
  127. package/src/internal/reminder-runner.ts +30 -0
  128. package/src/internal/sdk-runner.ts +131 -13
  129. package/src/internal/server.ts +2 -0
  130. package/src/internal/session-engine.ts +14 -0
  131. package/src/internal/slack-provision-client.ts +7 -3
  132. package/src/types.ts +33 -0
  133. package/dist/docs/assets/chunks/@localSearchIndexroot.DCBsrfwP.js +0 -1
  134. package/dist/playground/assets/index-CidizGZv.css +0 -1
  135. /package/dist/docs/assets/{example-agents_security-reviewer.md.BZSNycMf.lean.js → example-agents_security-reviewer.md.Dhj_m7_B.lean.js} +0 -0
@@ -86,16 +86,90 @@ export interface CursorSdkRunnerOptions {
86
86
  logger?: (line: string) => void;
87
87
  }
88
88
 
89
+ /**
90
+ * How long one SDK agent handle (and the Connect transport inside it) may be
91
+ * reused before the runner retires it.
92
+ *
93
+ * The transport exchanges the account API key for a short-lived access token
94
+ * (`SessionType.API_KEY_TOKEN`, 60 minutes) once per handle, and has no
95
+ * reliable refresh: an Unauthenticated rejection on the agent's streaming RPC
96
+ * surfaces while the stream is being read — outside the transport's
97
+ * request-time 401 retry — so an expired token gets replayed on every
98
+ * subsequent turn until the handle is rebuilt. Long-lived sessions (watch
99
+ * wakes, crons) hit this at the ~60 minute mark and then fail forever.
100
+ * Retiring the handle comfortably before the token can expire keeps them
101
+ * healthy: `Agent.resume` rehydrates the same conversation from the runner
102
+ * store on a fresh transport, so recycling costs only transport warmth.
103
+ */
104
+ const MAX_AGENT_HANDLE_AGE_MS = 45 * 60 * 1000;
105
+
106
+ interface CachedAgentHandle {
107
+ agent: Promise<SDKAgent>;
108
+ openedAtMs: number;
109
+ }
110
+
111
+ /**
112
+ * The backend's `NotLoggedIn` copy ("Authentication error" title, "try
113
+ * logging out and back in." detail) as it surfaces through SDK RunResults.
114
+ * Deliberately narrow: this gates a one-shot in-place retry, and matching
115
+ * broader error text would retry turns that a fresh transport cannot fix.
116
+ */
117
+ export function isAuthErrorMessage(message: string | undefined): boolean {
118
+ if (message === undefined) {
119
+ return false;
120
+ }
121
+ return /authentication error|try logging out and back in/i.test(message);
122
+ }
123
+
89
124
  export class CursorSdkRunner implements AgentRunner {
90
- private readonly agents = new Map<string, Promise<SDKAgent>>();
125
+ private readonly agents = new Map<string, CachedAgentHandle>();
91
126
  private readonly stores = new Map<string, Promise<LocalAgentStore>>();
92
127
  private readonly prewarmReleases: Array<() => Promise<void>> = [];
93
128
 
94
129
  constructor(private readonly options: CursorSdkRunnerOptions = {}) {}
95
130
 
96
131
  async runTurn(request: RunnerTurnRequest): Promise<TurnOutcome> {
132
+ let sawUpdate = false;
133
+ const tracked: RunnerTurnRequest = {
134
+ ...request,
135
+ onUpdate: (update) => {
136
+ sawUpdate = true;
137
+ return request.onUpdate(update);
138
+ },
139
+ };
140
+ const outcome = await this.runTurnAttempt(tracked);
141
+ if (
142
+ outcome.status !== "error" ||
143
+ !isAuthErrorMessage(outcome.errorMessage) ||
144
+ sawUpdate ||
145
+ request.isFirstTurn ||
146
+ request.signal?.aborted === true
147
+ ) {
148
+ return outcome;
149
+ }
150
+ // The turn died on an authentication failure before producing any output
151
+ // — the signature of a handle whose exchanged access token expired (the
152
+ // failed attempt already evicted it). Retrying once on a fresh handle is
153
+ // safe here because nothing streamed and no tool ran. First turns are
154
+ // excluded: their token was exchanged moments ago, so the credential
155
+ // itself is the problem and a retry cannot succeed.
156
+ this.log(
157
+ `[agentkit] session ${request.sessionId}: retrying the turn on a fresh SDK agent handle after an authentication failure`
158
+ );
159
+ try {
160
+ return await this.runTurnAttempt(tracked);
161
+ } catch {
162
+ // The retry was best effort; report the original failure.
163
+ return outcome;
164
+ }
165
+ }
166
+
167
+ private async runTurnAttempt(
168
+ request: RunnerTurnRequest
169
+ ): Promise<TurnOutcome> {
170
+ const agentPromise = this.getAgent(request);
97
171
  try {
98
- const agent = await this.getAgent(request);
172
+ const agent = await agentPromise;
99
173
  const run = await agent.send(request.prompt, {
100
174
  onDelta: async ({ update }) => {
101
175
  try {
@@ -141,14 +215,24 @@ export class CursorSdkRunner implements AgentRunner {
141
215
  }
142
216
  try {
143
217
  const result = await run.wait();
144
- return toTurnOutcome(result);
218
+ const outcome = toTurnOutcome(result);
219
+ if (outcome.status === "error") {
220
+ // An error result usually means the handle's transport is poisoned
221
+ // — most often a cached access token that aged past its 60-minute
222
+ // lifetime and gets replayed on every request (stream failures
223
+ // bypass the transport's 401 retry). Retire the handle so the next
224
+ // attempt resumes the conversation from the store on a fresh
225
+ // transport instead of failing every turn until a restart.
226
+ this.evictAgent(request.sessionId, agentPromise);
227
+ }
228
+ return outcome;
145
229
  } finally {
146
230
  signal?.removeEventListener("abort", onAbort);
147
231
  }
148
232
  } catch (error) {
149
233
  // Drop the cached agent so a dead Connect/HTTP2 session cannot poison
150
234
  // the next turn (terminal/shell overflow often kills the transport).
151
- this.agents.delete(request.sessionId);
235
+ this.evictAgent(request.sessionId, agentPromise);
152
236
  throw error;
153
237
  }
154
238
  }
@@ -178,7 +262,9 @@ export class CursorSdkRunner implements AgentRunner {
178
262
  async dispose(): Promise<void> {
179
263
  const releases = this.prewarmReleases.splice(0);
180
264
  await Promise.allSettled(releases.map((release) => release()));
181
- const agents = await Promise.allSettled([...this.agents.values()]);
265
+ const agents = await Promise.allSettled(
266
+ [...this.agents.values()].map((handle) => handle.agent)
267
+ );
182
268
  this.agents.clear();
183
269
  for (const settled of agents) {
184
270
  if (settled.status === "fulfilled") {
@@ -188,17 +274,49 @@ export class CursorSdkRunner implements AgentRunner {
188
274
  }
189
275
 
190
276
  private getAgent(request: RunnerTurnRequest): Promise<SDKAgent> {
191
- let agentPromise = this.agents.get(request.sessionId);
192
- if (agentPromise === undefined) {
193
- agentPromise = this.openAgent(request).catch((error) => {
194
- this.agents.delete(request.sessionId);
195
- throw error;
196
- });
197
- this.agents.set(request.sessionId, agentPromise);
277
+ const cached = this.agents.get(request.sessionId);
278
+ if (cached !== undefined) {
279
+ const ageMs = Date.now() - cached.openedAtMs;
280
+ if (ageMs < MAX_AGENT_HANDLE_AGE_MS) {
281
+ return cached.agent;
282
+ }
283
+ // Past this age the handle's exchanged access token could expire
284
+ // mid-turn. Retire it and reopen below on a fresh transport.
285
+ this.log(
286
+ `[agentkit] session ${request.sessionId}: recycling the SDK agent handle after ${Math.round(ageMs / 60_000)} min so the turn runs on a fresh access token`
287
+ );
288
+ this.evictAgent(request.sessionId, cached.agent);
198
289
  }
290
+ const agentPromise = this.openAgent(request).catch((error) => {
291
+ // Nothing opened, so there is no handle to close — just free the slot.
292
+ this.agents.delete(request.sessionId);
293
+ throw error;
294
+ });
295
+ this.agents.set(request.sessionId, {
296
+ agent: agentPromise,
297
+ openedAtMs: Date.now(),
298
+ });
199
299
  return agentPromise;
200
300
  }
201
301
 
302
+ /**
303
+ * Drop a cached handle and close it best-effort to release its transport
304
+ * and executor lease. Identity-checked so a turn that lost a race with a
305
+ * newer open cannot evict the replacement handle.
306
+ */
307
+ private evictAgent(sessionId: string, agent: Promise<SDKAgent>): void {
308
+ const cached = this.agents.get(sessionId);
309
+ if (cached === undefined || cached.agent !== agent) {
310
+ return;
311
+ }
312
+ this.agents.delete(sessionId);
313
+ void cached.agent.then((sdkAgent) => sdkAgent.close()).catch(() => {});
314
+ }
315
+
316
+ private log(line: string): void {
317
+ (this.options.logger ?? (() => {}))(line);
318
+ }
319
+
202
320
  private requireApiKey(): string {
203
321
  const apiKey = this.options.apiKey ?? process.env.CURSOR_API_KEY;
204
322
  if (apiKey === undefined || apiKey.trim() === "") {
@@ -274,7 +392,7 @@ export class CursorSdkRunner implements AgentRunner {
274
392
  // conversation under the same id beats failing this thread's turns
275
393
  // forever ("Agent … not found" on every follow-up). The channel's
276
394
  // warm-turn prompt context re-hydrates the thread history.
277
- (this.options.logger ?? (() => {}))(
395
+ this.log(
278
396
  `[agentkit] no stored conversation for ${agentId} on this host; starting a fresh one`
279
397
  );
280
398
  return Agent.create({ ...options, agentId });
@@ -421,6 +421,8 @@ export async function startServer(
421
421
  cancel: (id, reason) => reminderRunner.cancel(id, reason),
422
422
  cancelForContinuation: (token, reason) =>
423
423
  reminderRunner.cancelForContinuation(token, reason),
424
+ rekeyContinuation: (channelId, oldToken, newToken) =>
425
+ reminderRunner.rekeyContinuation(channelId, oldToken, newToken),
424
426
  dispatch: (id) => reminderRunner.dispatch(id),
425
427
  });
426
428
  engine.setEvals({
@@ -2148,10 +2148,24 @@ export class SessionEngine {
2148
2148
  sessionId: string,
2149
2149
  token: string | null
2150
2150
  ): Promise<void> {
2151
+ const previous = await this.sessions.get(sessionId);
2151
2152
  await this.updateRecord(sessionId, (current) => ({
2152
2153
  ...current,
2153
2154
  continuationKey: token,
2154
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
+ }
2155
2169
  }
2156
2170
 
2157
2171
  isTurnRunning(sessionId: string): boolean {
@@ -9,9 +9,10 @@
9
9
  * The endpoints are gated on the `agent_serve_slack_provisioning` Statsig
10
10
  * gate and 404 when it is off — the error mapping says so instead of
11
11
  * "not found". Expected provisioning failures carry a stable JSON `code`
12
- * (`not_connected`, `insufficient_scopes`, `app_conflict`,
13
- * `app_cap_reached`, `ratelimited`, `invalid_app_id`, …) which flows
14
- * through on {@link SlackProvisionApiError.code} so the CLI can branch.
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.
15
16
  */
16
17
 
17
18
  import { z } from "zod";
@@ -21,6 +22,8 @@ export type SlackAppEnv = "dev" | "prod";
21
22
  /** One manager-connected Slack workspace from `GET /status`. */
22
23
  export interface SlackConnectedWorkspace {
23
24
  slackTeamId: string;
25
+ /** Workspace name; absent on older backends, null on pre-existing rows. */
26
+ slackTeamName?: string | null;
24
27
  slackUserId: string;
25
28
  connectedAt: string;
26
29
  }
@@ -113,6 +116,7 @@ const statusResponseSchema = z
113
116
  z
114
117
  .object({
115
118
  slackTeamId: z.string(),
119
+ slackTeamName: z.string().nullish(),
116
120
  slackUserId: z.string(),
117
121
  connectedAt: z.string(),
118
122
  })
package/src/types.ts CHANGED
@@ -198,6 +198,29 @@ export interface AgentConfig {
198
198
  * Engine-wide turn admission limits. See {@link AgentConcurrencyOptions}.
199
199
  */
200
200
  concurrency?: AgentConcurrencyOptions;
201
+ /**
202
+ * Framework-provided model-facing tools, opted in per capability. See
203
+ * {@link BuiltinToolsConfig}.
204
+ */
205
+ builtinTools?: BuiltinToolsConfig;
206
+ }
207
+
208
+ /**
209
+ * Built-in tools on {@link AgentConfig.builtinTools}. Each enabled
210
+ * capability materializes as ordinary server tools at discovery time, so
211
+ * turns, direct calls, `info`, and the playground treat them like authored
212
+ * tools. Authored tools with the same name win (with a warning), and none
213
+ * of them run on cloud turns (server tools are local-runtime only).
214
+ */
215
+ export interface BuiltinToolsConfig {
216
+ /**
217
+ * Reminder tools bound to the current conversation, over
218
+ * {@link HostContext.reminders}: `reminders_create`, `reminders_list`,
219
+ * and `reminders_cancel` (the name the framework's reminder fire prompt
220
+ * tells the model to call when a stop condition is met). Sessions
221
+ * without a continuation key cannot arm reminders.
222
+ */
223
+ reminders?: boolean;
201
224
  }
202
225
 
203
226
  /**
@@ -1722,6 +1745,16 @@ export interface ReminderHostApi {
1722
1745
  continuationToken: string,
1723
1746
  reason: string
1724
1747
  ): Promise<number>;
1748
+ /**
1749
+ * Re-key armed reminders after a channel rotates a session's continuation
1750
+ * token, so earlier reminders keep waking the conversation. The engine
1751
+ * calls this from `setContinuationToken`; channels rarely need to.
1752
+ */
1753
+ rekeyContinuation(
1754
+ channelId: string,
1755
+ oldToken: string,
1756
+ newToken: string
1757
+ ): Promise<number>;
1725
1758
  /**
1726
1759
  * Fire immediately (dev / hillclimb).
1727
1760
  * `outcome` is `"noop"` when already firing or not armed.