@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
@@ -20,6 +20,7 @@ import { pathToFileURL } from "node:url";
20
20
  import { type ABConfigFile, resolveABMaxPlaygroundSessions } from "../ab.js";
21
21
  import { resolveArtifactsMax } from "../artifacts.js";
22
22
  import { httpChannel } from "../channels.js";
23
+ import type { OtelDefinition } from "../otel.js";
23
24
  import type { StorageDefinition } from "../storage.js";
24
25
  import {
25
26
  type AgentConcurrencyOptions,
@@ -300,6 +301,7 @@ export async function loadAgentProject(
300
301
  const abConfig = await loadABConfig(ctx, agentDir);
301
302
  const storage = await loadStorage(ctx, agentDir);
302
303
  const artifacts = await loadArtifacts(ctx, agentDir);
304
+ const otel = await loadOtel(ctx, agentDir);
303
305
  await appendArtifactTool(ctx, agentDir, agent, artifacts);
304
306
 
305
307
  if (agentDir !== rootDir) {
@@ -319,6 +321,7 @@ export async function loadAgentProject(
319
321
  abConfig,
320
322
  storage,
321
323
  artifacts,
324
+ otel,
322
325
  diagnostics,
323
326
  };
324
327
  }
@@ -373,6 +376,14 @@ async function loadAgent(
373
376
  message: "builtinTools is not supported on subagents; ignoring.",
374
377
  });
375
378
  }
379
+ if (config?.tools !== undefined) {
380
+ ctx.diagnostics.push({
381
+ severity: "warning",
382
+ path: displayPath(ctx, join(agentDir, "agent.ts")),
383
+ message:
384
+ "tools is not supported on subagents (they keep the harness task-tool toolset); ignoring.",
385
+ });
386
+ }
376
387
  const artifactsPath = await findModuleFile(agentDir, "artifacts");
377
388
  if (artifactsPath !== undefined) {
378
389
  ctx.diagnostics.push({
@@ -412,6 +423,46 @@ async function loadAgent(
412
423
  const localCwd = resolveAgentCwd(ctx, config?.local?.cwd);
413
424
  const localWorkspaceDir = resolveAgentCwd(ctx, config?.local?.workspaceDir);
414
425
  const localSandbox = config?.local?.sandbox === true;
426
+ // Built-in tool allowlist (top-level `tools`). `[]` is meaningful (no
427
+ // native built-in tools), so presence is keyed on the field, not on
428
+ // emptiness. "mcp" is always granted alongside a configured allowlist:
429
+ // it is transport, not capability — this kit's own server tools ride the
430
+ // SDK custom-tool MCP path, so omitting it silently breaks
431
+ // `agent/tools/`, and what MCP actually exposes stays controlled by the
432
+ // second layer (authored tools, mcp-connections, the harness cwd's
433
+ // ambient config).
434
+ const authoredAllowlist = config?.tools;
435
+ const allowedTools =
436
+ authoredAllowlist === undefined || authoredAllowlist.includes("mcp")
437
+ ? authoredAllowlist
438
+ : [...authoredAllowlist, "mcp" as const];
439
+ // The allowlist states runtime-agnostic intent, but the SDK cannot
440
+ // enforce tool restrictions on cloud agents yet — a cloud-runtime agent
441
+ // that declares one must fail at serve start, never run unrestricted.
442
+ // (Hybrid per-send cloud sessions fail closed in the session engine.)
443
+ if (allowedTools !== undefined && runtime === "cloud") {
444
+ ctx.diagnostics.push({
445
+ severity: "error",
446
+ path: displayPath(ctx, join(agentDir, "agent.ts")),
447
+ message:
448
+ 'tools cannot be enforced on runtime: "cloud" yet (the Cursor SDK rejects tool restrictions on cloud agents). Remove `tools` or run the agent on the local runtime.',
449
+ });
450
+ }
451
+ // `execution: "agent"` tool scripts run through the shell tool. Shell is
452
+ // a real capability, so it is never injected — but an allowlist that
453
+ // strands authored scripts deserves a loud diagnostic, not silence.
454
+ if (
455
+ allowedTools !== undefined &&
456
+ !allowedTools.includes("shell") &&
457
+ tools.some((tool) => tool.execution === "agent")
458
+ ) {
459
+ ctx.diagnostics.push({
460
+ severity: "warning",
461
+ path: displayPath(ctx, join(agentDir, "agent.ts")),
462
+ message:
463
+ '`tools` omits "shell" but this agent has execution: "agent" tools, which run as shell scripts; the model will not be able to run them.',
464
+ });
465
+ }
415
466
  if (localCwd !== undefined && localWorkspaceDir !== undefined) {
416
467
  ctx.diagnostics.push({
417
468
  severity: "warning",
@@ -452,6 +503,7 @@ async function loadAgent(
452
503
  runtime,
453
504
  cloud,
454
505
  local,
506
+ ...(allowedTools === undefined ? {} : { allowedTools }),
455
507
  hosting,
456
508
  concurrency,
457
509
  serving,
@@ -1718,6 +1770,22 @@ async function loadStorage(
1718
1770
  return definition as StorageDefinition | undefined;
1719
1771
  }
1720
1772
 
1773
+ // ============================================================================
1774
+ // OpenTelemetry (agent/otel.ts)
1775
+ // ============================================================================
1776
+
1777
+ async function loadOtel(
1778
+ ctx: DiscoveryContext,
1779
+ agentDir: string
1780
+ ): Promise<OtelDefinition | undefined> {
1781
+ const path = await findModuleFile(agentDir, "otel");
1782
+ if (path === undefined) {
1783
+ return undefined;
1784
+ }
1785
+ const definition = await importDefault(ctx, path, "otel");
1786
+ return definition as OtelDefinition | undefined;
1787
+ }
1788
+
1721
1789
  // ============================================================================
1722
1790
  // Artifacts (agent/artifacts.ts)
1723
1791
  // ============================================================================
@@ -1973,6 +2041,13 @@ export function projectInfo(project: AgentProject): AgentProjectInfo {
1973
2041
  agentTool: project.artifacts.agentTool === true,
1974
2042
  max: resolveArtifactsMax(project.artifacts),
1975
2043
  },
2044
+ otel:
2045
+ project.otel === undefined
2046
+ ? undefined
2047
+ : {
2048
+ enabled: true as const,
2049
+ serviceName: project.otel.serviceName,
2050
+ },
1976
2051
  diagnostics: project.diagnostics,
1977
2052
  };
1978
2053
  }
@@ -1994,6 +2069,7 @@ async function importDefault(
1994
2069
  | "ab"
1995
2070
  | "storage"
1996
2071
  | "artifacts"
2072
+ | "otel"
1997
2073
  ): Promise<unknown> {
1998
2074
  let mod: Record<string, unknown>;
1999
2075
  try {
@@ -2008,10 +2084,7 @@ async function importDefault(
2008
2084
  }
2009
2085
  const kind = getDefinitionKind(mod.default);
2010
2086
  if (kind !== expectedKind) {
2011
- const helper =
2012
- expectedKind === "ab"
2013
- ? "defineAB"
2014
- : `define${expectedKind[0]?.toUpperCase()}${expectedKind.slice(1)}`;
2087
+ const helper = DEFINE_HELPER_NAME[expectedKind];
2015
2088
  ctx.diagnostics.push({
2016
2089
  severity: "error",
2017
2090
  path: displayPath(ctx, modulePath),
@@ -2025,6 +2098,37 @@ async function importDefault(
2025
2098
  return mod.default;
2026
2099
  }
2027
2100
 
2101
+ const DEFINE_HELPER_NAME: Record<
2102
+ | "agent"
2103
+ | "instructions"
2104
+ | "tool"
2105
+ | "skill"
2106
+ | "connection"
2107
+ | "channel"
2108
+ | "http-channel"
2109
+ | "schedule"
2110
+ | "hook"
2111
+ | "ab"
2112
+ | "storage"
2113
+ | "artifacts"
2114
+ | "otel",
2115
+ string
2116
+ > = {
2117
+ agent: "defineAgent",
2118
+ instructions: "defineInstructions",
2119
+ tool: "defineTool",
2120
+ skill: "defineSkill",
2121
+ connection: "defineConnection",
2122
+ channel: "defineChannel",
2123
+ "http-channel": "httpChannel",
2124
+ schedule: "defineSchedule",
2125
+ hook: "defineHook",
2126
+ ab: "defineAB",
2127
+ storage: "defineStorage",
2128
+ artifacts: "defineArtifacts",
2129
+ otel: "defineOtel",
2130
+ };
2131
+
2028
2132
  async function warnUnknownSlots(
2029
2133
  ctx: DiscoveryContext,
2030
2134
  agentDir: string
@@ -114,4 +114,5 @@ export const AUTHORING_ENTRY_FILES: ReadonlyArray<
114
114
  ["storage", "storage.js"],
115
115
  ["storage/file-kv", "storage-backends/file-kv.js"],
116
116
  ["storage/cursor-hosted", "storage-backends/cursor-hosted.js"],
117
+ ["otel", "otel.js"],
117
118
  ];
@@ -0,0 +1,252 @@
1
+ /**
2
+ * Author-facing OpenTelemetry surface for {@link HostContext.otel}.
3
+ *
4
+ * Always present on `ctx.host`. Counters and histograms no-op when no meter
5
+ * is running. `setAttributes` still tags the open session when a collector
6
+ * is attached. `forSession` is engine-only so a tool cannot retarget another
7
+ * conversation's tags.
8
+ */
9
+
10
+ import type { Counter, Histogram, Meter } from "@opentelemetry/api";
11
+ import type { HostOtelApi, HostOtelAttributes } from "../types.js";
12
+ import type { AttrValue } from "./otel/attrs.js";
13
+ import { attrs } from "./otel/attrs.js";
14
+ import type { OtelCollector } from "./otel/collector.js";
15
+
16
+ const METRIC_NAME = /^[A-Za-z][A-Za-z0-9_./-]*$/;
17
+
18
+ const RESERVED_METRICS = new Set([
19
+ "cursor.token.usage",
20
+ "cursor.tool.calls",
21
+ "cursor.cost.usage",
22
+ "agent_sdk.session.count",
23
+ "agent_sdk.turn.count",
24
+ "agent_sdk.subagent.count",
25
+ "agent_sdk.http.duration",
26
+ ]);
27
+
28
+ const RESERVED_ATTRIBUTES = new Set([
29
+ "cursor.conversation.id",
30
+ "agent_sdk.agent",
31
+ "agent_sdk.turn_id",
32
+ "agent_sdk.framework",
33
+ ]);
34
+
35
+ export interface CreateHostOtelApiOptions {
36
+ agentName: string;
37
+ meter?: Meter;
38
+ collector?: OtelCollector;
39
+ /** Session bound for join tags; prefer {@link HostOtelSessionApi.forSession}. */
40
+ sessionId?: string;
41
+ }
42
+
43
+ /** Engine-only binding. Not part of the public {@link HostOtelApi}. */
44
+ export interface HostOtelSessionApi extends HostOtelApi {
45
+ forSession(sessionId: string): HostOtelSessionApi;
46
+ }
47
+
48
+ interface CachedInstrument {
49
+ kind: "counter" | "histogram";
50
+ instrument: Counter | Histogram;
51
+ }
52
+
53
+ interface SharedState {
54
+ agentName: string;
55
+ meter: Meter | undefined;
56
+ collector: OtelCollector | undefined;
57
+ instruments: Map<string, CachedInstrument>;
58
+ /** Used when no collector is attached so bound instances still share tags. */
59
+ sessionTags: Map<string, Record<string, AttrValue>>;
60
+ }
61
+
62
+ interface BoundState {
63
+ shared: SharedState;
64
+ sessionId: string | undefined;
65
+ localTags: Record<string, AttrValue>;
66
+ }
67
+
68
+ /** Build the host OpenTelemetry API for one agent process. */
69
+ export function createHostOtelApi(
70
+ options: CreateHostOtelApiOptions
71
+ ): HostOtelSessionApi {
72
+ return createBoundHostOtelApi({
73
+ shared: {
74
+ agentName: options.agentName,
75
+ meter: options.meter,
76
+ collector: options.collector,
77
+ instruments: new Map(),
78
+ sessionTags: new Map(),
79
+ },
80
+ sessionId: options.sessionId,
81
+ localTags: {},
82
+ });
83
+ }
84
+
85
+ function createBoundHostOtelApi(state: BoundState): HostOtelSessionApi {
86
+ const api: HostOtelSessionApi = {
87
+ get enabled(): boolean {
88
+ return state.shared.meter !== undefined;
89
+ },
90
+
91
+ setAttributes(attributes) {
92
+ const cleaned = validateAttributes(attributes);
93
+ if (state.sessionId === undefined) {
94
+ Object.assign(state.localTags, cleaned);
95
+ return;
96
+ }
97
+ mergeSessionTags(state, state.sessionId, cleaned);
98
+ state.shared.collector?.setSessionAttributes(state.sessionId, cleaned);
99
+ },
100
+
101
+ increment(
102
+ name: string,
103
+ valueOrAttributes?: number | HostOtelAttributes,
104
+ maybeAttributes?: HostOtelAttributes
105
+ ) {
106
+ const value =
107
+ typeof valueOrAttributes === "number" ? valueOrAttributes : 1;
108
+ const attributes =
109
+ typeof valueOrAttributes === "number"
110
+ ? maybeAttributes
111
+ : valueOrAttributes;
112
+ counter(state.shared, name)?.add(
113
+ value,
114
+ emitAttributes(state, attributes)
115
+ );
116
+ },
117
+
118
+ record(name, value, attributes) {
119
+ histogram(state.shared, name)?.record(
120
+ value,
121
+ emitAttributes(state, attributes)
122
+ );
123
+ },
124
+
125
+ forSession(sessionId) {
126
+ const trimmed = sessionId.trim();
127
+ if (trimmed === "") {
128
+ throw new Error("host.otel.forSession requires a non-empty session id");
129
+ }
130
+ return createBoundHostOtelApi({
131
+ shared: state.shared,
132
+ sessionId: trimmed,
133
+ localTags: {},
134
+ });
135
+ },
136
+ };
137
+ return api;
138
+ }
139
+
140
+ function mergeSessionTags(
141
+ state: BoundState,
142
+ sessionId: string,
143
+ incoming: Record<string, AttrValue>
144
+ ): void {
145
+ const bag = state.shared.sessionTags.get(sessionId) ?? {};
146
+ Object.assign(bag, incoming);
147
+ state.shared.sessionTags.set(sessionId, bag);
148
+ }
149
+
150
+ function emitAttributes(
151
+ state: BoundState,
152
+ callSite: HostOtelAttributes | undefined
153
+ ): Record<string, AttrValue> {
154
+ return attrs({
155
+ ...sessionBag(state),
156
+ ...validateAttributes(callSite),
157
+ "agent_sdk.agent": state.shared.agentName,
158
+ ...(state.sessionId === undefined
159
+ ? {}
160
+ : { "cursor.conversation.id": state.sessionId }),
161
+ });
162
+ }
163
+
164
+ function sessionBag(state: BoundState): Record<string, AttrValue> {
165
+ if (state.sessionId === undefined) {
166
+ return state.localTags;
167
+ }
168
+ const fromCollector = state.shared.collector?.getSessionAttributes(
169
+ state.sessionId
170
+ );
171
+ const fromShared = state.shared.sessionTags.get(state.sessionId) ?? {};
172
+ return {
173
+ ...fromShared,
174
+ ...(fromCollector ?? {}),
175
+ ...state.localTags,
176
+ };
177
+ }
178
+
179
+ function counter(shared: SharedState, name: string): Counter | undefined {
180
+ validateMetricName(name);
181
+ if (shared.meter === undefined) {
182
+ return undefined;
183
+ }
184
+ const existing = shared.instruments.get(name);
185
+ if (existing !== undefined) {
186
+ if (existing.kind !== "counter") {
187
+ throw new Error(`host.otel metric "${name}" is already a histogram`);
188
+ }
189
+ return existing.instrument as Counter;
190
+ }
191
+ const instrument = shared.meter.createCounter(name);
192
+ shared.instruments.set(name, { kind: "counter", instrument });
193
+ return instrument;
194
+ }
195
+
196
+ function histogram(shared: SharedState, name: string): Histogram | undefined {
197
+ validateMetricName(name);
198
+ if (shared.meter === undefined) {
199
+ return undefined;
200
+ }
201
+ const existing = shared.instruments.get(name);
202
+ if (existing !== undefined) {
203
+ if (existing.kind !== "histogram") {
204
+ throw new Error(`host.otel metric "${name}" is already a counter`);
205
+ }
206
+ return existing.instrument as Histogram;
207
+ }
208
+ const instrument = shared.meter.createHistogram(name);
209
+ shared.instruments.set(name, { kind: "histogram", instrument });
210
+ return instrument;
211
+ }
212
+
213
+ function validateMetricName(name: string): void {
214
+ if (!METRIC_NAME.test(name)) {
215
+ throw new Error(
216
+ `host.otel metric name "${name}" must match [A-Za-z][A-Za-z0-9_./-]*`
217
+ );
218
+ }
219
+ if (RESERVED_METRICS.has(name)) {
220
+ throw new Error(`host.otel metric name "${name}" is reserved`);
221
+ }
222
+ }
223
+
224
+ function validateAttributes(
225
+ attributes: HostOtelAttributes | undefined
226
+ ): Record<string, AttrValue> {
227
+ if (attributes === undefined) {
228
+ return {};
229
+ }
230
+ const out: Record<string, AttrValue> = {};
231
+ for (const [key, value] of Object.entries(attributes)) {
232
+ if (value === undefined) {
233
+ continue;
234
+ }
235
+ if (RESERVED_ATTRIBUTES.has(key)) {
236
+ throw new Error(
237
+ `host.otel attribute "${key}" is reserved; the runtime always sets it`
238
+ );
239
+ }
240
+ if (
241
+ typeof value !== "string" &&
242
+ typeof value !== "number" &&
243
+ typeof value !== "boolean"
244
+ ) {
245
+ throw new Error(
246
+ `host.otel attribute "${key}" must be a string, number, or boolean`
247
+ );
248
+ }
249
+ out[key] = value;
250
+ }
251
+ return out;
252
+ }
@@ -3,13 +3,14 @@
3
3
  */
4
4
 
5
5
  import { spawn } from "node:child_process";
6
- import { existsSync } from "node:fs";
7
- import { mkdir, writeFile } from "node:fs/promises";
6
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
7
+ import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
8
8
  import { basename, dirname, join, relative, resolve } from "node:path";
9
9
  import type { ModelSetting } from "../types.js";
10
10
  import {
11
11
  CLI_COMMAND_NAME,
12
12
  PACKAGE_NAME,
13
+ packageRootDir,
13
14
  packageVersion,
14
15
  } from "./distribution.js";
15
16
  import {
@@ -25,6 +26,7 @@ export interface InitProjectOptions {
25
26
  name?: string;
26
27
  /** Model id override written into agent.ts. */
27
28
  model?: string;
29
+ templateDir?: string;
28
30
  }
29
31
 
30
32
  export type InitFileAction = "create" | "exist";
@@ -44,6 +46,99 @@ export interface InitProjectResult {
44
46
  skipped: string[];
45
47
  }
46
48
 
49
+ export function availableTemplates(): string[] {
50
+ const templatesDir = join(packageRootDir(), "templates");
51
+ try {
52
+ return readdirSync(templatesDir, { withFileTypes: true })
53
+ .filter((entry) => entry.isDirectory())
54
+ .map((entry) => entry.name)
55
+ .sort();
56
+ } catch (error) {
57
+ if (hasErrorCode(error, "ENOENT")) {
58
+ return [];
59
+ }
60
+ throw error;
61
+ }
62
+ }
63
+
64
+ export type TemplateResolution =
65
+ | { ok: true; name: string; dir: string }
66
+ | { ok: false; message: string };
67
+
68
+ export function resolveTemplate(name: string): TemplateResolution {
69
+ if (!/^[a-z][a-z0-9-]*$/.test(name)) {
70
+ return {
71
+ ok: false,
72
+ message: "template name must match /^[a-z][a-z0-9-]*$/",
73
+ };
74
+ }
75
+ const available = availableTemplates();
76
+ if (!available.includes(name)) {
77
+ const detail =
78
+ available.length === 0
79
+ ? "no templates ship with this build"
80
+ : `available: ${available.join(", ")}`;
81
+ return {
82
+ ok: false,
83
+ message: `unknown template "${name}" (${detail})`,
84
+ };
85
+ }
86
+ return {
87
+ ok: true,
88
+ name,
89
+ dir: join(packageRootDir(), "templates", name),
90
+ };
91
+ }
92
+
93
+ export type InitTargetCheck = { ok: true } | { ok: false; message: string };
94
+
95
+ /**
96
+ * Re-init must keep the mode the project was scaffolded with: template
97
+ * scaffolds stamp `cursorAgentTemplate` into package.json, and a mismatch
98
+ * here would silently mix default-scaffold files into a template project
99
+ * (or the reverse).
100
+ */
101
+ export function checkInitTarget(
102
+ rootDir: string,
103
+ template: string | undefined
104
+ ): InitTargetCheck {
105
+ const pkgPath = join(rootDir, "package.json");
106
+ if (!existsSync(pkgPath)) {
107
+ return { ok: true };
108
+ }
109
+ let marker: string | undefined;
110
+ try {
111
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8")) as {
112
+ cursorAgentTemplate?: unknown;
113
+ };
114
+ marker =
115
+ typeof pkg.cursorAgentTemplate === "string"
116
+ ? pkg.cursorAgentTemplate
117
+ : undefined;
118
+ } catch {
119
+ marker = undefined;
120
+ }
121
+ if (marker === template) {
122
+ return { ok: true };
123
+ }
124
+ if (template === undefined) {
125
+ return {
126
+ ok: false,
127
+ message: `${rootDir} was scaffolded from the "${marker}" template; re-run with --template ${marker}`,
128
+ };
129
+ }
130
+ if (marker === undefined) {
131
+ return {
132
+ ok: false,
133
+ message: `${rootDir} already has a package.json without template provenance; re-run init without --template, or scaffold into a fresh directory`,
134
+ };
135
+ }
136
+ return {
137
+ ok: false,
138
+ message: `${rootDir} was scaffolded from the "${marker}" template, not "${template}"`,
139
+ };
140
+ }
141
+
47
142
  const DEFAULT_MODEL: ModelSetting = {
48
143
  id: "grok-4.5",
49
144
  params: [
@@ -73,13 +168,15 @@ export async function initProject(
73
168
  options: InitProjectOptions
74
169
  ): Promise<InitProjectResult> {
75
170
  const rootDir = resolve(options.dir);
76
- const name = options.name ?? basename(rootDir);
171
+ const projectName = basename(rootDir);
172
+ const name = options.name ?? projectName;
77
173
  const model = formatModelLiteral(options.model ?? DEFAULT_MODEL);
78
- const agentDir = join(rootDir, "agent");
79
- await mkdir(agentDir, { recursive: true });
80
174
 
81
175
  const files: InitFileEntry[] = [];
82
- const write = async (rel: string, contents: string): Promise<void> => {
176
+ const write = async (
177
+ rel: string,
178
+ contents: string | Uint8Array
179
+ ): Promise<void> => {
83
180
  const abs = join(rootDir, rel);
84
181
  await mkdir(dirname(abs), { recursive: true });
85
182
  try {
@@ -95,6 +192,47 @@ export async function initProject(
95
192
  }
96
193
  };
97
194
 
195
+ if (options.templateDir !== undefined) {
196
+ const templateDir = options.templateDir;
197
+ const entries = await readdir(templateDir, {
198
+ recursive: true,
199
+ withFileTypes: true,
200
+ });
201
+ const relativePaths = entries
202
+ .filter((entry) => entry.isFile())
203
+ .map((entry) => relative(templateDir, join(entry.parentPath, entry.name)))
204
+ .sort();
205
+ for (const rel of relativePaths) {
206
+ const source = join(templateDir, rel);
207
+ if (rel === "package.json") {
208
+ const packageJson: {
209
+ name: string;
210
+ dependencies: Record<string, string>;
211
+ [key: string]: unknown;
212
+ } = JSON.parse(await readFile(source, "utf8"));
213
+ packageJson.name = projectName;
214
+ packageJson.dependencies[PACKAGE_NAME] = `^${packageVersion()}`;
215
+ packageJson.cursorAgentTemplate = basename(templateDir);
216
+ await write(rel, `${JSON.stringify(packageJson, null, 2)}\n`);
217
+ } else {
218
+ await write(rel, await readFile(source));
219
+ }
220
+ }
221
+ return {
222
+ rootDir,
223
+ files,
224
+ created: files
225
+ .filter((entry) => entry.action === "create")
226
+ .map((entry) => entry.path),
227
+ skipped: files
228
+ .filter((entry) => entry.action === "exist")
229
+ .map((entry) => entry.path),
230
+ };
231
+ }
232
+
233
+ const agentDir = join(rootDir, "agent");
234
+ await mkdir(agentDir, { recursive: true });
235
+
98
236
  await write(
99
237
  "package.json",
100
238
  `${JSON.stringify(
@@ -209,11 +347,15 @@ export default memoryHook();
209
347
  }
210
348
 
211
349
  export function isEexist(error: unknown): boolean {
350
+ return hasErrorCode(error, "EEXIST");
351
+ }
352
+
353
+ function hasErrorCode(error: unknown, code: string): boolean {
212
354
  return (
213
355
  error !== null &&
214
356
  typeof error === "object" &&
215
357
  "code" in error &&
216
- (error as { code: string }).code === "EEXIST"
358
+ error.code === code
217
359
  );
218
360
  }
219
361
 
@@ -557,20 +699,20 @@ function initNextStepEntries(options: InitNextStepsOptions): Array<{
557
699
  if (cd !== undefined) {
558
700
  steps.push({ command: cd });
559
701
  }
560
- if (!options.cliOnPath) {
561
- steps.push({
562
- command: 'export PATH="$PWD/node_modules/.bin:$PATH"',
563
- note: "use the project-local agent-sdk bin",
564
- });
565
- }
566
702
  if (!options.signedIn) {
703
+ const login = options.cliOnPath
704
+ ? `${CLI_COMMAND_NAME} login`
705
+ : `npx ${PACKAGE_NAME} login`;
567
706
  steps.push({
568
- command: `${CLI_COMMAND_NAME} login`,
707
+ command: login,
569
708
  note: "connect your Cursor account (powers model turns)",
570
709
  });
571
710
  }
711
+ const dev = options.cliOnPath
712
+ ? `${CLI_COMMAND_NAME} dev`
713
+ : `npx ${PACKAGE_NAME} dev`;
572
714
  steps.push({
573
- command: `${CLI_COMMAND_NAME} dev`,
715
+ command: dev,
574
716
  note: "start the local playground",
575
717
  });
576
718
  return steps;
@@ -597,8 +739,9 @@ export function printInitGuide(): void {
597
739
  process.stdout.write(`${CLI_COMMAND_NAME} — coding agent setup
598
740
 
599
741
  Scaffold a project, then open the playground:
600
- npx ${PACKAGE_NAME} init ./my-agent # installs deps + puts agent-sdk on PATH
601
- cd ./my-agent && ${CLI_COMMAND_NAME} dev
742
+ npx ${PACKAGE_NAME} init ./my-agent # installs deps + links agent-sdk onto PATH
743
+ npx ${PACKAGE_NAME} init ./my-triage --template triage # start from a template (agent-sdk templates: triage)
744
+ cd ./my-agent && agent-sdk dev # or: npx ${PACKAGE_NAME} dev
602
745
 
603
746
  Recommended loop once the project exists (from the project directory):
604
747
  1. Edit agent/ + tools under agent/tools/