@cursor/july 0.1.42 → 0.1.45

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 (223) hide show
  1. package/AGENTS.md +1 -0
  2. package/dist/bin/agent-serve.js +3 -1
  3. package/dist/channels/github/api.d.ts +5 -0
  4. package/dist/channels/github/api.d.ts.map +1 -1
  5. package/dist/channels/github/api.js +18 -0
  6. package/dist/channels/github/defaults.d.ts +4 -5
  7. package/dist/channels/github/defaults.d.ts.map +1 -1
  8. package/dist/channels/github/defaults.js +27 -1
  9. package/dist/channels/github/github-channel.d.ts.map +1 -1
  10. package/dist/channels/github/github-channel.js +41 -10
  11. package/dist/channels/github/index.d.ts +1 -0
  12. package/dist/channels/github/index.d.ts.map +1 -1
  13. package/dist/channels/github/index.js +1 -0
  14. package/dist/channels/github/progress.d.ts +54 -0
  15. package/dist/channels/github/progress.d.ts.map +1 -0
  16. package/dist/channels/github/progress.js +168 -0
  17. package/dist/channels/github/state.d.ts +2 -0
  18. package/dist/channels/github/state.d.ts.map +1 -1
  19. package/dist/channels/github/state.js +1 -1
  20. package/dist/channels/github/types.d.ts +16 -4
  21. package/dist/channels/github/types.d.ts.map +1 -1
  22. package/dist/channels/slack/slack-channel.d.ts +4 -0
  23. package/dist/channels/slack/slack-channel.d.ts.map +1 -1
  24. package/dist/channels/slack/slack-channel.js +4 -0
  25. package/dist/channels.d.ts +17 -0
  26. package/dist/channels.d.ts.map +1 -1
  27. package/dist/channels.js +31 -0
  28. package/dist/docs/404.html +2 -2
  29. package/dist/docs/ab.html +4 -4
  30. package/dist/docs/assets/{app.C4-Gwi7g.js → app.fHnNXEJ6.js} +1 -1
  31. package/dist/docs/assets/chunks/@localSearchIndexroot.BMT0xM4I.js +1 -0
  32. package/dist/docs/assets/chunks/{VPLocalSearchBox.DDoLm843.js → VPLocalSearchBox.Cf6ghfFY.js} +1 -1
  33. package/dist/docs/assets/chunks/{theme.SbjGIiM0.js → theme.B_5-VLe8.js} +2 -2
  34. package/dist/docs/assets/{deployment.md.BHVjVqRa.js → deployment.md.Cdp4rTE4.js} +1 -1
  35. package/dist/docs/assets/{guides_github.md.R2QlpR75.js → guides_github.md.DSg0j9qj.js} +29 -3
  36. package/dist/docs/assets/{guides_github.md.R2QlpR75.lean.js → guides_github.md.DSg0j9qj.lean.js} +1 -1
  37. package/dist/docs/assets/guides_opentelemetry.md.Csn7ZI25.js +27 -0
  38. package/dist/docs/assets/guides_opentelemetry.md.Csn7ZI25.lean.js +1 -0
  39. package/dist/docs/assets/index.md.COeoSkcF.js +5 -0
  40. package/dist/docs/assets/index.md.COeoSkcF.lean.js +1 -0
  41. package/dist/docs/assets/{quickstart.md.rIbfahJM.js → quickstart.md.DZxBu44y.js} +18 -49
  42. package/dist/docs/assets/quickstart.md.DZxBu44y.lean.js +1 -0
  43. package/dist/docs/assets/{reference_agent-config.md.CRmkoxd6.js → reference_agent-config.md.XRpxctsA.js} +9 -4
  44. package/dist/docs/assets/{reference_agent-config.md.CRmkoxd6.lean.js → reference_agent-config.md.XRpxctsA.lean.js} +1 -1
  45. package/dist/docs/assets/{reference_channels.md.BIabFUAI.js → reference_channels.md.MN8iDy8V.js} +1 -1
  46. package/dist/docs/assets/{reference_cli.md.DqEqQpGn.js → reference_cli.md.BDmjPPAm.js} +2 -2
  47. package/dist/docs/assets/{reference_hooks.md.B40QAG6m.js → reference_hooks.md.4cbRRIIu.js} +2 -2
  48. package/dist/docs/assets/{reference_hooks.md.B40QAG6m.lean.js → reference_hooks.md.4cbRRIIu.lean.js} +1 -1
  49. package/dist/docs/assets/{reference_project-layout.md._XdeMahr.js → reference_project-layout.md.D3MdHM2z.js} +2 -1
  50. package/dist/docs/assets/{reference_tools.md.lSrsTxYJ.js → reference_tools.md.kJfJAP2m.js} +1 -1
  51. package/dist/docs/assets/templates_triage.md.CConZt1z.js +11 -0
  52. package/dist/docs/assets/templates_triage.md.CConZt1z.lean.js +1 -0
  53. package/dist/docs/building-with-agents.html +5 -5
  54. package/dist/docs/concepts.html +5 -5
  55. package/dist/docs/deployment.html +6 -6
  56. package/dist/docs/evals.html +4 -4
  57. package/dist/docs/example-agents/approval-buddy.html +4 -4
  58. package/dist/docs/example-agents/benny.html +4 -4
  59. package/dist/docs/example-agents/bugbot.html +4 -4
  60. package/dist/docs/example-agents/codebase-wiki.html +4 -4
  61. package/dist/docs/example-agents/codeowners-review.html +4 -4
  62. package/dist/docs/example-agents/concierge.html +4 -4
  63. package/dist/docs/example-agents/fsd.html +4 -4
  64. package/dist/docs/example-agents/index.html +5 -5
  65. package/dist/docs/example-agents/knowledge-base.html +4 -4
  66. package/dist/docs/example-agents/oncall.html +4 -4
  67. package/dist/docs/example-agents/security-reviewer.html +4 -4
  68. package/dist/docs/example-agents/slack-agent.html +4 -4
  69. package/dist/docs/example-agents/weather-agent.html +4 -4
  70. package/dist/docs/guides/agent-to-agent.html +4 -4
  71. package/dist/docs/guides/cloud-runtime.html +5 -5
  72. package/dist/docs/guides/github.html +33 -7
  73. package/dist/docs/guides/human-in-the-loop.html +4 -4
  74. package/dist/docs/guides/mcp-oauth.html +4 -4
  75. package/dist/docs/guides/opentelemetry.html +51 -0
  76. package/dist/docs/guides/slack.html +4 -4
  77. package/dist/docs/guides/webhooks.html +4 -4
  78. package/dist/docs/hashmap.json +1 -1
  79. package/dist/docs/hillclimbing.html +4 -4
  80. package/dist/docs/index.html +8 -27
  81. package/dist/docs/quickstart.html +24 -55
  82. package/dist/docs/reference/agent-config.html +12 -7
  83. package/dist/docs/reference/artifacts.html +4 -4
  84. package/dist/docs/reference/channels.html +6 -6
  85. package/dist/docs/reference/cli.html +7 -7
  86. package/dist/docs/reference/connections.html +4 -4
  87. package/dist/docs/reference/hooks.html +6 -6
  88. package/dist/docs/reference/http-api.html +4 -4
  89. package/dist/docs/reference/instructions.html +4 -4
  90. package/dist/docs/reference/playground.html +4 -4
  91. package/dist/docs/reference/project-layout.html +7 -6
  92. package/dist/docs/reference/prompt.html +4 -4
  93. package/dist/docs/reference/schedules.html +4 -4
  94. package/dist/docs/reference/sessions.html +4 -4
  95. package/dist/docs/reference/skills.html +4 -4
  96. package/dist/docs/reference/subagents.html +4 -4
  97. package/dist/docs/reference/tools.html +6 -6
  98. package/dist/docs/scaffolding-agents.html +4 -4
  99. package/dist/docs/storage.html +4 -4
  100. package/dist/docs/templates/triage.html +35 -0
  101. package/dist/docs/troubleshooting.html +4 -4
  102. package/dist/index.d.ts +3 -1
  103. package/dist/index.d.ts.map +1 -1
  104. package/dist/index.js +2 -1
  105. package/dist/internal/cli-ax.d.ts +1 -0
  106. package/dist/internal/cli-ax.d.ts.map +1 -1
  107. package/dist/internal/cli-ax.js +20 -5
  108. package/dist/internal/cli-deploy.d.ts.map +1 -1
  109. package/dist/internal/cli-deploy.js +38 -7
  110. package/dist/internal/deploy-client.d.ts +8 -0
  111. package/dist/internal/deploy-client.d.ts.map +1 -1
  112. package/dist/internal/deploy-client.js +5 -2
  113. package/dist/internal/discovery.d.ts.map +1 -1
  114. package/dist/internal/discovery.js +82 -13
  115. package/dist/internal/distribution.d.ts.map +1 -1
  116. package/dist/internal/distribution.js +1 -0
  117. package/dist/internal/host-otel.d.ts +25 -0
  118. package/dist/internal/host-otel.d.ts.map +1 -0
  119. package/dist/internal/host-otel.js +162 -0
  120. package/dist/internal/init-project.d.ts +24 -0
  121. package/dist/internal/init-project.d.ts.map +1 -1
  122. package/dist/internal/init-project.js +136 -17
  123. package/dist/internal/otel/attrs.d.ts +7 -0
  124. package/dist/internal/otel/attrs.d.ts.map +1 -0
  125. package/dist/internal/otel/attrs.js +13 -0
  126. package/dist/internal/otel/collector.d.ts +21 -0
  127. package/dist/internal/otel/collector.d.ts.map +1 -0
  128. package/dist/internal/otel/collector.js +605 -0
  129. package/dist/internal/otel/config.d.ts +42 -0
  130. package/dist/internal/otel/config.d.ts.map +1 -0
  131. package/dist/internal/otel/config.js +236 -0
  132. package/dist/internal/otel/http.d.ts +11 -0
  133. package/dist/internal/otel/http.d.ts.map +1 -0
  134. package/dist/internal/otel/http.js +97 -0
  135. package/dist/internal/otel/runtime.d.ts +21 -0
  136. package/dist/internal/otel/runtime.d.ts.map +1 -0
  137. package/dist/internal/otel/runtime.js +188 -0
  138. package/dist/internal/sdk-runner.d.ts +1 -1
  139. package/dist/internal/sdk-runner.d.ts.map +1 -1
  140. package/dist/internal/sdk-runner.js +1 -1
  141. package/dist/internal/server.d.ts.map +1 -1
  142. package/dist/internal/server.js +26 -1
  143. package/dist/internal/session-engine.d.ts +15 -2
  144. package/dist/internal/session-engine.d.ts.map +1 -1
  145. package/dist/internal/session-engine.js +58 -7
  146. package/dist/memory.d.ts +4 -0
  147. package/dist/memory.d.ts.map +1 -1
  148. package/dist/memory.js +4 -0
  149. package/dist/otel.d.ts +88 -0
  150. package/dist/otel.d.ts.map +1 -0
  151. package/dist/otel.js +64 -0
  152. package/dist/playground/assets/index-CZH9EFvO.css +1 -0
  153. package/dist/playground/assets/{index-B3HBneZZ.js → index-Dces8BDp.js} +34 -34
  154. package/dist/playground/index.html +2 -2
  155. package/dist/types.d.ts +103 -3
  156. package/dist/types.d.ts.map +1 -1
  157. package/docs/README.md +35 -105
  158. package/docs/deployment.md +2 -1
  159. package/docs/guides/github.md +79 -6
  160. package/docs/guides/opentelemetry.md +214 -0
  161. package/docs/quickstart.md +89 -227
  162. package/docs/reference/agent-config.md +52 -0
  163. package/docs/reference/channels.md +3 -1
  164. package/docs/reference/cli.md +2 -2
  165. package/docs/reference/hooks.md +21 -4
  166. package/docs/reference/project-layout.md +2 -0
  167. package/docs/reference/tools.md +5 -4
  168. package/docs/templates/triage.md +147 -0
  169. package/package.json +24 -2
  170. package/skills/create-agent/SKILL.md +2 -2
  171. package/skills/framework-map/SKILL.md +2 -0
  172. package/skills/otel/SKILL.md +98 -0
  173. package/src/bin/agent-serve.ts +3 -1
  174. package/src/channels/github/api.ts +22 -0
  175. package/src/channels/github/defaults.ts +27 -6
  176. package/src/channels/github/github-channel.ts +36 -1
  177. package/src/channels/github/index.ts +1 -0
  178. package/src/channels/github/progress.ts +243 -0
  179. package/src/channels/github/state.ts +3 -0
  180. package/src/channels/github/types.ts +12 -4
  181. package/src/channels/slack/slack-channel.ts +4 -0
  182. package/src/channels.ts +38 -0
  183. package/src/index.ts +7 -0
  184. package/src/internal/cli-ax.ts +21 -3
  185. package/src/internal/cli-deploy.ts +58 -3
  186. package/src/internal/deploy-client.ts +11 -0
  187. package/src/internal/discovery.ts +108 -4
  188. package/src/internal/distribution.ts +1 -0
  189. package/src/internal/host-otel.ts +252 -0
  190. package/src/internal/init-project.ts +160 -17
  191. package/src/internal/otel/attrs.ts +18 -0
  192. package/src/internal/otel/collector.ts +881 -0
  193. package/src/internal/otel/config.ts +307 -0
  194. package/src/internal/otel/http.ts +126 -0
  195. package/src/internal/otel/runtime.ts +271 -0
  196. package/src/internal/sdk-runner.ts +6 -0
  197. package/src/internal/server.ts +39 -1
  198. package/src/internal/session-engine.ts +83 -2
  199. package/src/memory.ts +4 -0
  200. package/src/otel.ts +155 -0
  201. package/src/types.ts +109 -3
  202. package/templates/triage/README.md +48 -0
  203. package/templates/triage/agent/agent.ts +14 -0
  204. package/templates/triage/agent/channels/intake.ts +20 -0
  205. package/templates/triage/agent/channels/slack.ts +3 -0
  206. package/templates/triage/agent/hooks/memory.ts +3 -0
  207. package/templates/triage/agent/instructions.md +27 -0
  208. package/templates/triage/agent/skills/triage-rubric.md +43 -0
  209. package/templates/triage/agent/tools/record_triage.ts +23 -0
  210. package/templates/triage/evals/evals.config.ts +5 -0
  211. package/templates/triage/evals/triage.eval.ts +43 -0
  212. package/templates/triage/package.json +18 -0
  213. package/templates/triage/tsconfig.json +12 -0
  214. package/dist/docs/assets/chunks/@localSearchIndexroot.BlU3A8EB.js +0 -1
  215. package/dist/docs/assets/index.md.BKpbkCa6.js +0 -24
  216. package/dist/docs/assets/index.md.BKpbkCa6.lean.js +0 -1
  217. package/dist/docs/assets/quickstart.md.rIbfahJM.lean.js +0 -1
  218. package/dist/playground/assets/index-Bqk9nplh.css +0 -1
  219. /package/dist/docs/assets/{deployment.md.BHVjVqRa.lean.js → deployment.md.Cdp4rTE4.lean.js} +0 -0
  220. /package/dist/docs/assets/{reference_channels.md.BIabFUAI.lean.js → reference_channels.md.MN8iDy8V.lean.js} +0 -0
  221. /package/dist/docs/assets/{reference_cli.md.DqEqQpGn.lean.js → reference_cli.md.BDmjPPAm.lean.js} +0 -0
  222. /package/dist/docs/assets/{reference_project-layout.md._XdeMahr.lean.js → reference_project-layout.md.D3MdHM2z.lean.js} +0 -0
  223. /package/dist/docs/assets/{reference_tools.md.lSrsTxYJ.lean.js → reference_tools.md.kJfJAP2m.lean.js} +0 -0
@@ -52,6 +52,7 @@ export type RunnerPrewarmRequest = Pick<
52
52
  | "workspaceDir"
53
53
  | "runnerStateDir"
54
54
  | "sandbox"
55
+ | "tools"
55
56
  | "mcpServers"
56
57
  | "subagents"
57
58
  | "model"
@@ -373,6 +374,11 @@ export class CursorSdkRunner implements AgentRunner {
373
374
  this.options.store ?? (await this.getStore(request.runnerStateDir));
374
375
  return {
375
376
  ...this.buildSharedOptions(request),
377
+ // The built-in tool allowlist rides AgentOptions, not `local`, and is
378
+ // deliberately absent from openCloudAgent's options: the SDK rejects
379
+ // tool restrictions on cloud agents. Passed on create *and* resume
380
+ // (both flow through here) — the SDK does not persist it across runs.
381
+ ...(request.tools === undefined ? {} : { tools: request.tools }),
376
382
  local: {
377
383
  cwd: request.workspaceDir,
378
384
  settingSources: ["project"],
@@ -112,6 +112,10 @@ import {
112
112
  TOOL_MCP_ENDPOINT_PATH,
113
113
  } from "./mcp-endpoint.js";
114
114
  import { isLoopbackAddress } from "./net.js";
115
+ import { createOtelCollector, type OtelCollector } from "./otel/collector.js";
116
+ import { pickAuthoredOtel, resolveOtelConfig } from "./otel/config.js";
117
+ import { instrumentHttp } from "./otel/http.js";
118
+ import { createOtelRuntime, shutdownOtelRuntime } from "./otel/runtime.js";
115
119
  import { collectPeerConnections } from "./peer-connections.js";
116
120
  import {
117
121
  PLATFORM_SCHEDULE_CATALOG_FILENAME,
@@ -222,6 +226,30 @@ export async function startServer(
222
226
  // agentkit_enabled gate applies to every turn.
223
227
  process.env.CURSOR_AGENTKIT_VERSION ??= packageVersion();
224
228
 
229
+ const authoredOtel = pickAuthoredOtel(
230
+ mounts.map((m) => ({
231
+ slug: m.slug,
232
+ name: m.project.name,
233
+ otel: m.project.otel,
234
+ }))
235
+ );
236
+ const otelRuntime = createOtelRuntime(
237
+ resolveOtelConfig({
238
+ authored: authoredOtel,
239
+ serve: options.otel,
240
+ }),
241
+ logger
242
+ );
243
+ if (
244
+ otelRuntime === undefined &&
245
+ options.otel !== false &&
246
+ authoredOtel !== undefined
247
+ ) {
248
+ logger(
249
+ "[agent-sdk] otel: agent/otel.ts present but no OTLP exporters configured (set exporters or OTEL_EXPORTER_OTLP_ENDPOINT)"
250
+ );
251
+ }
252
+
225
253
  const cursorGithubTargets = collectCursorGithubTargets(mounts);
226
254
  const cursorGithubConfig = resolveCursorGithubEventConfig({
227
255
  configured: options.cursorEvents,
@@ -396,12 +424,18 @@ export async function startServer(
396
424
  const baseStateRoot =
397
425
  options.stateRoot ?? join(project.rootDir, ".agent-serve");
398
426
  const stateRoot = slug === "" ? baseStateRoot : join(baseStateRoot, slug);
427
+ const otelCollector: OtelCollector | undefined =
428
+ otelRuntime === undefined
429
+ ? undefined
430
+ : createOtelCollector(otelRuntime, project.name, logger);
399
431
  const engine = new SessionEngine({
400
432
  project,
401
433
  stateRoot,
402
434
  runner,
403
435
  logger,
404
436
  platforms: hostPlatforms,
437
+ otel: otelCollector,
438
+ otelMeter: otelRuntime?.meter,
405
439
  });
406
440
  // Rehydrate sessions from the authored storage sink before any
407
441
  // channel or route can address them (continuation tokens must resolve).
@@ -696,7 +730,9 @@ export async function startServer(
696
730
  });
697
731
 
698
732
  const server = createServer((req, res) => {
699
- void handleRequest(router, req, res, logger);
733
+ void instrumentHttp(otelRuntime, req, res, () =>
734
+ handleRequest(router, req, res, logger)
735
+ );
700
736
  });
701
737
 
702
738
  const host = options.host ?? "127.0.0.1";
@@ -715,6 +751,7 @@ export async function startServer(
715
751
  await cursorEventRelay?.stop();
716
752
  await cursorSlackRelay?.stop();
717
753
  cursorGithubCredentials?.stop();
754
+ await shutdownOtelRuntime(otelRuntime);
718
755
  throw error;
719
756
  }
720
757
  const address = server.address() as AddressInfo;
@@ -900,6 +937,7 @@ export async function startServer(
900
937
  await Promise.all(runtimes.map((runtime) => runtime.engine.close()));
901
938
  } finally {
902
939
  cursorGithubCredentials?.stop();
940
+ await shutdownOtelRuntime(otelRuntime);
903
941
  }
904
942
  },
905
943
  };
@@ -10,6 +10,7 @@
10
10
  import { mkdir, rm, writeFile } from "node:fs/promises";
11
11
  import { dirname, join } from "node:path";
12
12
  import type { SDKCustomTool, SDKCustomToolResult } from "@cursor/sdk";
13
+ import type { Meter } from "@opentelemetry/api";
13
14
  import {
14
15
  type ABDefinition,
15
16
  mergeABInstructions,
@@ -95,6 +96,7 @@ import { TurnEventMapper } from "./event-mapper.js";
95
96
  import { selectFrameworkStorageDefinition } from "./framework-storage-selection.js";
96
97
  import { createHostFilesApi } from "./host-files.js";
97
98
  import { createHostKvApi } from "./host-kv.js";
99
+ import { createHostOtelApi, type HostOtelSessionApi } from "./host-otel.js";
98
100
  import {
99
101
  createHostPlatforms,
100
102
  createHostSlackClient,
@@ -109,6 +111,7 @@ import {
109
111
  import { validateToolInput } from "./json-schema.js";
110
112
  import { LedgerServing, newLedgerPodId } from "./ledger-serving.js";
111
113
  import { McpHost } from "./mcp-host.js";
114
+ import type { OtelCollector } from "./otel/collector.js";
112
115
  import {
113
116
  buildSessionContextBlock,
114
117
  SCHEDULE_CHANNEL_PREFIX,
@@ -243,6 +246,10 @@ export interface SessionEngineOptions {
243
246
  * *without* `cas` fails fast. Ignored for resident-mode agents.
244
247
  */
245
248
  durableSessionStore?: HostKvApi;
249
+ /** Process-level OTel collector; observe-only, never fatal. */
250
+ otel?: OtelCollector;
251
+ /** Process-level meter for {@link HostContext.otel}. Absent → no-op counters. */
252
+ otelMeter?: Meter;
246
253
  }
247
254
 
248
255
  /**
@@ -358,6 +365,8 @@ export class SessionEngine {
358
365
  private readonly platforms: HostPlatforms;
359
366
  private readonly approvals = new ApprovalGate();
360
367
  private readonly abCollector: ABCollector;
368
+ private readonly otel: OtelCollector | undefined;
369
+ private readonly otelHost: HostOtelSessionApi;
361
370
  private reminderApi: HostContext["reminders"];
362
371
  private evalsApi: HostContext["evals"];
363
372
  private readonly kvApi: HostContext["kv"];
@@ -390,6 +399,12 @@ export class SessionEngine {
390
399
  this.logger = options.logger ?? (() => {});
391
400
  this.getCloudUsage = options.getCloudUsage ?? defaultGetCloudUsage;
392
401
  this.estimateTurnCost = options.estimateTurnCost ?? estimateTurnCostFromApi;
402
+ this.otel = options.otel;
403
+ this.otelHost = createHostOtelApi({
404
+ agentName: options.project.name,
405
+ meter: options.otelMeter,
406
+ collector: options.otel,
407
+ });
393
408
  this.turnGovernor = new TurnGovernor(options.project.agent.concurrency);
394
409
  this.sessions = new SessionStore(join(options.stateRoot, "sessions"));
395
410
  this.nudgeStore = new SlackNudgeStore(
@@ -569,7 +584,7 @@ export class SessionEngine {
569
584
  this.cloudServerToolsConnection = connection;
570
585
  }
571
586
 
572
- /** Shared host services (MCP / GitHub / Slack / reminders / evals / kv / Slack nudges). */
587
+ /** Shared host services (MCP / GitHub / Slack / reminders / evals / kv / files / otel / Slack nudges). */
573
588
  get host(): HostContext {
574
589
  return {
575
590
  mcp: this.mcpHost,
@@ -579,6 +594,7 @@ export class SessionEngine {
579
594
  evals: this.evalsApi,
580
595
  kv: this.kvApi,
581
596
  files: this.filesApi,
597
+ otel: this.otelHost,
582
598
  slackNudges: this.slackNudgeHostApi(),
583
599
  };
584
600
  }
@@ -593,7 +609,11 @@ export class SessionEngine {
593
609
  const host = this.host;
594
610
  return sessionId === undefined
595
611
  ? host
596
- : { ...host, files: this.filesApi.forSession(sessionId) };
612
+ : {
613
+ ...host,
614
+ files: this.filesApi.forSession(sessionId),
615
+ otel: this.otelHost.forSession(sessionId),
616
+ };
597
617
  }
598
618
 
599
619
  /** Unbound `ctx.artifacts` facade (channel handlers, scratch tool calls). */
@@ -775,6 +795,17 @@ export class SessionEngine {
775
795
  cloudOverride: options.cloud,
776
796
  }));
777
797
  }
798
+ // Refresh wake-derived channel state (GitHub headSha / refs) without
799
+ // wiping sticky fields. Null patch values are skipped.
800
+ if (options.refreshState !== undefined) {
801
+ record = await this.updateRecord(record.sessionId, (current) => ({
802
+ ...current,
803
+ channelState: mergeChannelStateSkipNull(
804
+ current.channelState,
805
+ options.refreshState
806
+ ),
807
+ }));
808
+ }
778
809
  const coalesced = await this.startTurn(record.sessionId, message, {
779
810
  admission: options.admission ?? "preempt",
780
811
  ...(options.coalesceSourceTs === undefined
@@ -1343,6 +1374,9 @@ export class SessionEngine {
1343
1374
  workspaceDir,
1344
1375
  runnerStateDir: join(this.stateRoot, "runner"),
1345
1376
  sandbox: agent.local?.sandbox === true,
1377
+ ...(agent.allowedTools === undefined
1378
+ ? {}
1379
+ : { tools: agent.allowedTools }),
1346
1380
  mcpServers: this.buildMcpServers("local"),
1347
1381
  subagents: buildRunnerSubagents(agent),
1348
1382
  ...(agent.model === undefined ? {} : { model: agent.model }),
@@ -1598,6 +1632,15 @@ export class SessionEngine {
1598
1632
  agentRuntime: agent.runtime,
1599
1633
  cloudOverride: record.cloudOverride,
1600
1634
  });
1635
+ // Fail closed: the SDK cannot enforce the built-in tool allowlist on
1636
+ // cloud agents yet, and a restriction that silently does not apply is
1637
+ // worse than a refused turn. Cloud-runtime agents already fail
1638
+ // discovery; this guards hybrid agents' per-send cloud sessions.
1639
+ if (runtime === "cloud" && agent.allowedTools !== undefined) {
1640
+ throw new Error(
1641
+ `Agent "${agent.name}" declares a built-in tool allowlist (tools), which cannot be enforced on cloud turns yet. Refusing the cloud session rather than running it unrestricted.`
1642
+ );
1643
+ }
1601
1644
  const mergedCloud =
1602
1645
  runtime === "cloud"
1603
1646
  ? mergeCloudOptions({
@@ -1652,6 +1695,9 @@ export class SessionEngine {
1652
1695
  : { sdkAgentId: record.sdkAgentId }),
1653
1696
  workspaceDir: this.harnessCwd(record),
1654
1697
  sandbox: this.project.agent.local?.sandbox === true,
1698
+ ...(runtime === "local" && agent.allowedTools !== undefined
1699
+ ? { tools: agent.allowedTools }
1700
+ : {}),
1655
1701
  runnerStateDir: join(this.stateRoot, "runner"),
1656
1702
  prompt: turnPrompt,
1657
1703
  isFirstTurn,
@@ -2457,6 +2503,8 @@ export class SessionEngine {
2457
2503
  this.project.name
2458
2504
  );
2459
2505
  }
2506
+
2507
+ this.otel?.onEvent(event, this.sessionInfo(record));
2460
2508
  }
2461
2509
 
2462
2510
  private channelEventHandlersFor(
@@ -3352,6 +3400,7 @@ export class SessionEngine {
3352
3400
  await this.storage?.close();
3353
3401
  await this.mcpHost.close();
3354
3402
  await this.runner.dispose?.();
3403
+ this.otel?.close();
3355
3404
  }
3356
3405
  }
3357
3406
 
@@ -3385,6 +3434,38 @@ function mergePendingWorkspaceFiles(
3385
3434
  return { ...current, ...incoming };
3386
3435
  }
3387
3436
 
3437
+ /**
3438
+ * Shallow-merge a wake patch into durable channel state. Null / undefined
3439
+ * patch values leave the existing key alone so sticky fields survive.
3440
+ */
3441
+ export function mergeChannelStateSkipNull(
3442
+ current: JsonValue | undefined,
3443
+ patch: JsonValue | undefined
3444
+ ): JsonValue | undefined {
3445
+ if (patch === undefined) {
3446
+ return current;
3447
+ }
3448
+ if (
3449
+ typeof patch !== "object" ||
3450
+ patch === null ||
3451
+ Array.isArray(patch) ||
3452
+ typeof current !== "object" ||
3453
+ current === null ||
3454
+ Array.isArray(current)
3455
+ ) {
3456
+ return structuredClone(patch);
3457
+ }
3458
+ const merged: Record<string, JsonValue> = {
3459
+ ...(current as Record<string, JsonValue>),
3460
+ };
3461
+ for (const [key, value] of Object.entries(patch)) {
3462
+ if (value !== null && value !== undefined) {
3463
+ merged[key] = value as JsonValue;
3464
+ }
3465
+ }
3466
+ return merged;
3467
+ }
3468
+
3388
3469
  function eventsNdjson(events: SessionEvent[]): string | undefined {
3389
3470
  if (events.length === 0) {
3390
3471
  return undefined;
package/src/memory.ts CHANGED
@@ -237,6 +237,10 @@ function truncate(text: string, max: number): string {
237
237
  * import { memoryHook } from "@cursor/july/memory";
238
238
  * export default memoryHook();
239
239
  * ```
240
+ *
241
+ * The journal is shared across every session — and every user — of the
242
+ * agent. Pass your own {@link MemoryHookOptions.backend} to change where
243
+ * memories live; delete the hook file to opt out entirely.
240
244
  */
241
245
  export function memoryHook(options: MemoryHookOptions = {}): HookDefinition {
242
246
  const backend = options.backend ?? defaultMemoryBackend();
package/src/otel.ts ADDED
@@ -0,0 +1,155 @@
1
+ /**
2
+ * OpenTelemetry for Agent SDK — author `agent/otel.ts`, pass `serve({ otel })`,
3
+ * or set `OTEL_EXPORTER_OTLP_ENDPOINT` (standard OTel env).
4
+ *
5
+ * ```ts
6
+ * import { defineOtel } from "@cursor/july/otel";
7
+ *
8
+ * export default defineOtel({
9
+ * serviceName: "cursor",
10
+ * exporters: [{ url: "https://otel.example.com", protocol: "http/protobuf" }],
11
+ * });
12
+ * ```
13
+ */
14
+
15
+ import { brandDefinition } from "./internal/brand.js";
16
+
17
+ export type OtelSignal = "traces" | "metrics" | "logs";
18
+
19
+ /** One OTLP destination. Multiple entries fan out. */
20
+ export interface OtelExporterConfig {
21
+ /**
22
+ * OTLP base URL (`https://otel.example.com`) or signal path
23
+ * (`…/v1/traces`). Signal paths are stripped to the base; the runtime
24
+ * re-appends `/v1/{signal}`.
25
+ */
26
+ url: string;
27
+ /** Extra HTTP headers (merged with `OTEL_EXPORTER_OTLP_HEADERS`). */
28
+ headers?: Record<string, string>;
29
+ /**
30
+ * Wire format. Default `http/protobuf` (matches Cursor enterprise OTLP).
31
+ * Use `http/json` for collectors that only accept JSON.
32
+ */
33
+ protocol?: "http/json" | "http/protobuf";
34
+ /**
35
+ * Restrict this destination to these signals. Default: all enabled
36
+ * signals. Set by env when per-signal `OTEL_EXPORTER_OTLP_*_ENDPOINT`
37
+ * vars differ.
38
+ */
39
+ signals?: OtelSignal[];
40
+ }
41
+
42
+ export interface OtelTraceOptions {
43
+ /** Sample ratio in `[0, 1]`. Default `1`. */
44
+ sampleRatio?: number;
45
+ }
46
+
47
+ export interface OtelMetricOptions {
48
+ /** Export interval in ms. Default `10000`. */
49
+ exportIntervalMillis?: number;
50
+ }
51
+
52
+ export interface OtelLogOptions {
53
+ /**
54
+ * Include user prompt text on log records / span events.
55
+ * Default false; also toggled by `OTEL_LOG_USER_PROMPTS=1`.
56
+ */
57
+ userPrompts?: boolean;
58
+ /**
59
+ * Include tool argument / result payloads and failure text (truncated).
60
+ * Default false; also `OTEL_LOG_TOOL_CONTENT=1`.
61
+ */
62
+ toolContent?: boolean;
63
+ }
64
+
65
+ /**
66
+ * Authored / programmatic OTel config. Empty config still enables when
67
+ * standard `OTEL_EXPORTER_OTLP_*` env vars are set.
68
+ */
69
+ export interface OtelConfig {
70
+ /**
71
+ * Resource `service.name`. Default: `OTEL_SERVICE_NAME` or `"cursor"`
72
+ * (same as enterprise export) so Agent SDK signals join org streams.
73
+ */
74
+ serviceName?: string;
75
+ /** Resource `service.version`. */
76
+ serviceVersion?: string;
77
+ /** Extra resource attributes on every signal. */
78
+ resourceAttributes?: Record<string, string>;
79
+ /**
80
+ * OTLP destinations. Default: from `OTEL_EXPORTER_OTLP_ENDPOINT` /
81
+ * per-signal `*_TRACES_ENDPOINT` etc. Empty with no env = disabled.
82
+ */
83
+ exporters?: OtelExporterConfig[];
84
+ /** Traces on/off or options. Default on. */
85
+ traces?: boolean | OtelTraceOptions;
86
+ /** Metrics on/off or options. Default on. */
87
+ metrics?: boolean | OtelMetricOptions;
88
+ /** Logs on/off or options. Default off (opt-in content). */
89
+ logs?: boolean | OtelLogOptions;
90
+ }
91
+
92
+ export type OtelDefinition = OtelConfig & { readonly __agentServe: "otel" };
93
+
94
+ /** Author OpenTelemetry export under `agent/otel.ts`. */
95
+ export function defineOtel(config: OtelConfig = {}): OtelDefinition {
96
+ if (config.exporters !== undefined) {
97
+ for (const exporter of config.exporters) {
98
+ if (typeof exporter.url !== "string" || exporter.url.length === 0) {
99
+ throw new Error(
100
+ "defineOtel: exporters[].url must be a non-empty string"
101
+ );
102
+ }
103
+ if (
104
+ exporter.protocol !== undefined &&
105
+ exporter.protocol !== "http/json" &&
106
+ exporter.protocol !== "http/protobuf"
107
+ ) {
108
+ throw new Error(
109
+ 'defineOtel: exporters[].protocol must be "http/json" or "http/protobuf"'
110
+ );
111
+ }
112
+ assertSignals(exporter.signals);
113
+ }
114
+ }
115
+ if (config.resourceAttributes !== undefined) {
116
+ for (const [key, value] of Object.entries(config.resourceAttributes)) {
117
+ if (typeof value !== "string") {
118
+ throw new Error(
119
+ `defineOtel: resourceAttributes.${key} must be a string`
120
+ );
121
+ }
122
+ }
123
+ }
124
+ assertSampleRatio(config.traces);
125
+ return brandDefinition("otel", config);
126
+ }
127
+
128
+ function assertSignals(signals: OtelSignal[] | undefined): void {
129
+ if (signals === undefined) {
130
+ return;
131
+ }
132
+ if (signals.length === 0) {
133
+ throw new Error("defineOtel: exporters[].signals must be non-empty");
134
+ }
135
+ for (const signal of signals) {
136
+ if (signal !== "traces" && signal !== "metrics" && signal !== "logs") {
137
+ throw new Error(
138
+ 'defineOtel: exporters[].signals must be "traces" | "metrics" | "logs"'
139
+ );
140
+ }
141
+ }
142
+ }
143
+
144
+ function assertSampleRatio(traces: OtelConfig["traces"]): void {
145
+ if (traces === undefined || typeof traces === "boolean") {
146
+ return;
147
+ }
148
+ const ratio = traces.sampleRatio;
149
+ if (ratio === undefined) {
150
+ return;
151
+ }
152
+ if (!Number.isFinite(ratio) || ratio < 0 || ratio > 1) {
153
+ throw new Error("defineOtel: traces.sampleRatio must be in [0, 1]");
154
+ }
155
+ }
package/src/types.ts CHANGED
@@ -8,9 +8,10 @@
8
8
  * SDK as the execution harness.
9
9
  */
10
10
 
11
- import type { InteractionUpdate, SDKCustomTool } from "@cursor/sdk";
11
+ import type { InteractionUpdate, SDKCustomTool, ToolName } from "@cursor/sdk";
12
12
  import type { z } from "zod";
13
13
  import type { ABConfigFile } from "./ab.js";
14
+ import type { OtelConfig, OtelDefinition } from "./otel.js";
14
15
  import type { StorageDefinition } from "./storage.js";
15
16
 
16
17
  // ============================================================================
@@ -46,7 +47,8 @@ export type DefinitionKind =
46
47
  | "eval"
47
48
  | "ab"
48
49
  | "storage"
49
- | "artifacts";
50
+ | "artifacts"
51
+ | "otel";
50
52
 
51
53
  export interface BrandedDefinition<K extends DefinitionKind> {
52
54
  readonly __agentServe: K;
@@ -204,6 +206,40 @@ export interface AgentConfig {
204
206
  * {@link BuiltinToolsConfig}.
205
207
  */
206
208
  builtinTools?: BuiltinToolsConfig;
209
+ /**
210
+ * Allowlist of built-in harness tools offered to the model
211
+ * (`AgentOptions.tools` on the SDK). Opt-in by design: unset keeps the
212
+ * model's full standard toolset, and once set the agent gets exactly
213
+ * what it names — nothing more, `[]` meaning no native built-in tools.
214
+ * There is deliberately no exclusion form: an agent that restricts
215
+ * tools states what it keeps, so tools the platform adds later stay
216
+ * off until someone opts in.
217
+ *
218
+ * `"mcp"` is always granted alongside a configured allowlist. It is
219
+ * transport, not capability: this kit's own server tools reach the
220
+ * model over the SDK custom-tool MCP path, so omitting it would
221
+ * silently strand `agent/tools/`. What MCP actually exposes stays
222
+ * governed by the second layer — the tools you author, the connections
223
+ * you declare, and the harness cwd's ambient `.cursor` config (point
224
+ * `local.cwd` outside checkouts whose MCP servers the agent must not
225
+ * see, and `local.sandbox` makes MCP calls fail closed).
226
+ *
227
+ * Names use the SDK's public tool vocabulary (`"shell"`, `"read"`,
228
+ * `"edit"`, `"grep"`, `"glob"`, `"ls"`, `"task"`, ...). `"shell"` is a
229
+ * capability group that also grants shell stdin writes — and is what
230
+ * `execution: "agent"` tool scripts run through, so an allowlist
231
+ * without it strands them (discovery warns). `"task"` gates subagents.
232
+ * Unknown names fail the turn (`ConfigurationError` at `Agent.create`).
233
+ *
234
+ * The allowlist states runtime-agnostic intent, but the SDK cannot
235
+ * enforce tool restrictions on cloud agents yet, and it must never
236
+ * silently not apply: a `runtime: "cloud"` agent that sets it fails at
237
+ * serve start (error diagnostic), and a hybrid agent's per-send cloud
238
+ * sessions are refused (the turn errors) rather than run unrestricted.
239
+ * Not a security boundary against the serve host: like `local.sandbox`,
240
+ * it only shapes what the model can call.
241
+ */
242
+ tools?: ToolName[];
207
243
  /**
208
244
  * How the serve host coordinates event delivery and turn execution. See
209
245
  * {@link AgentServingMode}. Defaults to `"resident"`; opt in to
@@ -504,6 +540,12 @@ export interface HostContext {
504
540
  * process.
505
541
  */
506
542
  files: HostFilesApi;
543
+ /**
544
+ * Author-facing OpenTelemetry metrics and session tags. Always present.
545
+ * Counters and histograms no-op when no meter is running. `setAttributes`
546
+ * still tags the open session when a collector is attached.
547
+ */
548
+ otel: HostOtelApi;
507
549
  }
508
550
 
509
551
  /** Host-facing JSON key-value API attached to {@link HostContext.kv}. */
@@ -704,6 +746,39 @@ export interface HostFilesApi {
704
746
  ): string | undefined;
705
747
  }
706
748
 
749
+ /** Attribute bag accepted by {@link HostOtelApi}. `undefined` values are dropped. */
750
+ export type HostOtelAttributes = Record<
751
+ string,
752
+ string | number | boolean | undefined
753
+ >;
754
+
755
+ /**
756
+ * Author-facing OpenTelemetry surface on {@link HostContext.otel}.
757
+ *
758
+ * Metrics and tags only — no custom spans, and no raw OTel `Meter` /
759
+ * `Tracer`. First-party names (`cursor.token.usage`, …) and join keys
760
+ * (`cursor.conversation.id`, `agent_sdk.agent`, …) are reserved.
761
+ */
762
+ export interface HostOtelApi {
763
+ /** True when a meter is running and `increment` / `record` export. */
764
+ readonly enabled: boolean;
765
+
766
+ /**
767
+ * Merge tags onto this host binding. On a session-bound host they join
768
+ * the open session span and later first-party metrics. Unbound tags
769
+ * stay on this host instance only.
770
+ */
771
+ setAttributes(attributes: HostOtelAttributes): void;
772
+
773
+ /** Add 1 to a counter. */
774
+ increment(name: string, attributes?: HostOtelAttributes): void;
775
+ /** Add `value` to a counter. */
776
+ increment(name: string, value: number, attributes?: HostOtelAttributes): void;
777
+
778
+ /** Record one histogram observation. */
779
+ record(name: string, value: number, attributes?: HostOtelAttributes): void;
780
+ }
781
+
707
782
  /** Runtime context handed to a tool's `execute`. */
708
783
  export interface ToolContext {
709
784
  /** Correlates with `actions.requested` / `action.result` stream events. */
@@ -1422,6 +1497,14 @@ export interface SendMessageOptions {
1422
1497
  continuationToken?: string;
1423
1498
  /** Initial channel state for newly created sessions. */
1424
1499
  state?: JsonValue;
1500
+ /**
1501
+ * On resume (continuation hit), shallow-merge into the existing session
1502
+ * channel state before the turn starts. Null / undefined values in the
1503
+ * patch do not overwrite existing keys, so sticky fields (stream ts,
1504
+ * progress banner comment id, …) survive while wake-derived fields
1505
+ * (`headSha`, refs) refresh. Ignored on create; pass {@link state} there.
1506
+ */
1507
+ refreshState?: JsonValue;
1425
1508
  /** Session display title (applied when creating a new session). */
1426
1509
  title?: string;
1427
1510
  /**
@@ -1828,7 +1911,12 @@ export interface ChannelMeta {
1828
1911
  }
1829
1912
 
1830
1913
  export interface ChannelConfig<TState = JsonValue> {
1831
- /** Route auth chain. Defaults to `[localDevStrict()]`. */
1914
+ /**
1915
+ * Route auth chain. Defaults to `[localDevStrict()]`. Include
1916
+ * `publicEndpoint()` to serve this channel WITHOUT the platform alias
1917
+ * token on Cursor hosting (for webhook senders that cannot attach custom
1918
+ * headers — the handler must verify deliveries itself).
1919
+ */
1832
1920
  auth?: AuthPolicy[];
1833
1921
  routes?: ChannelRoute[];
1834
1922
  /**
@@ -2337,6 +2425,13 @@ export interface ResolvedAgent {
2337
2425
  * (`cwd` is absolute — relative paths resolved against the project root).
2338
2426
  */
2339
2427
  local?: AgentLocalOptions;
2428
+ /**
2429
+ * Built-in harness tool allowlist from {@link AgentConfig.tools}, with
2430
+ * the "mcp" transport grant injected. Named apart from {@link tools}
2431
+ * (the authored `agent/tools/` list). Applied to local turns; cloud
2432
+ * turns fail closed (cloud-runtime agents already fail discovery).
2433
+ */
2434
+ allowedTools?: ToolName[];
2340
2435
  /** Managed-hosting declarations from {@link AgentConfig.hosting}. */
2341
2436
  hosting?: AgentHostingOptions;
2342
2437
  /** Validated turn admission limits from {@link AgentConfig.concurrency}. */
@@ -2374,6 +2469,8 @@ export interface AgentProject {
2374
2469
  storage?: StorageDefinition;
2375
2470
  /** Optional `agent/artifacts.ts` (`defineArtifacts`). */
2376
2471
  artifacts?: ArtifactsDefinition;
2472
+ /** Optional `agent/otel.ts` (`defineOtel`). */
2473
+ otel?: OtelDefinition;
2377
2474
  diagnostics: Diagnostic[];
2378
2475
  }
2379
2476
 
@@ -2456,6 +2553,7 @@ export interface AgentProjectInfo {
2456
2553
  };
2457
2554
  /** Project storage sink from `agent/storage.ts`, when authored. */
2458
2555
  storage?: { name?: string };
2556
+ otel?: { serviceName?: string; enabled: true };
2459
2557
  /** Declared artifact kinds from `agent/artifacts.ts`, when authored. */
2460
2558
  artifacts?: { kinds: string[]; agentTool: boolean; max: number };
2461
2559
  diagnostics: Diagnostic[];
@@ -2490,6 +2588,8 @@ export interface RunnerTurnRequest {
2490
2588
  workspaceDir: string;
2491
2589
  /** Run the local harness sandboxed ({@link AgentLocalOptions.sandbox}). */
2492
2590
  sandbox?: boolean;
2591
+ /** Built-in tool allowlist ({@link AgentConfig.tools}, resolved). */
2592
+ tools?: ToolName[];
2493
2593
  /** Absolute path for runner-owned durable state (conversation store). */
2494
2594
  runnerStateDir: string;
2495
2595
  prompt: string;
@@ -2646,6 +2746,12 @@ export interface ServeOptions {
2646
2746
  apiKey?: string;
2647
2747
  };
2648
2748
  logger?: (line: string) => void;
2749
+ /**
2750
+ * OpenTelemetry export. Pass a config object, or `false` to force-disable
2751
+ * even when `OTEL_EXPORTER_OTLP_*` env vars / `agent/otel.ts` are set.
2752
+ * When omitted, env + authored `defineOtel` still enable export.
2753
+ */
2754
+ otel?: OtelConfig | false;
2649
2755
  }
2650
2756
 
2651
2757
  /** One agent mounted on a server (its URL slug + compiled project). */