@cursor/july 0.1.9 → 0.1.11

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 (200) hide show
  1. package/AGENTS.md +1 -0
  2. package/dist/bin/agent-serve.js +26 -3
  3. package/dist/channels/github/github-channel.d.ts.map +1 -1
  4. package/dist/channels/github/github-channel.js +2 -1
  5. package/dist/channels/github/types.d.ts +4 -0
  6. package/dist/channels/github/types.d.ts.map +1 -1
  7. package/dist/channels/slack/cursor-account.d.ts.map +1 -1
  8. package/dist/channels/slack/cursor-account.js +1 -1
  9. package/dist/channels/slack/dispatch.d.ts +13 -0
  10. package/dist/channels/slack/dispatch.d.ts.map +1 -1
  11. package/dist/channels/slack/dispatch.js +36 -11
  12. package/dist/channels/slack/eval-directive.d.ts +63 -0
  13. package/dist/channels/slack/eval-directive.d.ts.map +1 -0
  14. package/dist/channels/slack/eval-directive.js +283 -0
  15. package/dist/channels/slack/index.d.ts +1 -0
  16. package/dist/channels/slack/index.d.ts.map +1 -1
  17. package/dist/channels/slack/index.js +1 -0
  18. package/dist/channels/slack/slack-channel.d.ts.map +1 -1
  19. package/dist/channels/slack/slack-channel.js +4 -2
  20. package/dist/channels/slack/types.d.ts +7 -0
  21. package/dist/channels/slack/types.d.ts.map +1 -1
  22. package/dist/connections.d.ts +10 -5
  23. package/dist/connections.d.ts.map +1 -1
  24. package/dist/connections.js +10 -5
  25. package/dist/docs/404.html +2 -2
  26. package/dist/docs/ab.html +4 -4
  27. package/dist/docs/assets/{app.Ciosf24M.js → app.BcXUGca7.js} +1 -1
  28. package/dist/docs/assets/chunks/@localSearchIndexroot.BIbvK8M1.js +1 -0
  29. package/dist/docs/assets/chunks/{VPLocalSearchBox.BcRDly7n.js → VPLocalSearchBox.CWur6XGZ.js} +1 -1
  30. package/dist/docs/assets/chunks/{theme.CMpbjdfx.js → theme.BnvulOX2.js} +2 -2
  31. package/dist/docs/assets/{deployment.md.DC5-ImcM.js → deployment.md.TecHo0_2.js} +3 -3
  32. package/dist/docs/assets/{deployment.md.DC5-ImcM.lean.js → deployment.md.TecHo0_2.lean.js} +1 -1
  33. package/dist/docs/assets/guides_mcp-oauth.md.DqmEOQY1.js +29 -0
  34. package/dist/docs/assets/guides_mcp-oauth.md.DqmEOQY1.lean.js +1 -0
  35. package/dist/docs/assets/{index.md.COiu-1jL.js → index.md.t0TM2Qzz.js} +3 -3
  36. package/dist/docs/assets/{index.md.COiu-1jL.lean.js → index.md.t0TM2Qzz.lean.js} +1 -1
  37. package/dist/docs/assets/{reference_cli.md.D189RBCH.js → reference_cli.md.B74NrVzb.js} +5 -4
  38. package/dist/docs/assets/{reference_cli.md.D189RBCH.lean.js → reference_cli.md.B74NrVzb.lean.js} +1 -1
  39. package/dist/docs/assets/{reference_connections.md.zaEYCLHT.js → reference_connections.md.CsSr3rDz.js} +9 -4
  40. package/dist/docs/assets/{reference_connections.md.zaEYCLHT.lean.js → reference_connections.md.CsSr3rDz.lean.js} +1 -1
  41. package/dist/docs/assets/reference_prompt.md.BaiweQxE.js +1 -0
  42. package/dist/docs/assets/reference_prompt.md.BaiweQxE.lean.js +1 -0
  43. package/dist/docs/assets/{reference_tools.md.CqgJroI0.js → reference_tools.md.BRSDnTbN.js} +16 -3
  44. package/dist/docs/assets/{reference_tools.md.CqgJroI0.lean.js → reference_tools.md.BRSDnTbN.lean.js} +1 -1
  45. package/dist/docs/assets/{troubleshooting.md.DkovjS7h.js → troubleshooting.md.CmQkmnzC.js} +1 -1
  46. package/dist/docs/assets/{troubleshooting.md.DkovjS7h.lean.js → troubleshooting.md.CmQkmnzC.lean.js} +1 -1
  47. package/dist/docs/building-with-agents.html +4 -4
  48. package/dist/docs/concepts.html +4 -4
  49. package/dist/docs/deployment.html +7 -7
  50. package/dist/docs/evals.html +4 -4
  51. package/dist/docs/example-agents/approval-buddy.html +4 -4
  52. package/dist/docs/example-agents/benny.html +4 -4
  53. package/dist/docs/example-agents/bugbot.html +4 -4
  54. package/dist/docs/example-agents/codebase-wiki.html +4 -4
  55. package/dist/docs/example-agents/codeowners-review.html +4 -4
  56. package/dist/docs/example-agents/concierge.html +4 -4
  57. package/dist/docs/example-agents/fsd.html +4 -4
  58. package/dist/docs/example-agents/index.html +4 -4
  59. package/dist/docs/example-agents/knowledge-base.html +4 -4
  60. package/dist/docs/example-agents/oncall.html +4 -4
  61. package/dist/docs/example-agents/security-reviewer.html +4 -4
  62. package/dist/docs/example-agents/slack-agent.html +4 -4
  63. package/dist/docs/example-agents/weather-agent.html +4 -4
  64. package/dist/docs/guides/agent-to-agent.html +4 -4
  65. package/dist/docs/guides/cloud-runtime.html +4 -4
  66. package/dist/docs/guides/github.html +4 -4
  67. package/dist/docs/guides/human-in-the-loop.html +4 -4
  68. package/dist/docs/guides/mcp-oauth.html +53 -0
  69. package/dist/docs/guides/slack.html +4 -4
  70. package/dist/docs/guides/webhooks.html +4 -4
  71. package/dist/docs/hashmap.json +1 -1
  72. package/dist/docs/hillclimbing.html +4 -4
  73. package/dist/docs/index.html +6 -6
  74. package/dist/docs/quickstart.html +4 -4
  75. package/dist/docs/reference/agent-config.html +4 -4
  76. package/dist/docs/reference/channels.html +4 -4
  77. package/dist/docs/reference/cli.html +8 -7
  78. package/dist/docs/reference/connections.html +13 -8
  79. package/dist/docs/reference/hooks.html +4 -4
  80. package/dist/docs/reference/http-api.html +4 -4
  81. package/dist/docs/reference/instructions.html +4 -4
  82. package/dist/docs/reference/playground.html +4 -4
  83. package/dist/docs/reference/project-layout.html +4 -4
  84. package/dist/docs/reference/prompt.html +33 -0
  85. package/dist/docs/reference/schedules.html +4 -4
  86. package/dist/docs/reference/sessions.html +4 -4
  87. package/dist/docs/reference/skills.html +5 -5
  88. package/dist/docs/reference/subagents.html +4 -4
  89. package/dist/docs/reference/tools.html +20 -7
  90. package/dist/docs/scaffolding-agents.html +4 -4
  91. package/dist/docs/storage.html +4 -4
  92. package/dist/docs/troubleshooting.html +5 -5
  93. package/dist/evals.d.ts +39 -0
  94. package/dist/evals.d.ts.map +1 -1
  95. package/dist/index.d.ts +3 -1
  96. package/dist/index.d.ts.map +1 -1
  97. package/dist/index.js +2 -1
  98. package/dist/internal/cli-ax.d.ts.map +1 -1
  99. package/dist/internal/cli-ax.js +7 -4
  100. package/dist/internal/cli-mcp-oauth.d.ts +16 -0
  101. package/dist/internal/cli-mcp-oauth.d.ts.map +1 -0
  102. package/dist/internal/cli-mcp-oauth.js +194 -0
  103. package/dist/internal/cli-mcp.d.ts +4 -1
  104. package/dist/internal/cli-mcp.d.ts.map +1 -1
  105. package/dist/internal/cli-mcp.js +19 -1
  106. package/dist/internal/corp-ca.d.ts +9 -0
  107. package/dist/internal/corp-ca.d.ts.map +1 -0
  108. package/dist/internal/corp-ca.js +44 -0
  109. package/dist/internal/cursor/account-mcp.d.ts +17 -4
  110. package/dist/internal/cursor/account-mcp.d.ts.map +1 -1
  111. package/dist/internal/cursor/account-mcp.js +105 -17
  112. package/dist/internal/discovery.d.ts.map +1 -1
  113. package/dist/internal/discovery.js +64 -10
  114. package/dist/internal/distribution.d.ts.map +1 -1
  115. package/dist/internal/distribution.js +1 -0
  116. package/dist/internal/logs-client.js +1 -1
  117. package/dist/internal/mcp-host.d.ts.map +1 -1
  118. package/dist/internal/mcp-host.js +49 -26
  119. package/dist/internal/mcp-oauth.d.ts +88 -0
  120. package/dist/internal/mcp-oauth.d.ts.map +1 -0
  121. package/dist/internal/mcp-oauth.js +385 -0
  122. package/dist/internal/resolved-connections.d.ts +6 -0
  123. package/dist/internal/resolved-connections.d.ts.map +1 -1
  124. package/dist/internal/resolved-connections.js +10 -3
  125. package/dist/internal/sdk-runner.d.ts +9 -0
  126. package/dist/internal/sdk-runner.d.ts.map +1 -1
  127. package/dist/internal/sdk-runner.js +28 -2
  128. package/dist/internal/server.d.ts.map +1 -1
  129. package/dist/internal/server.js +24 -6
  130. package/dist/internal/session-engine.d.ts +30 -10
  131. package/dist/internal/session-engine.d.ts.map +1 -1
  132. package/dist/internal/session-engine.js +166 -74
  133. package/dist/internal/sessions-client.d.ts +16 -2
  134. package/dist/internal/sessions-client.d.ts.map +1 -1
  135. package/dist/internal/sessions-client.js +30 -13
  136. package/dist/internal/storage-coordinator.d.ts +11 -0
  137. package/dist/internal/storage-coordinator.d.ts.map +1 -1
  138. package/dist/internal/storage-coordinator.js +30 -0
  139. package/dist/internal/stream-progress.d.ts.map +1 -1
  140. package/dist/internal/stream-progress.js +8 -4
  141. package/dist/internal/turn-governor.d.ts +54 -0
  142. package/dist/internal/turn-governor.d.ts.map +1 -0
  143. package/dist/internal/turn-governor.js +111 -0
  144. package/dist/playground/assets/{index-C0-QEG4O.js → index-BIGLubwK.js} +46 -46
  145. package/dist/playground/assets/{index-z3JY9O2x.css → index-C17ZQKDG.css} +1 -1
  146. package/dist/playground/index.html +2 -2
  147. package/dist/prompt.d.ts +39 -0
  148. package/dist/prompt.d.ts.map +1 -0
  149. package/dist/prompt.js +70 -0
  150. package/dist/storage-backends/cursor-hosted.d.ts +10 -0
  151. package/dist/storage-backends/cursor-hosted.d.ts.map +1 -1
  152. package/dist/storage-backends/cursor-hosted.js +14 -0
  153. package/dist/types.d.ts +93 -5
  154. package/dist/types.d.ts.map +1 -1
  155. package/dist/types.js +6 -0
  156. package/docs/README.md +4 -0
  157. package/docs/deployment.md +6 -1
  158. package/docs/guides/mcp-oauth.md +157 -0
  159. package/docs/reference/cli.md +30 -0
  160. package/docs/reference/connections.md +32 -2
  161. package/docs/reference/prompt.md +47 -0
  162. package/docs/reference/tools.md +21 -0
  163. package/docs/troubleshooting.md +11 -0
  164. package/package.json +9 -2
  165. package/skills/create-agent/SKILL.md +7 -3
  166. package/skills/framework-map/SKILL.md +1 -0
  167. package/skills/mcp-auth/SKILL.md +138 -0
  168. package/src/bin/agent-serve.ts +30 -3
  169. package/src/channels/github/github-channel.ts +2 -0
  170. package/src/channels/github/types.ts +4 -0
  171. package/src/channels/slack/cursor-account.ts +2 -0
  172. package/src/channels/slack/dispatch.ts +49 -0
  173. package/src/channels/slack/eval-directive.ts +348 -0
  174. package/src/channels/slack/index.ts +1 -0
  175. package/src/channels/slack/slack-channel.ts +3 -0
  176. package/src/channels/slack/types.ts +7 -0
  177. package/src/connections.ts +10 -5
  178. package/src/evals.ts +43 -0
  179. package/src/index.ts +3 -0
  180. package/src/internal/cli-ax.ts +10 -4
  181. package/src/internal/cli-mcp-oauth.ts +254 -0
  182. package/src/internal/cli-mcp.ts +25 -2
  183. package/src/internal/cursor/account-mcp.ts +153 -41
  184. package/src/internal/discovery.ts +70 -8
  185. package/src/internal/distribution.ts +1 -0
  186. package/src/internal/logs-client.ts +1 -1
  187. package/src/internal/mcp-host.ts +33 -9
  188. package/src/internal/mcp-oauth.ts +496 -0
  189. package/src/internal/resolved-connections.ts +20 -5
  190. package/src/internal/sdk-runner.ts +37 -1
  191. package/src/internal/server.ts +28 -6
  192. package/src/internal/session-engine.ts +192 -76
  193. package/src/internal/sessions-client.ts +51 -15
  194. package/src/internal/storage-coordinator.ts +36 -0
  195. package/src/internal/stream-progress.ts +8 -0
  196. package/src/internal/turn-governor.ts +145 -0
  197. package/src/prompt.ts +83 -0
  198. package/src/storage-backends/cursor-hosted.ts +21 -0
  199. package/src/types.ts +92 -6
  200. package/dist/docs/assets/chunks/@localSearchIndexroot.D154Detj.js +0 -1
@@ -51,7 +51,10 @@ import {
51
51
  cmdStop,
52
52
  type DeployCliOptions,
53
53
  } from "../internal/cli-deploy.js";
54
- import { CLI_COMMAND_NAME as CLI } from "../internal/distribution.js";
54
+ import {
55
+ CLI_COMMAND_NAME as CLI,
56
+ packageVersion,
57
+ } from "../internal/distribution.js";
55
58
  import { clearTerminal, stdoutPalette } from "../internal/terminal-style.js";
56
59
  import { cmdUpdate, warnIfOutdated } from "../internal/update-check.js";
57
60
  import type { AgentProject, AgentServeHandle } from "../types.js";
@@ -93,6 +96,7 @@ Usage:
93
96
  | --url <baseUrl> [--bearer-token <t>]
94
97
  ${CLI} mcp install [--name <serverName>] [--print] [--json]
95
98
  (same target flags as mcp)
99
+ ${CLI} mcp oauth <connection> [--dir <path>] [--store] [--slug <slug>]
96
100
  ${CLI} eval [--dir <path>] [evalId...] [--list] [--json]
97
101
  [--tag <tag>]... [--verbose] [--timeout-ms <n>]
98
102
  [--no-stream] [--no-wait]
@@ -106,6 +110,7 @@ Usage:
106
110
  ${CLI} logout
107
111
  ${CLI} whoami [--json]
108
112
  ${CLI} update
113
+ ${CLI} version
109
114
  ${CLI} deploy [--dir <path>] [--slug <slug> | --all] [--team <id>] [--json]
110
115
  [--no-wait] [--repo <url>] [--ref <ref>]
111
116
  [--path <agentPath>] [--cursor-events-repo <owner/name>]...
@@ -170,7 +175,9 @@ Commands:
170
175
  --url targets a running server. stdout is reserved for the
171
176
  MCP wire. \`mcp install\` writes the matching entry into
172
177
  ~/.cursor/mcp.json (--print to preview; --name to override
173
- the server name).
178
+ the server name). \`mcp oauth <connection>\` runs host PKCE
179
+ for defineConnection({ url, oauth: true }); \`--store\` upserts
180
+ MCP_OAUTH_<NAME>_* secrets on the hosted deployment.
174
181
  eval Run filesystem evals under evals/**/*.eval.ts (streams on a TTY).
175
182
  Default: ephemeral local harness against evals on disk. Pass
176
183
  --prod to kick off the batch on the team's hosted deployment
@@ -194,6 +201,7 @@ Commands:
194
201
  whoami Show which Cursor account powers this host (and why).
195
202
  update Upgrade this install to the latest published version (detects
196
203
  global npm/pnpm/yarn/bun installs and project dependencies).
204
+ version Print the installed ${CLI} version and exit (also --version / -V).
197
205
  deploy Deploy onto Cursor's managed agent-serve hosting, then wait for
198
206
  the engine to come up. --repo/--ref/--path stay available;
199
207
  inside an agent git checkout they are inferred when omitted
@@ -224,6 +232,7 @@ Agent loop:
224
232
  ${CLI} dev .
225
233
 
226
234
  Options:
235
+ --version, -V Print the installed version and exit.
227
236
  --dir Project root (defaults to the current directory).
228
237
  --port Port to listen on (default 3000).
229
238
  --host Host to bind (default 127.0.0.1).
@@ -247,6 +256,11 @@ Options:
247
256
  githubChannel({ cursorAccount }). Admitted callers can drive
248
257
  sessions holding the account's repo-scoped GitHub credential,
249
258
  so use only behind a trusted boundary (e.g. an SSO proxy).
259
+ --allow-anonymous-cursor-account-mcp
260
+ serve: allow --allow-anonymous together with
261
+ defineConnection({ cursorAccount: true }). Admitted callers
262
+ can drive the account's MCP connectors, so use only behind
263
+ a trusted boundary (e.g. an SSO proxy or hosted alias token).
250
264
  --public-url serve: base URL where this host is reachable from outside
251
265
  (e.g. https://agent-serve.example.com). Required for peer
252
266
  connections (defineConnection({ agent })) on cloud-runtime
@@ -349,6 +363,10 @@ async function main(argv: string[]): Promise<number> {
349
363
  process.stdout.write(HELP);
350
364
  return command === undefined ? 1 : 0;
351
365
  }
366
+ if (command === "version" || command === "--version" || command === "-V") {
367
+ process.stdout.write(`${CLI} ${packageVersion()}\n`);
368
+ return 0;
369
+ }
352
370
 
353
371
  // `session --events` means "dump raw events" (boolean). Rewrite so it does
354
372
  // not collide with run/trajectory's `--events <file.ndjson>` string flag.
@@ -372,6 +390,7 @@ async function main(argv: string[]): Promise<number> {
372
390
  "bearer-token": { type: "string" },
373
391
  "allow-anonymous": { type: "boolean", default: false },
374
392
  "allow-anonymous-cursor-github": { type: "boolean", default: false },
393
+ "allow-anonymous-cursor-account-mcp": { type: "boolean", default: false },
375
394
  "no-schedules": { type: "boolean", default: false },
376
395
  "no-playground": { type: "boolean", default: false },
377
396
  "no-docs": { type: "boolean", default: false },
@@ -403,6 +422,7 @@ async function main(argv: string[]): Promise<number> {
403
422
  "mount-path": { type: "string" },
404
423
  "base-url": { type: "string" },
405
424
  print: { type: "boolean", default: false },
425
+ store: { type: "boolean", default: false },
406
426
  transport: { type: "string" },
407
427
  prefix: { type: "string" },
408
428
  "no-prefix": { type: "boolean", default: false },
@@ -502,7 +522,8 @@ async function main(argv: string[]): Promise<number> {
502
522
 
503
523
  case "mcp": {
504
524
  const { cmdMcp } = await import("../internal/cli-mcp.js");
505
- return cmdMcp(positionals[0], {
525
+ const mcpSub = positionals[0];
526
+ return cmdMcp(mcpSub, {
506
527
  dir,
507
528
  slug: values.slug,
508
529
  team: values.team,
@@ -513,6 +534,8 @@ async function main(argv: string[]): Promise<number> {
513
534
  name: values.name,
514
535
  print: values.print,
515
536
  json: values.json,
537
+ store: values.store,
538
+ oauthConnection: mcpSub === "oauth" ? positionals[1] : undefined,
516
539
  });
517
540
  }
518
541
 
@@ -976,6 +999,10 @@ async function main(argv: string[]): Promise<number> {
976
999
  allowAnonymous: values["allow-anonymous"] === true ? true : undefined,
977
1000
  allowAnonymousCursorGithub:
978
1001
  values["allow-anonymous-cursor-github"] === true ? true : undefined,
1002
+ allowAnonymousCursorAccountMcp:
1003
+ values["allow-anonymous-cursor-account-mcp"] === true
1004
+ ? true
1005
+ : undefined,
979
1006
  schedules: values["no-schedules"] === true ? false : undefined,
980
1007
  playground: values["no-playground"] === true ? false : undefined,
981
1008
  docs: values["no-docs"] === true ? false : undefined,
@@ -116,6 +116,7 @@ const githubPullRequestPayloadSchema: z.ZodType<GitHubPullRequestPayload> =
116
116
  title: z.string(),
117
117
  body: z.string().nullable().optional(),
118
118
  draft: z.boolean().optional(),
119
+ merged: z.boolean().optional(),
119
120
  user: z
120
121
  .object({
121
122
  login: z.string().min(1).optional(),
@@ -330,6 +331,7 @@ export function parseGitHubPullRequestEvent(args: {
330
331
  ? {}
331
332
  : { baseSha: payload.pull_request.base.sha }),
332
333
  draft: payload.pull_request.draft ?? false,
334
+ merged: payload.pull_request.merged === true,
333
335
  ...(payload.pull_request.user?.login === undefined
334
336
  ? {}
335
337
  : { authorLogin: payload.pull_request.user.login }),
@@ -148,6 +148,8 @@ export type GitHubPullRequestPayload = {
148
148
  title: string;
149
149
  body?: string | null;
150
150
  draft?: boolean;
151
+ /** Present on real webhook payloads; false/undefined when not merged. */
152
+ merged?: boolean;
151
153
  user?: {
152
154
  login?: string;
153
155
  id?: number;
@@ -182,6 +184,8 @@ export interface GitHubPullRequestEvent {
182
184
  baseRef: string;
183
185
  baseSha?: string;
184
186
  draft: boolean;
187
+ /** True when the PR was merged (from the webhook payload). */
188
+ merged: boolean;
185
189
  authorLogin?: string;
186
190
  }
187
191
 
@@ -167,6 +167,8 @@ export async function dispatchCursorSlackEnvelope(args: {
167
167
  credentials: runtime.credentials,
168
168
  host: args.host,
169
169
  log,
170
+ evalCommandsEnabled: runtime.options.evalCommands !== false,
171
+ cursorAgentName: envelope.agentName,
170
172
  ...(args.hasContinuationSession === undefined
171
173
  ? {}
172
174
  : { hasContinuationSession: args.hasContinuationSession }),
@@ -6,6 +6,10 @@ import type { HostContext, JsonValue, SendMessageFn } from "../../types.js";
6
6
  import { buildSlackBinding, slackContinuationKey } from "./api.js";
7
7
  import { firstLineTitle, maybeSetThreadTitle } from "./assistant.js";
8
8
  import type { ChannelPostWatcher } from "./channel-watch.js";
9
+ import {
10
+ handleSlackEvalDirective,
11
+ parseSlackEvalDirective,
12
+ } from "./eval-directive.js";
9
13
  import type { EventIdDedupe } from "./event-id-dedupe.js";
10
14
  import {
11
15
  formatSlackContextBlock,
@@ -82,6 +86,13 @@ export interface SlackInboundDispatchDeps {
82
86
  ) => Promise<string | undefined>;
83
87
  /** Cancel the active turn + clear coalesced nudges for this continuation. */
84
88
  interruptContinuation?: (continuationToken: string) => Promise<boolean>;
89
+ /**
90
+ * When false, skip the built-in `eval` directive. Defaults to true when
91
+ * `host.evals` is present.
92
+ */
93
+ evalCommandsEnabled?: boolean;
94
+ /** cursorAccount agent name stripped before `eval` directive parsing. */
95
+ cursorAgentName?: string;
85
96
  }
86
97
 
87
98
  function triggeringMergeMessage(
@@ -114,6 +125,12 @@ export async function dispatchInboundMessage(args: {
114
125
  interruptContinuation?: (continuationToken: string) => Promise<boolean>;
115
126
  log?: SlackLog;
116
127
  eventId?: string;
128
+ evalCommandsEnabled?: boolean;
129
+ /**
130
+ * cursorAccount agent display name — stripped before parsing `eval …`
131
+ * (`OncallBuddy eval list`).
132
+ */
133
+ cursorAgentName?: string;
117
134
  }): Promise<void> {
118
135
  const { message, kind } = args;
119
136
  const log = args.log ?? defaultSlackLog;
@@ -168,6 +185,34 @@ export async function dispatchInboundMessage(args: {
168
185
  return;
169
186
  }
170
187
 
188
+ // Built-in eval directive: run playground batches without an agent turn.
189
+ const evalCommandsEnabled = args.evalCommandsEnabled !== false;
190
+ const evalParseOptions =
191
+ args.cursorAgentName === undefined || args.cursorAgentName === ""
192
+ ? undefined
193
+ : { agentName: args.cursorAgentName };
194
+ const evalDirective =
195
+ evalCommandsEnabled && args.host.evals !== undefined
196
+ ? (parseSlackEvalDirective(message.text, evalParseOptions) ??
197
+ parseSlackEvalDirective(message.markdown, evalParseOptions))
198
+ : null;
199
+ if (evalDirective !== null && args.host.evals !== undefined) {
200
+ log.info("eval directive", {
201
+ kind,
202
+ channel: message.channelId,
203
+ thread_ts: message.threadTs,
204
+ continuation,
205
+ event_id: args.eventId,
206
+ directive: evalDirective.kind,
207
+ });
208
+ await handleSlackEvalDirective({
209
+ directive: evalDirective,
210
+ evals: args.host.evals,
211
+ binding,
212
+ });
213
+ return;
214
+ }
215
+
171
216
  if (message.ts === message.threadTs) {
172
217
  await maybeSetThreadTitle({
173
218
  botToken: args.credentials?.botToken,
@@ -393,6 +438,8 @@ export function dispatchInboundFromEventBody(
393
438
  credentials: deps.credentials,
394
439
  host: deps.host,
395
440
  log,
441
+ evalCommandsEnabled: deps.evalCommandsEnabled,
442
+ cursorAgentName: deps.cursorAgentName,
396
443
  ...(deps.hasContinuationSession === undefined
397
444
  ? {}
398
445
  : { hasContinuationSession: deps.hasContinuationSession }),
@@ -431,6 +478,8 @@ export function dispatchInboundFromEventBody(
431
478
  credentials: deps.credentials,
432
479
  host: deps.host,
433
480
  log,
481
+ evalCommandsEnabled: deps.evalCommandsEnabled,
482
+ cursorAgentName: deps.cursorAgentName,
434
483
  ...(deps.hasContinuationSession === undefined
435
484
  ? {}
436
485
  : { hasContinuationSession: deps.hasContinuationSession }),
@@ -0,0 +1,348 @@
1
+ /**
2
+ * Built-in Slack `eval` directive — run playground evals from a mention/DM
3
+ * without per-agent custom handlers (parity with CLI / playground runs).
4
+ *
5
+ * Examples (after stripping @mentions / cursorAccount agent name):
6
+ * eval
7
+ * eval triage/open-incidents smoke
8
+ * eval --tag oncall
9
+ * eval list
10
+ * eval status
11
+ * eval help
12
+ */
13
+
14
+ import type { EvalRunSnapshot, HostEvalsApi } from "../../evals.js";
15
+ import {
16
+ EvalRunConflictError,
17
+ EvalRunEmptyError,
18
+ } from "../../internal/eval-run-store.js";
19
+ import { stripLeadingSlackMentions } from "./stop.js";
20
+ import type { SlackBinding } from "./types.js";
21
+
22
+ const EVAL_POLL_MS = 2_000;
23
+ const TYPING_STATUS_MAX = 50;
24
+
25
+ export type SlackEvalDirective =
26
+ | { kind: "help" }
27
+ | { kind: "list" }
28
+ | { kind: "status" }
29
+ | { kind: "run"; filterIds?: string[]; tags?: string[] };
30
+
31
+ function escapeRegExp(value: string): string {
32
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
33
+ }
34
+
35
+ /**
36
+ * Normalize inbound Slack text for directive parsing: strip leading
37
+ * `<@U…>` mentions, leading `@U…` markdown mentions, and an optional
38
+ * cursorAccount agentName prefix (`OncallBuddy eval …`).
39
+ */
40
+ export function normalizeSlackEvalDirectiveBody(
41
+ text: string,
42
+ options?: { agentName?: string }
43
+ ): string {
44
+ let body = stripLeadingSlackMentions(text);
45
+ // markdown form from slackTextToMarkdown / cursorAccount envelopes
46
+ while (/^@[A-Za-z0-9._-]+\s+/.test(body)) {
47
+ body = body.replace(/^@[A-Za-z0-9._-]+\s+/, "").trim();
48
+ }
49
+ const agentName = options?.agentName?.trim();
50
+ if (agentName !== undefined && agentName.length > 0) {
51
+ const prefix = new RegExp(`^${escapeRegExp(agentName)}\\s+`, "i");
52
+ body = body.replace(prefix, "").trim();
53
+ }
54
+ return body;
55
+ }
56
+
57
+ /**
58
+ * Parse a Slack eval directive. Returns null when the message is not an
59
+ * eval command (falls through to the normal agent handler).
60
+ */
61
+ export function parseSlackEvalDirective(
62
+ text: string,
63
+ options?: { agentName?: string }
64
+ ): SlackEvalDirective | null {
65
+ const body = normalizeSlackEvalDirectiveBody(text, options);
66
+ if (body.length === 0) {
67
+ return null;
68
+ }
69
+ const tokens = body.split(/\s+/).filter((t) => t.length > 0);
70
+ if (tokens.length === 0 || !/^eval$/i.test(tokens[0] ?? "")) {
71
+ return null;
72
+ }
73
+
74
+ const rest = tokens.slice(1);
75
+ if (rest.length === 0) {
76
+ return { kind: "run" };
77
+ }
78
+
79
+ const head = (rest[0] ?? "").toLowerCase();
80
+ if (
81
+ rest.length === 1 &&
82
+ (head === "help" || head === "-h" || head === "--help")
83
+ ) {
84
+ return { kind: "help" };
85
+ }
86
+ if (rest.length === 1 && (head === "list" || head === "ls")) {
87
+ return { kind: "list" };
88
+ }
89
+ if (rest.length === 1 && head === "status") {
90
+ return { kind: "status" };
91
+ }
92
+
93
+ const filterIds: string[] = [];
94
+ const tags: string[] = [];
95
+ for (let i = 0; i < rest.length; i++) {
96
+ const tok = rest[i] ?? "";
97
+ if (tok === "--tag" || tok === "-t") {
98
+ const value = rest[i + 1];
99
+ if (value === undefined || value.length === 0) {
100
+ return { kind: "help" };
101
+ }
102
+ tags.push(value);
103
+ i += 1;
104
+ continue;
105
+ }
106
+ if (tok.startsWith("--tag=")) {
107
+ const value = tok.slice("--tag=".length);
108
+ if (value.length === 0) {
109
+ return { kind: "help" };
110
+ }
111
+ tags.push(value);
112
+ continue;
113
+ }
114
+ if (tok.startsWith("-t=") && tok.length > 3) {
115
+ tags.push(tok.slice(3));
116
+ continue;
117
+ }
118
+ if (tok.startsWith("-")) {
119
+ // Unknown flag — surface help rather than treating as an eval id.
120
+ return { kind: "help" };
121
+ }
122
+ filterIds.push(tok);
123
+ }
124
+
125
+ const run: SlackEvalDirective = { kind: "run" };
126
+ if (filterIds.length > 0) {
127
+ run.filterIds = filterIds;
128
+ }
129
+ if (tags.length > 0) {
130
+ run.tags = tags;
131
+ }
132
+ return run;
133
+ }
134
+
135
+ /** True when either raw Slack text or markdown form is an eval directive. */
136
+ export function isSlackEvalDirective(
137
+ text: string,
138
+ markdown: string,
139
+ options?: { agentName?: string }
140
+ ): boolean {
141
+ return (
142
+ parseSlackEvalDirective(text, options) !== null ||
143
+ parseSlackEvalDirective(markdown, options) !== null
144
+ );
145
+ }
146
+
147
+ export function formatSlackEvalHelp(): string {
148
+ return [
149
+ "*Eval commands* (same batches as the playground / `agentkit eval --prod`):",
150
+ "• `eval` — run all evals",
151
+ "• `eval <id> [id…]` — run specific eval ids",
152
+ "• `eval --tag <tag>` — run evals with a tag (`-t` also works)",
153
+ "• `eval list` — list available evals",
154
+ "• `eval status` — show the active or latest eval run",
155
+ "• `eval help` — this message",
156
+ ].join("\n");
157
+ }
158
+
159
+ export function formatSlackEvalList(
160
+ evals: Array<{
161
+ id: string;
162
+ description?: string;
163
+ tags?: string[];
164
+ }>
165
+ ): string {
166
+ if (evals.length === 0) {
167
+ return "No evals found under `evals/`.";
168
+ }
169
+ const lines = [`*${evals.length} eval(s):*`];
170
+ for (const item of evals) {
171
+ const tags =
172
+ item.tags === undefined || item.tags.length === 0
173
+ ? ""
174
+ : ` _[${item.tags.join(", ")}]_`;
175
+ const desc =
176
+ item.description === undefined || item.description === ""
177
+ ? ""
178
+ : ` — ${item.description}`;
179
+ lines.push(`• \`${item.id}\`${tags}${desc}`);
180
+ }
181
+ return lines.join("\n");
182
+ }
183
+
184
+ export function formatSlackEvalRunSummary(run: EvalRunSnapshot): string {
185
+ const { summary } = run;
186
+ const statusLabel =
187
+ run.status === "completed"
188
+ ? "completed"
189
+ : run.status === "failed"
190
+ ? "failed"
191
+ : "running";
192
+ const lines = [
193
+ `*Eval run* \`${run.runId}\` — *${statusLabel}*`,
194
+ `• ${summary.passed} passed, ${summary.failed} failed, ${summary.total} total` +
195
+ (summary.done < summary.total
196
+ ? ` (${summary.done}/${summary.total} done)`
197
+ : ""),
198
+ ];
199
+ if (run.error !== undefined && run.error !== "") {
200
+ lines.push(`• error: ${run.error}`);
201
+ }
202
+ for (const c of run.cases) {
203
+ const mark =
204
+ c.status !== "done"
205
+ ? c.status === "running"
206
+ ? "RUN "
207
+ : "…. "
208
+ : c.ok === true
209
+ ? "PASS"
210
+ : "FAIL";
211
+ const dur =
212
+ c.durationMs === undefined
213
+ ? ""
214
+ : ` (${(c.durationMs / 1000).toFixed(1)}s)`;
215
+ const err =
216
+ c.ok === false && c.error !== undefined && c.error !== ""
217
+ ? ` — ${c.error}`
218
+ : "";
219
+ lines.push(`\`${mark}\` \`${c.id}\`${dur}${err}`);
220
+ }
221
+ return lines.join("\n");
222
+ }
223
+
224
+ function truncateTyping(status: string): string {
225
+ if (status.length <= TYPING_STATUS_MAX) {
226
+ return status;
227
+ }
228
+ return `${status.slice(0, TYPING_STATUS_MAX - 1)}…`;
229
+ }
230
+
231
+ async function sleep(ms: number): Promise<void> {
232
+ await new Promise((resolve) => {
233
+ setTimeout(resolve, ms);
234
+ });
235
+ }
236
+
237
+ async function safeTyping(
238
+ binding: SlackBinding,
239
+ status: string
240
+ ): Promise<void> {
241
+ try {
242
+ await binding.thread.startTyping(truncateTyping(status));
243
+ } catch {
244
+ // best-effort status
245
+ }
246
+ }
247
+
248
+ async function safePost(binding: SlackBinding, text: string): Promise<void> {
249
+ try {
250
+ await binding.thread.post(text);
251
+ } catch {
252
+ // best-effort reply
253
+ }
254
+ }
255
+
256
+ /**
257
+ * Handle a parsed Slack eval directive against {@link HostEvalsApi}.
258
+ * Posts results in-thread; does not start an agent turn.
259
+ */
260
+ export async function handleSlackEvalDirective(args: {
261
+ directive: SlackEvalDirective;
262
+ evals: HostEvalsApi;
263
+ binding: SlackBinding;
264
+ /** Override poll interval (tests). */
265
+ pollMs?: number;
266
+ }): Promise<void> {
267
+ const { directive, evals, binding } = args;
268
+ const pollMs = args.pollMs ?? EVAL_POLL_MS;
269
+
270
+ if (directive.kind === "help") {
271
+ await safePost(binding, formatSlackEvalHelp());
272
+ return;
273
+ }
274
+
275
+ if (directive.kind === "list") {
276
+ const listed = await evals.list();
277
+ await safePost(binding, formatSlackEvalList(listed.evals));
278
+ return;
279
+ }
280
+
281
+ if (directive.kind === "status") {
282
+ const { runs, activeRunId } = await evals.listRunsWithActive();
283
+ const run =
284
+ (activeRunId === undefined ? undefined : await evals.get(activeRunId)) ??
285
+ runs[0];
286
+ if (run === undefined) {
287
+ await safePost(binding, "No eval runs yet. Try `eval list` then `eval`.");
288
+ return;
289
+ }
290
+ await safePost(binding, formatSlackEvalRunSummary(run));
291
+ return;
292
+ }
293
+
294
+ await safeTyping(binding, "Starting evals…");
295
+ let run: EvalRunSnapshot;
296
+ try {
297
+ run = await evals.start({
298
+ filterIds: directive.filterIds,
299
+ tags: directive.tags,
300
+ });
301
+ } catch (error) {
302
+ if (error instanceof EvalRunConflictError) {
303
+ const active = await evals.get(error.activeRunId);
304
+ await safePost(
305
+ binding,
306
+ active === undefined
307
+ ? `An eval run is already in progress (\`${error.activeRunId}\`).`
308
+ : `An eval run is already in progress:\n${formatSlackEvalRunSummary(active)}`
309
+ );
310
+ return;
311
+ }
312
+ if (error instanceof EvalRunEmptyError) {
313
+ await safePost(
314
+ binding,
315
+ "No matching evals. Try `eval list` or `eval help`."
316
+ );
317
+ return;
318
+ }
319
+ const message = error instanceof Error ? error.message : String(error);
320
+ await safePost(binding, `Failed to start evals: ${message}`);
321
+ return;
322
+ }
323
+
324
+ await safePost(
325
+ binding,
326
+ `Started eval run \`${run.runId}\` (${run.summary.total} case(s)).`
327
+ );
328
+
329
+ while (run.status === "running") {
330
+ await safeTyping(
331
+ binding,
332
+ `Evals ${run.summary.done}/${run.summary.total}…`
333
+ );
334
+ await sleep(pollMs);
335
+ const next = await evals.get(run.runId);
336
+ if (next === undefined) {
337
+ await safePost(
338
+ binding,
339
+ `Eval run \`${run.runId}\` disappeared before completion.`
340
+ );
341
+ return;
342
+ }
343
+ run = next;
344
+ }
345
+
346
+ await safePost(binding, formatSlackEvalRunSummary(run));
347
+ await safeTyping(binding, "Evals done.");
348
+ }
@@ -29,6 +29,7 @@ export * from "./cursor-account.js";
29
29
  export * from "./defaults.js";
30
30
  export * from "./dispatch.js";
31
31
  export * from "./doctor.js";
32
+ export * from "./eval-directive.js";
32
33
  export * from "./event-id-dedupe.js";
33
34
  export * from "./inbound.js";
34
35
  export * from "./init.js";
@@ -138,6 +138,7 @@ export function slackChannel<const Name extends string = string>(
138
138
  const engagement = options.engagement ?? {};
139
139
  const mentionsEnabled = engagement.mentions !== false;
140
140
  const directMessagesEnabled = engagement.directMessages !== false;
141
+ const evalCommandsEnabled = options.evalCommands !== false;
141
142
  const channelPosts = engagement.channelPosts;
142
143
  if (channelPosts !== undefined) {
143
144
  const entries = channelPosts.allow.filter((entry) => entry.trim() !== "");
@@ -204,6 +205,7 @@ export function slackChannel<const Name extends string = string>(
204
205
  log: getLog(),
205
206
  mentionsEnabled,
206
207
  directMessagesEnabled,
208
+ evalCommandsEnabled,
207
209
  ...(args.hasContinuationSession === undefined
208
210
  ? {}
209
211
  : { hasContinuationSession: args.hasContinuationSession }),
@@ -342,6 +344,7 @@ export function slackChannel<const Name extends string = string>(
342
344
  credentials,
343
345
  host: args.host,
344
346
  log: getLog(),
347
+ evalCommandsEnabled,
345
348
  ...(args.hasContinuationSession === undefined
346
349
  ? {}
347
350
  : {
@@ -382,6 +382,13 @@ export interface SlackChannelOptions {
382
382
  * entry at 50 chars). Defaults to Thinking… / Working on it… / Almost there….
383
383
  */
384
384
  loadingMessages?: string[];
385
+ /**
386
+ * Built-in Slack `eval` / `eval list` / `eval status` directive that runs
387
+ * playground eval batches (`host.evals` / `/v1/dev/evals/runs`) without an
388
+ * agent turn. Defaults to `true`. Set `false` to leave `eval …` for the
389
+ * model handler instead.
390
+ */
391
+ evalCommands?: boolean;
385
392
  /** Optional teardown (e.g. stop example-local debouncers). Runs after Socket Mode stops. */
386
393
  onStop?: () => void | Promise<void>;
387
394
  }
@@ -2,11 +2,9 @@
2
2
  * `defineConnection` — authored at `agent/mcp-connections/<name>.ts`.
3
3
  *
4
4
  * A connection gives tools from an external MCP server. The filename
5
- * becomes the connection (server) name. Every connection is always
6
- * available in three places:
7
- * - the Cursor agent (local harness or cloud), via SDK `mcpServers`
8
- * - the agent-serve host, via `ctx.host.mcp` on in-process tools
9
- * - channel / schedule handlers, via `args.host.mcp`
5
+ * becomes the connection (server) name. Available to the agent (SDK
6
+ * `mcpServers`), host (`ctx.host.mcp`), and channel/schedule handlers.
7
+ * Pass `hostOnly: true` to keep it off the model (still on `host.mcp`).
10
8
  *
11
9
  * ```ts
12
10
  * import { defineConnection } from "@anysphere/agent-serve/connections";
@@ -15,6 +13,13 @@
15
13
  * url: "https://mcp.linear.app/mcp",
16
14
  * headers: { authorization: `Bearer ${process.env.LINEAR_TOKEN}` },
17
15
  * });
16
+ *
17
+ * // privileged connector, model cannot call directly:
18
+ * export default defineConnection({
19
+ * cursorAccount: true,
20
+ * servers: ["anytool"],
21
+ * hostOnly: true,
22
+ * });
18
23
  * ```
19
24
  *
20
25
  * A **Cursor account MCP connection** exposes the MCP connectors the operator