@cursor/july 0.1.41 → 0.1.43

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 (195) hide show
  1. package/AGENTS.md +10 -3
  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 +28 -5
  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 +8 -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/docs/404.html +2 -2
  26. package/dist/docs/ab.html +4 -4
  27. package/dist/docs/assets/{app.mIw24pb1.js → app.DsTLo2j1.js} +1 -1
  28. package/dist/docs/assets/chunks/@localSearchIndexroot.BZx4pmZL.js +1 -0
  29. package/dist/docs/assets/chunks/{VPLocalSearchBox.hOzpPwrG.js → VPLocalSearchBox.DoVfutcV.js} +1 -1
  30. package/dist/docs/assets/chunks/{theme.BtFhUYmh.js → theme.DtuVtKkc.js} +2 -2
  31. package/dist/docs/assets/{deployment.md.BHVjVqRa.js → deployment.md.Cdp4rTE4.js} +1 -1
  32. package/dist/docs/assets/{guides_github.md.R2QlpR75.js → guides_github.md.DSg0j9qj.js} +29 -3
  33. package/dist/docs/assets/{guides_github.md.R2QlpR75.lean.js → guides_github.md.DSg0j9qj.lean.js} +1 -1
  34. package/dist/docs/assets/guides_opentelemetry.md.DmyUwC5R.js +20 -0
  35. package/dist/docs/assets/guides_opentelemetry.md.DmyUwC5R.lean.js +1 -0
  36. package/dist/docs/assets/{index.md.BKpbkCa6.js → index.md.CMc5FP7E.js} +2 -1
  37. package/dist/docs/assets/{reference_channels.md.BIabFUAI.js → reference_channels.md.MN8iDy8V.js} +1 -1
  38. package/dist/docs/assets/reference_hooks.md.4cbRRIIu.js +14 -0
  39. package/dist/docs/assets/{reference_hooks.md.BHeG-vjK.lean.js → reference_hooks.md.4cbRRIIu.lean.js} +1 -1
  40. package/dist/docs/assets/{reference_project-layout.md._XdeMahr.js → reference_project-layout.md.D3MdHM2z.js} +2 -1
  41. package/dist/docs/assets/templates_triage.md.CConZt1z.js +11 -0
  42. package/dist/docs/assets/templates_triage.md.CConZt1z.lean.js +1 -0
  43. package/dist/docs/building-with-agents.html +5 -5
  44. package/dist/docs/concepts.html +5 -5
  45. package/dist/docs/deployment.html +6 -6
  46. package/dist/docs/evals.html +4 -4
  47. package/dist/docs/example-agents/approval-buddy.html +4 -4
  48. package/dist/docs/example-agents/benny.html +4 -4
  49. package/dist/docs/example-agents/bugbot.html +4 -4
  50. package/dist/docs/example-agents/codebase-wiki.html +4 -4
  51. package/dist/docs/example-agents/codeowners-review.html +4 -4
  52. package/dist/docs/example-agents/concierge.html +4 -4
  53. package/dist/docs/example-agents/fsd.html +4 -4
  54. package/dist/docs/example-agents/index.html +5 -5
  55. package/dist/docs/example-agents/knowledge-base.html +4 -4
  56. package/dist/docs/example-agents/oncall.html +4 -4
  57. package/dist/docs/example-agents/security-reviewer.html +4 -4
  58. package/dist/docs/example-agents/slack-agent.html +4 -4
  59. package/dist/docs/example-agents/weather-agent.html +4 -4
  60. package/dist/docs/guides/agent-to-agent.html +4 -4
  61. package/dist/docs/guides/cloud-runtime.html +5 -5
  62. package/dist/docs/guides/github.html +33 -7
  63. package/dist/docs/guides/human-in-the-loop.html +4 -4
  64. package/dist/docs/guides/mcp-oauth.html +4 -4
  65. package/dist/docs/guides/opentelemetry.html +44 -0
  66. package/dist/docs/guides/slack.html +4 -4
  67. package/dist/docs/guides/webhooks.html +4 -4
  68. package/dist/docs/hashmap.json +1 -1
  69. package/dist/docs/hillclimbing.html +4 -4
  70. package/dist/docs/index.html +7 -6
  71. package/dist/docs/quickstart.html +4 -4
  72. package/dist/docs/reference/agent-config.html +4 -4
  73. package/dist/docs/reference/artifacts.html +4 -4
  74. package/dist/docs/reference/channels.html +6 -6
  75. package/dist/docs/reference/cli.html +4 -4
  76. package/dist/docs/reference/connections.html +4 -4
  77. package/dist/docs/reference/hooks.html +9 -7
  78. package/dist/docs/reference/http-api.html +4 -4
  79. package/dist/docs/reference/instructions.html +4 -4
  80. package/dist/docs/reference/playground.html +4 -4
  81. package/dist/docs/reference/project-layout.html +7 -6
  82. package/dist/docs/reference/prompt.html +4 -4
  83. package/dist/docs/reference/schedules.html +4 -4
  84. package/dist/docs/reference/sessions.html +4 -4
  85. package/dist/docs/reference/skills.html +4 -4
  86. package/dist/docs/reference/subagents.html +4 -4
  87. package/dist/docs/reference/tools.html +4 -4
  88. package/dist/docs/scaffolding-agents.html +4 -4
  89. package/dist/docs/storage.html +4 -4
  90. package/dist/docs/templates/triage.html +35 -0
  91. package/dist/docs/troubleshooting.html +4 -4
  92. package/dist/hooks.d.ts +3 -1
  93. package/dist/hooks.d.ts.map +1 -1
  94. package/dist/hooks.js +3 -1
  95. package/dist/index.d.ts +2 -0
  96. package/dist/index.d.ts.map +1 -1
  97. package/dist/index.js +1 -0
  98. package/dist/internal/cli-ax.d.ts +1 -0
  99. package/dist/internal/cli-ax.d.ts.map +1 -1
  100. package/dist/internal/cli-ax.js +18 -3
  101. package/dist/internal/discovery.d.ts.map +1 -1
  102. package/dist/internal/discovery.js +36 -4
  103. package/dist/internal/distribution.d.ts.map +1 -1
  104. package/dist/internal/distribution.js +1 -0
  105. package/dist/internal/init-project.d.ts +24 -0
  106. package/dist/internal/init-project.d.ts.map +1 -1
  107. package/dist/internal/init-project.js +126 -7
  108. package/dist/internal/otel/attrs.d.ts +7 -0
  109. package/dist/internal/otel/attrs.d.ts.map +1 -0
  110. package/dist/internal/otel/attrs.js +13 -0
  111. package/dist/internal/otel/collector.d.ts +16 -0
  112. package/dist/internal/otel/collector.d.ts.map +1 -0
  113. package/dist/internal/otel/collector.js +564 -0
  114. package/dist/internal/otel/config.d.ts +42 -0
  115. package/dist/internal/otel/config.d.ts.map +1 -0
  116. package/dist/internal/otel/config.js +236 -0
  117. package/dist/internal/otel/http.d.ts +11 -0
  118. package/dist/internal/otel/http.d.ts.map +1 -0
  119. package/dist/internal/otel/http.js +97 -0
  120. package/dist/internal/otel/runtime.d.ts +21 -0
  121. package/dist/internal/otel/runtime.d.ts.map +1 -0
  122. package/dist/internal/otel/runtime.js +188 -0
  123. package/dist/internal/server.d.ts.map +1 -1
  124. package/dist/internal/server.js +25 -1
  125. package/dist/internal/session-engine.d.ts +10 -1
  126. package/dist/internal/session-engine.d.ts.map +1 -1
  127. package/dist/internal/session-engine.js +36 -2
  128. package/dist/memory.d.ts +4 -0
  129. package/dist/memory.d.ts.map +1 -1
  130. package/dist/memory.js +4 -0
  131. package/dist/otel.d.ts +88 -0
  132. package/dist/otel.d.ts.map +1 -0
  133. package/dist/otel.js +64 -0
  134. package/dist/playground/assets/{index-B3HBneZZ.js → index-D-DIYQzw.js} +31 -31
  135. package/dist/playground/assets/index-DQTH622f.css +1 -0
  136. package/dist/playground/index.html +2 -2
  137. package/dist/types.d.ts +31 -4
  138. package/dist/types.d.ts.map +1 -1
  139. package/docs/README.md +3 -0
  140. package/docs/deployment.md +2 -1
  141. package/docs/guides/github.md +79 -6
  142. package/docs/guides/opentelemetry.md +184 -0
  143. package/docs/reference/channels.md +3 -1
  144. package/docs/reference/hooks.md +25 -5
  145. package/docs/reference/project-layout.md +2 -0
  146. package/docs/templates/triage.md +147 -0
  147. package/package.json +23 -1
  148. package/skills/create-agent/SKILL.md +2 -2
  149. package/skills/debug/SKILL.md +4 -0
  150. package/skills/framework-map/SKILL.md +2 -0
  151. package/skills/otel/SKILL.md +80 -0
  152. package/src/bin/agent-serve.ts +3 -1
  153. package/src/channels/github/api.ts +22 -0
  154. package/src/channels/github/defaults.ts +27 -6
  155. package/src/channels/github/github-channel.ts +27 -1
  156. package/src/channels/github/index.ts +1 -0
  157. package/src/channels/github/progress.ts +243 -0
  158. package/src/channels/github/state.ts +3 -0
  159. package/src/channels/github/types.ts +8 -4
  160. package/src/channels/slack/slack-channel.ts +4 -0
  161. package/src/hooks.ts +3 -1
  162. package/src/index.ts +6 -0
  163. package/src/internal/cli-ax.ts +19 -1
  164. package/src/internal/discovery.ts +59 -4
  165. package/src/internal/distribution.ts +1 -0
  166. package/src/internal/init-project.ts +150 -7
  167. package/src/internal/otel/attrs.ts +18 -0
  168. package/src/internal/otel/collector.ts +796 -0
  169. package/src/internal/otel/config.ts +307 -0
  170. package/src/internal/otel/http.ts +126 -0
  171. package/src/internal/otel/runtime.ts +271 -0
  172. package/src/internal/server.ts +38 -1
  173. package/src/internal/session-engine.ts +52 -0
  174. package/src/memory.ts +4 -0
  175. package/src/otel.ts +155 -0
  176. package/src/types.ts +29 -4
  177. package/templates/triage/README.md +48 -0
  178. package/templates/triage/agent/agent.ts +14 -0
  179. package/templates/triage/agent/channels/intake.ts +20 -0
  180. package/templates/triage/agent/channels/slack.ts +3 -0
  181. package/templates/triage/agent/hooks/memory.ts +3 -0
  182. package/templates/triage/agent/instructions.md +27 -0
  183. package/templates/triage/agent/skills/triage-rubric.md +43 -0
  184. package/templates/triage/agent/tools/record_triage.ts +23 -0
  185. package/templates/triage/evals/evals.config.ts +5 -0
  186. package/templates/triage/evals/triage.eval.ts +43 -0
  187. package/templates/triage/package.json +18 -0
  188. package/templates/triage/tsconfig.json +12 -0
  189. package/dist/docs/assets/chunks/@localSearchIndexroot.BA7V8dEJ.js +0 -1
  190. package/dist/docs/assets/reference_hooks.md.BHeG-vjK.js +0 -12
  191. package/dist/playground/assets/index-Bqk9nplh.css +0 -1
  192. /package/dist/docs/assets/{deployment.md.BHVjVqRa.lean.js → deployment.md.Cdp4rTE4.lean.js} +0 -0
  193. /package/dist/docs/assets/{index.md.BKpbkCa6.lean.js → index.md.CMc5FP7E.lean.js} +0 -0
  194. /package/dist/docs/assets/{reference_channels.md.BIabFUAI.lean.js → reference_channels.md.MN8iDy8V.lean.js} +0 -0
  195. /package/dist/docs/assets/{reference_project-layout.md._XdeMahr.lean.js → reference_project-layout.md.D3MdHM2z.lean.js} +0 -0
@@ -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,17 @@ 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,
405
438
  });
406
439
  // Rehydrate sessions from the authored storage sink before any
407
440
  // channel or route can address them (continuation tokens must resolve).
@@ -696,7 +729,9 @@ export async function startServer(
696
729
  });
697
730
 
698
731
  const server = createServer((req, res) => {
699
- void handleRequest(router, req, res, logger);
732
+ void instrumentHttp(otelRuntime, req, res, () =>
733
+ handleRequest(router, req, res, logger)
734
+ );
700
735
  });
701
736
 
702
737
  const host = options.host ?? "127.0.0.1";
@@ -715,6 +750,7 @@ export async function startServer(
715
750
  await cursorEventRelay?.stop();
716
751
  await cursorSlackRelay?.stop();
717
752
  cursorGithubCredentials?.stop();
753
+ await shutdownOtelRuntime(otelRuntime);
718
754
  throw error;
719
755
  }
720
756
  const address = server.address() as AddressInfo;
@@ -900,6 +936,7 @@ export async function startServer(
900
936
  await Promise.all(runtimes.map((runtime) => runtime.engine.close()));
901
937
  } finally {
902
938
  cursorGithubCredentials?.stop();
939
+ await shutdownOtelRuntime(otelRuntime);
903
940
  }
904
941
  },
905
942
  };
@@ -109,6 +109,7 @@ import {
109
109
  import { validateToolInput } from "./json-schema.js";
110
110
  import { LedgerServing, newLedgerPodId } from "./ledger-serving.js";
111
111
  import { McpHost } from "./mcp-host.js";
112
+ import type { OtelCollector } from "./otel/collector.js";
112
113
  import {
113
114
  buildSessionContextBlock,
114
115
  SCHEDULE_CHANNEL_PREFIX,
@@ -243,6 +244,8 @@ export interface SessionEngineOptions {
243
244
  * *without* `cas` fails fast. Ignored for resident-mode agents.
244
245
  */
245
246
  durableSessionStore?: HostKvApi;
247
+ /** Process-level OTel collector; observe-only, never fatal. */
248
+ otel?: OtelCollector;
246
249
  }
247
250
 
248
251
  /**
@@ -358,6 +361,7 @@ export class SessionEngine {
358
361
  private readonly platforms: HostPlatforms;
359
362
  private readonly approvals = new ApprovalGate();
360
363
  private readonly abCollector: ABCollector;
364
+ private readonly otel: OtelCollector | undefined;
361
365
  private reminderApi: HostContext["reminders"];
362
366
  private evalsApi: HostContext["evals"];
363
367
  private readonly kvApi: HostContext["kv"];
@@ -390,6 +394,7 @@ export class SessionEngine {
390
394
  this.logger = options.logger ?? (() => {});
391
395
  this.getCloudUsage = options.getCloudUsage ?? defaultGetCloudUsage;
392
396
  this.estimateTurnCost = options.estimateTurnCost ?? estimateTurnCostFromApi;
397
+ this.otel = options.otel;
393
398
  this.turnGovernor = new TurnGovernor(options.project.agent.concurrency);
394
399
  this.sessions = new SessionStore(join(options.stateRoot, "sessions"));
395
400
  this.nudgeStore = new SlackNudgeStore(
@@ -775,6 +780,17 @@ export class SessionEngine {
775
780
  cloudOverride: options.cloud,
776
781
  }));
777
782
  }
783
+ // Refresh wake-derived channel state (GitHub headSha / refs) without
784
+ // wiping sticky fields. Null patch values are skipped.
785
+ if (options.refreshState !== undefined) {
786
+ record = await this.updateRecord(record.sessionId, (current) => ({
787
+ ...current,
788
+ channelState: mergeChannelStateSkipNull(
789
+ current.channelState,
790
+ options.refreshState
791
+ ),
792
+ }));
793
+ }
778
794
  const coalesced = await this.startTurn(record.sessionId, message, {
779
795
  admission: options.admission ?? "preempt",
780
796
  ...(options.coalesceSourceTs === undefined
@@ -2436,6 +2452,7 @@ export class SessionEngine {
2436
2452
  },
2437
2453
  session: this.sessionInfo(record),
2438
2454
  stateRoot: this.stateRoot,
2455
+ host: this.hostForSession(record.sessionId),
2439
2456
  artifacts: this.artifactsApi({
2440
2457
  sessionId: record.sessionId,
2441
2458
  turnId: event.turnId,
@@ -2456,6 +2473,8 @@ export class SessionEngine {
2456
2473
  this.project.name
2457
2474
  );
2458
2475
  }
2476
+
2477
+ this.otel?.onEvent(event, this.sessionInfo(record));
2459
2478
  }
2460
2479
 
2461
2480
  private channelEventHandlersFor(
@@ -3351,6 +3370,7 @@ export class SessionEngine {
3351
3370
  await this.storage?.close();
3352
3371
  await this.mcpHost.close();
3353
3372
  await this.runner.dispose?.();
3373
+ this.otel?.close();
3354
3374
  }
3355
3375
  }
3356
3376
 
@@ -3384,6 +3404,38 @@ function mergePendingWorkspaceFiles(
3384
3404
  return { ...current, ...incoming };
3385
3405
  }
3386
3406
 
3407
+ /**
3408
+ * Shallow-merge a wake patch into durable channel state. Null / undefined
3409
+ * patch values leave the existing key alone so sticky fields survive.
3410
+ */
3411
+ export function mergeChannelStateSkipNull(
3412
+ current: JsonValue | undefined,
3413
+ patch: JsonValue | undefined
3414
+ ): JsonValue | undefined {
3415
+ if (patch === undefined) {
3416
+ return current;
3417
+ }
3418
+ if (
3419
+ typeof patch !== "object" ||
3420
+ patch === null ||
3421
+ Array.isArray(patch) ||
3422
+ typeof current !== "object" ||
3423
+ current === null ||
3424
+ Array.isArray(current)
3425
+ ) {
3426
+ return structuredClone(patch);
3427
+ }
3428
+ const merged: Record<string, JsonValue> = {
3429
+ ...(current as Record<string, JsonValue>),
3430
+ };
3431
+ for (const [key, value] of Object.entries(patch)) {
3432
+ if (value !== null && value !== undefined) {
3433
+ merged[key] = value as JsonValue;
3434
+ }
3435
+ }
3436
+ return merged;
3437
+ }
3438
+
3387
3439
  function eventsNdjson(events: SessionEvent[]): string | undefined {
3388
3440
  if (events.length === 0) {
3389
3441
  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
@@ -11,6 +11,7 @@
11
11
  import type { InteractionUpdate, SDKCustomTool } 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;
@@ -455,9 +457,9 @@ export interface SlackNudgeHostApi {
455
457
  }
456
458
 
457
459
  /**
458
- * Shared host services available on every tool / channel / schedule surface.
459
- * Channel packs attach this as `ctx.host` so handlers can tap GitHub, Slack,
460
- * or MCP without the pack depending on another platform.
460
+ * Shared host services available on every tool / hook / channel / schedule
461
+ * surface. Channel packs attach this as `ctx.host` so handlers can tap
462
+ * GitHub, Slack, or MCP without the pack depending on another platform.
461
463
  */
462
464
  export interface HostContext {
463
465
  /**
@@ -1422,6 +1424,14 @@ export interface SendMessageOptions {
1422
1424
  continuationToken?: string;
1423
1425
  /** Initial channel state for newly created sessions. */
1424
1426
  state?: JsonValue;
1427
+ /**
1428
+ * On resume (continuation hit), shallow-merge into the existing session
1429
+ * channel state before the turn starts. Null / undefined values in the
1430
+ * patch do not overwrite existing keys, so sticky fields (stream ts,
1431
+ * progress banner comment id, …) survive while wake-derived fields
1432
+ * (`headSha`, refs) refresh. Ignored on create; pass {@link state} there.
1433
+ */
1434
+ refreshState?: JsonValue;
1425
1435
  /** Session display title (applied when creating a new session). */
1426
1436
  title?: string;
1427
1437
  /**
@@ -1891,6 +1901,12 @@ export interface HookContext {
1891
1901
  * memory journal) write here.
1892
1902
  */
1893
1903
  stateRoot: string;
1904
+ /**
1905
+ * Shared host services (MCP / GitHub / Slack / kv / files). Same as
1906
+ * tool `ctx.host`. `files` is bound to this hook's session, so a
1907
+ * relative path cannot read another session's files.
1908
+ */
1909
+ host: HostContext;
1894
1910
  /**
1895
1911
  * Durable artifacts, bound to this hook's session: `tag` auto-fills
1896
1912
  * `sessionId` (and `turnId` when known).
@@ -2368,6 +2384,8 @@ export interface AgentProject {
2368
2384
  storage?: StorageDefinition;
2369
2385
  /** Optional `agent/artifacts.ts` (`defineArtifacts`). */
2370
2386
  artifacts?: ArtifactsDefinition;
2387
+ /** Optional `agent/otel.ts` (`defineOtel`). */
2388
+ otel?: OtelDefinition;
2371
2389
  diagnostics: Diagnostic[];
2372
2390
  }
2373
2391
 
@@ -2450,6 +2468,7 @@ export interface AgentProjectInfo {
2450
2468
  };
2451
2469
  /** Project storage sink from `agent/storage.ts`, when authored. */
2452
2470
  storage?: { name?: string };
2471
+ otel?: { serviceName?: string; enabled: true };
2453
2472
  /** Declared artifact kinds from `agent/artifacts.ts`, when authored. */
2454
2473
  artifacts?: { kinds: string[]; agentTool: boolean; max: number };
2455
2474
  diagnostics: Diagnostic[];
@@ -2640,6 +2659,12 @@ export interface ServeOptions {
2640
2659
  apiKey?: string;
2641
2660
  };
2642
2661
  logger?: (line: string) => void;
2662
+ /**
2663
+ * OpenTelemetry export. Pass a config object, or `false` to force-disable
2664
+ * even when `OTEL_EXPORTER_OTLP_*` env vars / `agent/otel.ts` are set.
2665
+ * When omitted, env + authored `defineOtel` still enable export.
2666
+ */
2667
+ otel?: OtelConfig | false;
2643
2668
  }
2644
2669
 
2645
2670
  /** One agent mounted on a server (its URL slug + compiled project). */
@@ -0,0 +1,48 @@
1
+ # Triage agent
2
+
3
+ A triage front door for your team. Send it bug reports, questions, and
4
+ feedback; it classifies each report against a rubric, judges severity,
5
+ records a structured verdict, and replies with the next step.
6
+
7
+ Scaffolded with:
8
+
9
+ ```bash
10
+ npx @cursor/july init ./my-triage-agent --template triage
11
+ ```
12
+
13
+ ## Run it
14
+
15
+ ```bash
16
+ agent-sdk login # once per machine
17
+ agent-sdk dev
18
+ ```
19
+
20
+ Open the playground URL it prints. Chat directly, or use the `/report`
21
+ slash command to submit a report the way an integration would
22
+ (`POST /v1/channels/intake/report`).
23
+
24
+ ## Wire it to Slack
25
+
26
+ The Slack channel idles until its tokens exist. Create a Slack app and
27
+ its env with:
28
+
29
+ ```bash
30
+ agent-sdk slack setup
31
+ ```
32
+
33
+ Then @mention the bot or DM it. The setup-slack skill in the
34
+ @cursor/july package has the full walkthrough.
35
+
36
+ ## Make it yours
37
+
38
+ - `agent/instructions.md` owns the flow every report follows.
39
+ - `agent/skills/triage-rubric.md` owns the kinds, the tie-breakers, and
40
+ the severity ladder. Edit this first; it is the judgment.
41
+ - `agent/tools/record_triage.ts` owns the verdict shape. Point its
42
+ `execute` at your tracker (Linear, GitHub Issues, a webhook) to file
43
+ real tickets.
44
+ - `evals/triage.eval.ts` holds three smoke cases. Run `agent-sdk eval`
45
+ after every rubric change.
46
+
47
+ The full walkthrough, from scaffold through deploy, is the triage
48
+ cookbook: run `agent-sdk docs` and open Templates, then Triage agent.
@@ -0,0 +1,14 @@
1
+ import { defineAgent } from "@cursor/july";
2
+
3
+ export default defineAgent({
4
+ model: {
5
+ id: "grok-4.5",
6
+ params: [
7
+ { id: "effort", value: "high" },
8
+ { id: "fast", value: "true" },
9
+ ],
10
+ },
11
+ local: {
12
+ sandbox: true,
13
+ },
14
+ });
@@ -0,0 +1,20 @@
1
+ import { defineChannel, POST } from "@cursor/july/channels";
2
+ import { z } from "zod";
3
+
4
+ export default defineChannel({
5
+ routes: [
6
+ POST("/report", {
7
+ description: "Submit a report for triage",
8
+ bodySchema: z.object({
9
+ message: z.string(),
10
+ reporter: z.string().optional(),
11
+ }),
12
+ handler: async (_req, { send, body }) => {
13
+ const from =
14
+ body.reporter === undefined ? "" : ` (from ${body.reporter})`;
15
+ const session = await send(`New report${from}:\n\n${body.message}`);
16
+ return Response.json({ ok: true, sessionId: session.id });
17
+ },
18
+ }),
19
+ ],
20
+ });
@@ -0,0 +1,3 @@
1
+ import { slackChannel } from "@cursor/july/channels/slack";
2
+
3
+ export default slackChannel();
@@ -0,0 +1,3 @@
1
+ import { memoryHook } from "@cursor/july/memory";
2
+
3
+ export default memoryHook();
@@ -0,0 +1,27 @@
1
+ # Triage agent
2
+
3
+ You are the triage front door for this team. People send you reports:
4
+ bug reports, questions, feedback, feature ideas. You classify each one,
5
+ judge its severity, and hand back a verdict the team can act on.
6
+
7
+ Read the `triage-rubric` skill before classifying. It defines the three
8
+ kinds and the tie-breakers.
9
+
10
+ ## Every report
11
+
12
+ 1. Read the report. If it is too thin to classify (no symptom, no
13
+ question, no opinion), ask for the missing piece once. Otherwise do
14
+ not ask; classify with what you have.
15
+ 2. If a `memory/` directory exists in your workspace, grep the journal
16
+ for similar past reports and say when one matches.
17
+ 3. Call `record_triage` exactly once with your verdict.
18
+ 4. Reply with the verdict: kind, severity, a one-line reason, and the
19
+ next step. Keep the reply under six lines. No preamble.
20
+
21
+ ## Boundaries
22
+
23
+ - You classify and route. You do not fix bugs, write code, or promise
24
+ timelines.
25
+ - When a report mixes kinds, pick the dominant one and name the split
26
+ in your reason.
27
+ - Never invent reporter details. Missing fields stay empty.
@@ -0,0 +1,43 @@
1
+ ---
2
+ description: Definitions and tie-breakers for classifying a report as bug, question, or feedback, plus the severity ladder.
3
+ ---
4
+
5
+ # Triage rubric
6
+
7
+ ## Kinds
8
+
9
+ - **bug**: something is broken, erroring, slow, or behaving wrong right
10
+ now, and the reporter wants it diagnosed or fixed. Usually has a
11
+ symptom, an error, an id, a timestamp, or steps.
12
+ - **question**: the reporter wants to know something that has an answer
13
+ (how does X work, is Y expected, who owns Z). Someone has to look it
14
+ up, not fix it.
15
+ - **feedback**: an opinion, suggestion, complaint about how something
16
+ behaves, or a feature idea. The reporter is telling the team
17
+ something, not asking it to find anything out.
18
+
19
+ ## Tie-breakers
20
+
21
+ - "This is annoying, you should change X" is feedback even when it
22
+ sounds like a complaint.
23
+ - A question about a specific failure that already happened is a bug.
24
+ "Why did my export fail yesterday?" needs an investigation, not a
25
+ lookup.
26
+ - A bug report that also suggests a fix is a bug.
27
+ - A known bug with a workaround that only works sometimes is a bug. The
28
+ unreliable workaround is the thing to explain.
29
+ - A feature idea phrased as a question ("could we have dark mode?") is
30
+ feedback.
31
+
32
+ ## Severity
33
+
34
+ - **urgent**: data loss, security exposure, or many users blocked with
35
+ no workaround.
36
+ - **high**: a core flow is broken or wrong for some users, no
37
+ reasonable workaround.
38
+ - **medium**: broken or wrong with a workaround, or a sharp edge that
39
+ costs time.
40
+ - **low**: cosmetic, minor friction, questions, and most feedback.
41
+
42
+ Questions and feedback default to low unless the report shows real
43
+ user impact.
@@ -0,0 +1,23 @@
1
+ import { defineTool } from "@cursor/july/tools";
2
+ import { z } from "zod";
3
+
4
+ export default defineTool({
5
+ description:
6
+ "Record the structured triage verdict for the current report. Call exactly once per report, before replying.",
7
+ inputSchema: z.object({
8
+ kind: z.enum(["bug", "question", "feedback"]),
9
+ severity: z.enum(["low", "medium", "high", "urgent"]),
10
+ title: z
11
+ .string()
12
+ .max(80)
13
+ .describe("Short, imperative summary of the report."),
14
+ summary: z
15
+ .string()
16
+ .describe("What the reporter said, in two sentences or less."),
17
+ nextStep: z.string().describe("The single next action for the team."),
18
+ reporter: z.string().optional().describe("Who reported it, when known."),
19
+ }),
20
+ async execute(verdict) {
21
+ return { recorded: true, at: new Date().toISOString(), verdict };
22
+ },
23
+ });
@@ -0,0 +1,5 @@
1
+ import { defineEvalConfig } from "@cursor/july/evals";
2
+
3
+ export default defineEvalConfig({
4
+ maxConcurrency: 10,
5
+ });