@cursor/july 0.1.43 → 0.1.46

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 (197) hide show
  1. package/AGENTS.md +5 -3
  2. package/README.md +3 -1
  3. package/dist/channels/github/api.d.ts +25 -0
  4. package/dist/channels/github/api.d.ts.map +1 -1
  5. package/dist/channels/github/api.js +48 -0
  6. package/dist/channels/github/cursor-account.d.ts +7 -0
  7. package/dist/channels/github/cursor-account.d.ts.map +1 -1
  8. package/dist/channels/github/cursor-account.js +18 -0
  9. package/dist/channels/github/defaults.d.ts +1 -1
  10. package/dist/channels/github/defaults.js +1 -1
  11. package/dist/channels/github/github-channel.d.ts.map +1 -1
  12. package/dist/channels/github/github-channel.js +15 -7
  13. package/dist/channels/github/progress.d.ts +6 -5
  14. package/dist/channels/github/progress.d.ts.map +1 -1
  15. package/dist/channels/github/progress.js +58 -16
  16. package/dist/channels/github/state.d.ts +11 -0
  17. package/dist/channels/github/state.d.ts.map +1 -1
  18. package/dist/channels/github/state.js +1 -1
  19. package/dist/channels/github/types.d.ts +11 -3
  20. package/dist/channels/github/types.d.ts.map +1 -1
  21. package/dist/channels.d.ts +17 -0
  22. package/dist/channels.d.ts.map +1 -1
  23. package/dist/channels.js +31 -0
  24. package/dist/docs/404.html +2 -2
  25. package/dist/docs/ab.html +3 -3
  26. package/dist/docs/assets/{app.DsTLo2j1.js → app.C2NFTb-h.js} +1 -1
  27. package/dist/docs/assets/chunks/@localSearchIndexroot.B-FxvuoI.js +1 -0
  28. package/dist/docs/assets/chunks/{VPLocalSearchBox.DoVfutcV.js → VPLocalSearchBox.o1_FJGip.js} +1 -1
  29. package/dist/docs/assets/chunks/{theme.DtuVtKkc.js → theme.BcGduE7g.js} +2 -2
  30. package/dist/docs/assets/{guides_github.md.DSg0j9qj.js → guides_github.md.B8QKtIpw.js} +7 -6
  31. package/dist/docs/assets/{guides_github.md.DSg0j9qj.lean.js → guides_github.md.B8QKtIpw.lean.js} +1 -1
  32. package/dist/docs/assets/guides_opentelemetry.md.Csn7ZI25.js +27 -0
  33. package/dist/docs/assets/guides_opentelemetry.md.Csn7ZI25.lean.js +1 -0
  34. package/dist/docs/assets/index.md.COeoSkcF.js +5 -0
  35. package/dist/docs/assets/index.md.COeoSkcF.lean.js +1 -0
  36. package/dist/docs/assets/{quickstart.md.rIbfahJM.js → quickstart.md.DZxBu44y.js} +18 -49
  37. package/dist/docs/assets/quickstart.md.DZxBu44y.lean.js +1 -0
  38. package/dist/docs/assets/{reference_agent-config.md.CRmkoxd6.js → reference_agent-config.md.DHkkvcEZ.js} +9 -4
  39. package/dist/docs/assets/{reference_agent-config.md.CRmkoxd6.lean.js → reference_agent-config.md.DHkkvcEZ.lean.js} +1 -1
  40. package/dist/docs/assets/{reference_cli.md.DqEqQpGn.js → reference_cli.md.BDmjPPAm.js} +2 -2
  41. package/dist/docs/assets/{reference_hooks.md.4cbRRIIu.js → reference_hooks.md.DJE5DXcT.js} +1 -1
  42. package/dist/docs/assets/{reference_tools.md.lSrsTxYJ.js → reference_tools.md.kJfJAP2m.js} +1 -1
  43. package/dist/docs/assets/{storage.md.DcGSnIxX.js → storage.md.CJYd5YmN.js} +2 -2
  44. package/dist/docs/assets/{storage.md.DcGSnIxX.lean.js → storage.md.CJYd5YmN.lean.js} +1 -1
  45. package/dist/docs/building-with-agents.html +3 -3
  46. package/dist/docs/concepts.html +3 -3
  47. package/dist/docs/deployment.html +3 -3
  48. package/dist/docs/evals.html +3 -3
  49. package/dist/docs/example-agents/approval-buddy.html +3 -3
  50. package/dist/docs/example-agents/benny.html +3 -3
  51. package/dist/docs/example-agents/bugbot.html +3 -3
  52. package/dist/docs/example-agents/codebase-wiki.html +3 -3
  53. package/dist/docs/example-agents/codeowners-review.html +3 -3
  54. package/dist/docs/example-agents/concierge.html +3 -3
  55. package/dist/docs/example-agents/fsd.html +3 -3
  56. package/dist/docs/example-agents/index.html +3 -3
  57. package/dist/docs/example-agents/knowledge-base.html +3 -3
  58. package/dist/docs/example-agents/oncall.html +3 -3
  59. package/dist/docs/example-agents/security-reviewer.html +3 -3
  60. package/dist/docs/example-agents/slack-agent.html +3 -3
  61. package/dist/docs/example-agents/weather-agent.html +3 -3
  62. package/dist/docs/guides/agent-to-agent.html +3 -3
  63. package/dist/docs/guides/cloud-runtime.html +3 -3
  64. package/dist/docs/guides/github.html +10 -9
  65. package/dist/docs/guides/human-in-the-loop.html +3 -3
  66. package/dist/docs/guides/mcp-oauth.html +3 -3
  67. package/dist/docs/guides/opentelemetry.html +12 -5
  68. package/dist/docs/guides/slack.html +3 -3
  69. package/dist/docs/guides/webhooks.html +3 -3
  70. package/dist/docs/hashmap.json +1 -1
  71. package/dist/docs/hillclimbing.html +3 -3
  72. package/dist/docs/index.html +8 -28
  73. package/dist/docs/quickstart.html +24 -55
  74. package/dist/docs/reference/agent-config.html +11 -6
  75. package/dist/docs/reference/artifacts.html +3 -3
  76. package/dist/docs/reference/channels.html +3 -3
  77. package/dist/docs/reference/cli.html +6 -6
  78. package/dist/docs/reference/connections.html +3 -3
  79. package/dist/docs/reference/hooks.html +5 -5
  80. package/dist/docs/reference/http-api.html +3 -3
  81. package/dist/docs/reference/instructions.html +3 -3
  82. package/dist/docs/reference/playground.html +3 -3
  83. package/dist/docs/reference/project-layout.html +3 -3
  84. package/dist/docs/reference/prompt.html +3 -3
  85. package/dist/docs/reference/schedules.html +3 -3
  86. package/dist/docs/reference/sessions.html +3 -3
  87. package/dist/docs/reference/skills.html +3 -3
  88. package/dist/docs/reference/subagents.html +3 -3
  89. package/dist/docs/reference/tools.html +5 -5
  90. package/dist/docs/scaffolding-agents.html +3 -3
  91. package/dist/docs/storage.html +5 -5
  92. package/dist/docs/templates/triage.html +3 -3
  93. package/dist/docs/troubleshooting.html +3 -3
  94. package/dist/index.d.ts +2 -2
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/index.js +2 -2
  97. package/dist/internal/auth.d.ts +4 -0
  98. package/dist/internal/auth.d.ts.map +1 -1
  99. package/dist/internal/auth.js +7 -0
  100. package/dist/internal/cli-ax.js +2 -2
  101. package/dist/internal/cli-deploy.d.ts.map +1 -1
  102. package/dist/internal/cli-deploy.js +38 -7
  103. package/dist/internal/deploy-client.d.ts +8 -0
  104. package/dist/internal/deploy-client.d.ts.map +1 -1
  105. package/dist/internal/deploy-client.js +5 -2
  106. package/dist/internal/discovery.d.ts.map +1 -1
  107. package/dist/internal/discovery.js +46 -9
  108. package/dist/internal/durable-session-store.d.ts +35 -23
  109. package/dist/internal/durable-session-store.d.ts.map +1 -1
  110. package/dist/internal/durable-session-store.js +71 -26
  111. package/dist/internal/host-otel.d.ts +25 -0
  112. package/dist/internal/host-otel.d.ts.map +1 -0
  113. package/dist/internal/host-otel.js +162 -0
  114. package/dist/internal/init-project.js +10 -10
  115. package/dist/internal/ledger-serving.d.ts +135 -67
  116. package/dist/internal/ledger-serving.d.ts.map +1 -1
  117. package/dist/internal/ledger-serving.js +326 -107
  118. package/dist/internal/otel/collector.d.ts +5 -0
  119. package/dist/internal/otel/collector.d.ts.map +1 -1
  120. package/dist/internal/otel/collector.js +68 -27
  121. package/dist/internal/sdk-runner.d.ts +1 -1
  122. package/dist/internal/sdk-runner.d.ts.map +1 -1
  123. package/dist/internal/sdk-runner.js +1 -1
  124. package/dist/internal/server.d.ts.map +1 -1
  125. package/dist/internal/server.js +3 -2
  126. package/dist/internal/session-engine.d.ts +23 -18
  127. package/dist/internal/session-engine.d.ts.map +1 -1
  128. package/dist/internal/session-engine.js +83 -32
  129. package/dist/kv/memory-kv.d.ts +8 -0
  130. package/dist/kv/memory-kv.d.ts.map +1 -1
  131. package/dist/kv/memory-kv.js +8 -0
  132. package/dist/ledger/janitor.d.ts +60 -0
  133. package/dist/ledger/janitor.d.ts.map +1 -0
  134. package/dist/ledger/janitor.js +137 -0
  135. package/dist/ledger/session-ledger.d.ts +132 -43
  136. package/dist/ledger/session-ledger.d.ts.map +1 -1
  137. package/dist/ledger/session-ledger.js +282 -50
  138. package/dist/ledger.d.ts +9 -8
  139. package/dist/ledger.d.ts.map +1 -1
  140. package/dist/ledger.js +7 -7
  141. package/dist/playground/assets/{index-D-DIYQzw.js → index-6wW6SAc4.js} +19 -19
  142. package/dist/playground/assets/index-CSRYDAFw.css +1 -0
  143. package/dist/playground/index.html +2 -2
  144. package/dist/storage-backends/cursor-hosted.d.ts +5 -0
  145. package/dist/storage-backends/cursor-hosted.d.ts.map +1 -1
  146. package/dist/storage-backends/cursor-hosted.js +6 -0
  147. package/dist/types.d.ts +100 -10
  148. package/dist/types.d.ts.map +1 -1
  149. package/docs/README.md +33 -106
  150. package/docs/guides/github.md +23 -18
  151. package/docs/guides/opentelemetry.md +30 -0
  152. package/docs/quickstart.md +89 -227
  153. package/docs/reference/agent-config.md +47 -0
  154. package/docs/reference/cli.md +2 -2
  155. package/docs/reference/hooks.md +1 -1
  156. package/docs/reference/tools.md +5 -4
  157. package/docs/storage.md +26 -9
  158. package/package.json +2 -2
  159. package/skills/otel/SKILL.md +20 -2
  160. package/src/channels/github/api.ts +122 -0
  161. package/src/channels/github/cursor-account.ts +23 -0
  162. package/src/channels/github/defaults.ts +1 -1
  163. package/src/channels/github/github-channel.ts +14 -1
  164. package/src/channels/github/progress.ts +84 -19
  165. package/src/channels/github/state.ts +14 -0
  166. package/src/channels/github/types.ts +7 -3
  167. package/src/channels.ts +38 -0
  168. package/src/index.ts +2 -0
  169. package/src/internal/auth.ts +10 -0
  170. package/src/internal/cli-ax.ts +2 -2
  171. package/src/internal/cli-deploy.ts +58 -3
  172. package/src/internal/deploy-client.ts +11 -0
  173. package/src/internal/discovery.ts +49 -0
  174. package/src/internal/durable-session-store.ts +66 -27
  175. package/src/internal/host-otel.ts +252 -0
  176. package/src/internal/init-project.ts +10 -10
  177. package/src/internal/ledger-serving.ts +439 -148
  178. package/src/internal/otel/collector.ts +135 -50
  179. package/src/internal/sdk-runner.ts +6 -0
  180. package/src/internal/server.ts +7 -2
  181. package/src/internal/session-engine.ts +127 -39
  182. package/src/kv/memory-kv.ts +8 -0
  183. package/src/ledger/janitor.ts +193 -0
  184. package/src/ledger/session-ledger.ts +372 -68
  185. package/src/ledger.ts +21 -6
  186. package/src/storage-backends/cursor-hosted.ts +17 -2
  187. package/src/types.ts +108 -10
  188. package/dist/docs/assets/chunks/@localSearchIndexroot.BZx4pmZL.js +0 -1
  189. package/dist/docs/assets/guides_opentelemetry.md.DmyUwC5R.js +0 -20
  190. package/dist/docs/assets/guides_opentelemetry.md.DmyUwC5R.lean.js +0 -1
  191. package/dist/docs/assets/index.md.CMc5FP7E.js +0 -25
  192. package/dist/docs/assets/index.md.CMc5FP7E.lean.js +0 -1
  193. package/dist/docs/assets/quickstart.md.rIbfahJM.lean.js +0 -1
  194. package/dist/playground/assets/index-DQTH622f.css +0 -1
  195. /package/dist/docs/assets/{reference_cli.md.DqEqQpGn.lean.js → reference_cli.md.BDmjPPAm.lean.js} +0 -0
  196. /package/dist/docs/assets/{reference_hooks.md.4cbRRIIu.lean.js → reference_hooks.md.DJE5DXcT.lean.js} +0 -0
  197. /package/dist/docs/assets/{reference_tools.md.lSrsTxYJ.lean.js → reference_tools.md.kJfJAP2m.lean.js} +0 -0
package/docs/storage.md CHANGED
@@ -213,12 +213,29 @@ server).
213
213
 
214
214
  Conditional writes are the substrate for the session ledger, which
215
215
  ships behind `defineAgent({ serving: "ledger" })`: session identity,
216
- one-writer-per-session leases with generation fencing, and a fenced
217
- commit sequence, coordinated through the agent's storage backend by
218
- default (its `cas` group) or an explicit `durableSessionStore`
219
- override. The ledger has **no durable inbox** an event arriving while
220
- a session's lease is held waits in-process on the delivering pod, so
221
- crash durability matches resident serving; what ledger mode adds is
222
- cross-pod mutual exclusion, zombie fencing, and the committed head
223
- sequence. A backend that implements `cas` needs no storage migration to
224
- adopt it.
216
+ one-writer-per-session leases with generation fencing, a fenced commit
217
+ sequence, and a **durable inbox**. A ledger-mode delivery parks its
218
+ event in the session mailbox first the source ack point moves after
219
+ that write then tries the lease: if another holder is serving the
220
+ session, the delivery walks away and the holder's commit drains the
221
+ parked event in a follow-up turn under the same lease. Sessions whose
222
+ holder died with events pending are recovered by a janitor loop that
223
+ scans ledgers by prefix, which is why ledger mode requires the full
224
+ `cas` group **including `listKeys`**: the janitor scan and the orphan
225
+ sweep are prefix scans, and mailbox payload blobs go through the
226
+ backend's plain `get`/`put`/`delete`. Coordination runs through the
227
+ agent's storage backend by default or an explicit `durableSessionStore`
228
+ override. Either way you supply the **raw** store: the engine
229
+ namespaces every ledger key under
230
+ `agentkit/v1/{agent}/durable-session/` itself, at the same boundary
231
+ where `host.kv` gets its agent scoping — a self-hoster never writes
232
+ prefixes. A backend that implements `cas` needs no storage migration to
233
+ adopt this.
234
+
235
+ One sharp edge: the `cas` group and the plain methods must be backed by
236
+ the **same durable store**. Pairing a real plain backend with
237
+ `memoryCasTable()` (or any process-local `cas` stand-in) and
238
+ `serving: "ledger"` silently voids ack-after-durable — the mailbox
239
+ blobs persist but the leases, heads, and pending indexes evaporate on
240
+ restart, so acked events are never redelivered. `memoryCasTable()` is
241
+ for test fixtures and inert sinks only.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cursor/july",
3
- "version": "0.1.43",
3
+ "version": "0.1.46",
4
4
  "description": "(early alpha) Filesystem-first framework for defining Cursor agents as markdown and TypeScript and serving them over channels with the Cursor SDK.",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "repository": {
@@ -215,7 +215,7 @@
215
215
  "node": ">=22.13"
216
216
  },
217
217
  "dependencies": {
218
- "@cursor/sdk": "1.0.26",
218
+ "@cursor/sdk": "1.0.27",
219
219
  "@modelcontextprotocol/sdk": "1.25.1",
220
220
  "@octokit/auth-app": "^7.2.0",
221
221
  "@octokit/rest": "^21.1.1",
@@ -2,8 +2,8 @@
2
2
  name: agentkit-otel
3
3
  description: >-
4
4
  Wire OpenTelemetry export for an Agent SDK agent (defineOtel, OTLP env,
5
- traces/metrics/logs). Use when adding observability, Datadog/Grafana/OTLP
6
- export, or session/turn/tool tracing.
5
+ traces/metrics/logs, ctx.host.otel). Use when adding observability,
6
+ Datadog/Grafana/OTLP export, session/turn/tool tracing, or custom metrics.
7
7
  ---
8
8
 
9
9
  # Agent SDK OpenTelemetry (`defineOtel`)
@@ -61,6 +61,24 @@ Precedence: `serve({ otel })` over `agent/otel.ts` over env.
61
61
  Prompt text, tool payloads, and failure text stay off the wire unless
62
62
  those flags are set.
63
63
 
64
+ ## Custom metrics (`ctx.host.otel`)
65
+
66
+ Always present. `increment` / `record` no-op without a meter.
67
+ `setAttributes` still tags the open session when a collector is
68
+ attached. Prefix names with team or agent. No custom spans. Reserved
69
+ metric names: `cursor.token.usage`, `cursor.tool.calls`,
70
+ `cursor.cost.usage`, `agent_sdk.session.count`,
71
+ `agent_sdk.turn.count`, `agent_sdk.subagent.count`,
72
+ `agent_sdk.http.duration`. Reserved attributes:
73
+ `cursor.conversation.id`, `agent_sdk.agent`, `agent_sdk.turn_id`,
74
+ `agent_sdk.framework`.
75
+
76
+ ```ts
77
+ ctx.host.otel.setAttributes({ "abc.ticket_id": "INC-123" });
78
+ ctx.host.otel.increment("abc.ticket.resolved");
79
+ ctx.host.otel.record("abc.approval.duration_ms", 1420);
80
+ ```
81
+
64
82
  ## Attributes worth joining on
65
83
 
66
84
  - `cursor.conversation.id` (session id)
@@ -384,6 +384,128 @@ export async function createGitHubCommitStatus(
384
384
  }
385
385
  }
386
386
 
387
+ export type GitHubCheckRunStatus = "queued" | "in_progress" | "completed";
388
+ export type GitHubCheckRunConclusion =
389
+ | "action_required"
390
+ | "cancelled"
391
+ | "failure"
392
+ | "neutral"
393
+ | "skipped"
394
+ | "success"
395
+ | "timed_out";
396
+
397
+ export interface GitHubCheckRun {
398
+ htmlUrl: string | undefined;
399
+ id: number;
400
+ raw: unknown;
401
+ }
402
+
403
+ export interface GitHubCheckRunWriteInput {
404
+ conclusion?: GitHubCheckRunConclusion;
405
+ detailsUrl?: string;
406
+ name: string;
407
+ output?: { summary: string; title: string };
408
+ status: GitHubCheckRunStatus;
409
+ }
410
+
411
+ /** Create a check run (Checks API). */
412
+ export async function createGitHubCheckRun(
413
+ input: GitHubResourceInput &
414
+ GitHubCheckRunWriteInput & {
415
+ sha: string;
416
+ }
417
+ ): Promise<GitHubCheckRun> {
418
+ const octokit = await createGitHubOctokit(input);
419
+ const now = new Date().toISOString();
420
+ try {
421
+ const response = await octokit.rest.checks.create({
422
+ owner: input.owner,
423
+ repo: input.repo,
424
+ name: input.name,
425
+ head_sha: input.sha,
426
+ status: input.status,
427
+ started_at: now,
428
+ ...checkRunCompletionFields(input, now),
429
+ ...checkRunDetailsUrl(input),
430
+ ...checkRunOutput(input),
431
+ });
432
+ return toCheckRun(response.data);
433
+ } catch (error) {
434
+ throw wrapOctokitError(
435
+ error,
436
+ "POST",
437
+ `/repos/${input.owner}/${input.repo}/check-runs`
438
+ );
439
+ }
440
+ }
441
+
442
+ /** Update a check run (Checks API). */
443
+ export async function updateGitHubCheckRun(
444
+ input: GitHubResourceInput &
445
+ GitHubCheckRunWriteInput & {
446
+ checkRunId: number;
447
+ }
448
+ ): Promise<GitHubCheckRun> {
449
+ const octokit = await createGitHubOctokit(input);
450
+ try {
451
+ const response = await octokit.rest.checks.update({
452
+ owner: input.owner,
453
+ repo: input.repo,
454
+ check_run_id: input.checkRunId,
455
+ name: input.name,
456
+ status: input.status,
457
+ ...checkRunCompletionFields(input, new Date().toISOString()),
458
+ ...checkRunDetailsUrl(input),
459
+ ...checkRunOutput(input),
460
+ });
461
+ return toCheckRun(response.data);
462
+ } catch (error) {
463
+ throw wrapOctokitError(
464
+ error,
465
+ "PATCH",
466
+ `/repos/${input.owner}/${input.repo}/check-runs/${input.checkRunId}`
467
+ );
468
+ }
469
+ }
470
+
471
+ function checkRunCompletionFields(
472
+ input: GitHubCheckRunWriteInput,
473
+ completedAt: string
474
+ ): {
475
+ completed_at?: string;
476
+ conclusion?: GitHubCheckRunConclusion;
477
+ } {
478
+ if (input.status !== "completed" || input.conclusion === undefined) {
479
+ return {};
480
+ }
481
+ return { completed_at: completedAt, conclusion: input.conclusion };
482
+ }
483
+
484
+ function checkRunDetailsUrl(input: GitHubCheckRunWriteInput): {
485
+ details_url?: string;
486
+ } {
487
+ return input.detailsUrl === undefined
488
+ ? {}
489
+ : { details_url: input.detailsUrl };
490
+ }
491
+
492
+ function checkRunOutput(input: GitHubCheckRunWriteInput): {
493
+ output?: { summary: string; title: string };
494
+ } {
495
+ return input.output === undefined ? {} : { output: input.output };
496
+ }
497
+
498
+ function toCheckRun(raw: {
499
+ html_url?: string | null;
500
+ id?: number;
501
+ }): GitHubCheckRun {
502
+ return {
503
+ htmlUrl: typeof raw.html_url === "string" ? raw.html_url : undefined,
504
+ id: typeof raw.id === "number" ? raw.id : 0,
505
+ raw,
506
+ };
507
+ }
508
+
387
509
  function toPostedComment(raw: {
388
510
  html_url?: string;
389
511
  id?: number;
@@ -117,6 +117,29 @@ export function parseCursorGithubRepoScope(
117
117
  };
118
118
  }
119
119
 
120
+ /**
121
+ * Hosted `cursorAccount` mints `checks:write` on the `contents-write` tier.
122
+ * Enabling `progress.commitStatus` opts a `pr-write` (or omitted) channel up
123
+ * to that tier so github-proxy / `/github-token` can post Checks API runs.
124
+ * `"read"` cannot post merge-box checks.
125
+ */
126
+ export function cursorGithubPermissionsForChannel(
127
+ options: Pick<GitHubChannelOptions, "progress">,
128
+ authored: CursorGithubPermissions | undefined = "pr-write"
129
+ ): CursorGithubPermissions {
130
+ const base = authored ?? "pr-write";
131
+ const commitStatus = options.progress?.commitStatus;
132
+ if (commitStatus === undefined || commitStatus === false) {
133
+ return base;
134
+ }
135
+ if (base === "read") {
136
+ throw new Error(
137
+ 'progress.commitStatus needs checks:write; cursorAccount permissions: "read" cannot post merge-box checks. Use "pr-write" or "contents-write".'
138
+ );
139
+ }
140
+ return "contents-write";
141
+ }
142
+
120
143
  /** One host credential: the most privileged requested tier wins. */
121
144
  export function resolveCursorGithubPermissions(
122
145
  values: readonly CursorGithubPermissions[]
@@ -19,7 +19,7 @@ export interface BuildDefaultGitHubEventsOptions extends GitHubBindingConfig {
19
19
  }
20
20
 
21
21
  /**
22
- * Built-in delivery: eyes reaction on turn start, optional commit status /
22
+ * Built-in delivery: eyes reaction on turn start, optional Checks API run /
23
23
  * sticky banner via {@link GitHubProgressConfig}, post assistant text on
24
24
  * `message.completed`, and a short error comment on turn/session failure.
25
25
  */
@@ -27,6 +27,7 @@ import {
27
27
  import {
28
28
  createCursorAccountGithubTokenResolver,
29
29
  createPendingCursorGithubCredentials,
30
+ cursorGithubPermissionsForChannel,
30
31
  type PendingCursorGithubCredentials,
31
32
  parseCursorGithubRepoScope,
32
33
  registerGithubCursorAccountRuntime,
@@ -137,6 +138,12 @@ const githubPullRequestPayloadSchema: z.ZodType<GitHubPullRequestPayload> =
137
138
  }),
138
139
  sender: senderSchema,
139
140
  installation: installationSchema,
141
+ label: z
142
+ .object({
143
+ name: z.string().min(1),
144
+ })
145
+ .nullable()
146
+ .optional(),
140
147
  });
141
148
 
142
149
  /**
@@ -336,6 +343,9 @@ export function parseGitHubPullRequestEvent(args: {
336
343
  ...(payload.pull_request.user?.login === undefined
337
344
  ? {}
338
345
  : { authorLogin: payload.pull_request.user.login }),
346
+ ...(payload.label?.name === undefined
347
+ ? {}
348
+ : { label: { name: payload.label.name } }),
339
349
  };
340
350
  return { ctx, pullRequest };
341
351
  }
@@ -599,7 +609,10 @@ export function githubChannel(
599
609
  typeof options.cursorAccount === "object"
600
610
  ? parseCursorGithubRepoScope(
601
611
  options.cursorAccount.repos,
602
- options.cursorAccount.permissions
612
+ cursorGithubPermissionsForChannel(
613
+ options,
614
+ options.cursorAccount.permissions
615
+ )
603
616
  )
604
617
  : undefined;
605
618
 
@@ -1,18 +1,20 @@
1
1
  /**
2
- * Opt-in PR progress surfaces: commit status + sticky banner comment.
2
+ * Opt-in PR progress surfaces: Checks API run + sticky banner comment.
3
3
  */
4
4
 
5
5
  import {
6
- createGitHubCommitStatus,
6
+ createGitHubCheckRun,
7
7
  createGitHubIssueComment,
8
8
  GitHubApiError,
9
+ type GitHubCheckRunWriteInput,
10
+ updateGitHubCheckRun,
9
11
  updateGitHubIssueComment,
10
12
  } from "./api.js";
11
13
  import type { GitHubBindingConfig } from "./binding.js";
12
14
  import { formatGitHubOutboundComment } from "./binding.js";
13
15
  import type { GitHubChannelState } from "./state.js";
14
16
 
15
- /** GitHub commit-status descriptions are capped at 140 characters. */
17
+ /** Merge-box check titles stay short; GitHub allows 1024, we clip to 140. */
16
18
  const STATUS_DESCRIPTION_MAX = 140;
17
19
 
18
20
  export type GitHubProgressPhase = "pending" | "success" | "failure";
@@ -24,7 +26,7 @@ export interface GitHubProgressPhaseCopy {
24
26
  }
25
27
 
26
28
  export interface GitHubCommitStatusProgress extends GitHubProgressPhaseCopy {
27
- /** Commit status context (unique per check in the merge box). */
29
+ /** Check run name (unique per check in the merge box). */
28
30
  context: string;
29
31
  }
30
32
 
@@ -34,9 +36,10 @@ export interface GitHubProgressConfig {
34
36
  /** When false, skip the eyes reaction on turn.started. Defaults to true. */
35
37
  reactions?: boolean;
36
38
  /**
37
- * Opt-in commit status on the PR head. Omit or `false` keeps today's
38
- * behavior (no statuses from default events). With `cursorAccount`,
39
- * needs `permissions: "contents-write"` (push-equivalent).
39
+ * Opt-in Checks API run on the PR head. Omit or `false` keeps today's
40
+ * behavior (no check from default events). With `cursorAccount`, enabling
41
+ * this opts the minted token up to `permissions: "contents-write"` so the
42
+ * hosted github-proxy lease includes `checks:write`.
40
43
  */
41
44
  commitStatus?: false | GitHubCommitStatusProgress;
42
45
  /**
@@ -127,7 +130,7 @@ export async function applyGitHubProgress(input: {
127
130
  const tasks: Array<Promise<void>> = [];
128
131
  if (commitStatus !== null) {
129
132
  tasks.push(
130
- postCommitStatus({
133
+ postProgressCheckRun({
131
134
  phase: input.phase,
132
135
  channel: input.channel,
133
136
  options: input.options,
@@ -149,7 +152,7 @@ export async function applyGitHubProgress(input: {
149
152
  await Promise.all(tasks);
150
153
  }
151
154
 
152
- async function postCommitStatus(input: {
155
+ async function postProgressCheckRun(input: {
153
156
  phase: GitHubProgressPhase;
154
157
  channel: { state: GitHubChannelState };
155
158
  options: GitHubProgressOptions;
@@ -164,6 +167,7 @@ async function postCommitStatus(input: {
164
167
  ) {
165
168
  return;
166
169
  }
170
+ const sha = state.headSha;
167
171
  const description = clipStatusDescription(
168
172
  progressPhaseCopy({
169
173
  copy: input.commitStatus,
@@ -171,23 +175,84 @@ async function postCommitStatus(input: {
171
175
  label: progressLabel(input.options, input.commitStatus.context),
172
176
  })
173
177
  );
178
+ const write: GitHubCheckRunWriteInput = {
179
+ name: input.commitStatus.context,
180
+ ...checkRunPhaseFields(input.phase),
181
+ output: { title: description, summary: description },
182
+ };
183
+ const resource = {
184
+ api: input.options.api,
185
+ credentials: input.options.credentials,
186
+ installationId: state.installationId ?? undefined,
187
+ owner: state.owner,
188
+ repo: state.repo,
189
+ };
190
+ const reuseId =
191
+ state.progressCheckRunId !== null && state.progressCheckRunSha === sha
192
+ ? state.progressCheckRunId
193
+ : null;
194
+ // Completed runs cannot go back to in_progress. GitHub often 200s and
195
+ // leaves the run completed (sometimes 422). Create a new run so a later
196
+ // turn on the same SHA shows pending again.
197
+ const completedBlocksReuse =
198
+ write.status === "in_progress" && state.progressCheckRunCompleted === true;
174
199
  try {
175
- await createGitHubCommitStatus({
176
- api: input.options.api,
177
- credentials: input.options.credentials,
178
- installationId: state.installationId ?? undefined,
179
- owner: state.owner,
180
- repo: state.repo,
181
- sha: state.headSha,
182
- state: input.phase,
183
- context: input.commitStatus.context,
184
- description,
200
+ if (reuseId !== null && !completedBlocksReuse) {
201
+ try {
202
+ await updateGitHubCheckRun({
203
+ ...resource,
204
+ ...write,
205
+ checkRunId: reuseId,
206
+ });
207
+ state.progressCheckRunCompleted = write.status === "completed";
208
+ return;
209
+ } catch (error) {
210
+ if (!shouldRecreateCheckRun(error, write.status)) {
211
+ return;
212
+ }
213
+ state.progressCheckRunId = null;
214
+ state.progressCheckRunSha = null;
215
+ state.progressCheckRunCompleted = false;
216
+ }
217
+ }
218
+ const created = await createGitHubCheckRun({
219
+ ...resource,
220
+ ...write,
221
+ sha,
185
222
  });
223
+ state.progressCheckRunId = created.id;
224
+ state.progressCheckRunSha = sha;
225
+ state.progressCheckRunCompleted = write.status === "completed";
186
226
  } catch {
187
227
  // Soft-fail: missing token / permission is fine.
188
228
  }
189
229
  }
190
230
 
231
+ function shouldRecreateCheckRun(
232
+ error: unknown,
233
+ status: GitHubCheckRunWriteInput["status"]
234
+ ): boolean {
235
+ if (!(error instanceof GitHubApiError)) {
236
+ return false;
237
+ }
238
+ if (error.status === 404) {
239
+ return true;
240
+ }
241
+ return error.status === 422 && status === "in_progress";
242
+ }
243
+
244
+ function checkRunPhaseFields(
245
+ phase: GitHubProgressPhase
246
+ ): Pick<GitHubCheckRunWriteInput, "conclusion" | "status"> {
247
+ if (phase === "pending") {
248
+ return { status: "in_progress" };
249
+ }
250
+ return {
251
+ status: "completed",
252
+ conclusion: phase === "success" ? "success" : "failure",
253
+ };
254
+ }
255
+
191
256
  async function upsertBanner(input: {
192
257
  phase: GitHubProgressPhase;
193
258
  channel: { state: GitHubChannelState };
@@ -13,6 +13,17 @@ export interface GitHubChannelState extends GitHubBindingState {
13
13
  headRef: string | null;
14
14
  /** Sticky progress banner comment id (issue/PR timeline). */
15
15
  progressBannerCommentId: number | null;
16
+ /** Latest Checks API run id for `progress.commitStatus`. */
17
+ progressCheckRunId: number | null;
18
+ /** Head SHA the latest check run was created against. */
19
+ progressCheckRunSha: string | null;
20
+ /**
21
+ * Whether that run is already `completed`. `null` means unknown (wake
22
+ * patches omit this so merge keeps the persisted value). GitHub rejects
23
+ * PATCH back to `in_progress`, so a later pending turn on the same SHA
24
+ * must create a new run.
25
+ */
26
+ progressCheckRunCompleted: boolean | null;
16
27
  }
17
28
 
18
29
  export function emptyGitHubChannelState(
@@ -35,6 +46,9 @@ export function emptyGitHubChannelState(
35
46
  baseRef: null,
36
47
  headRef: null,
37
48
  progressBannerCommentId: null,
49
+ progressCheckRunId: null,
50
+ progressCheckRunSha: null,
51
+ progressCheckRunCompleted: null,
38
52
  ...overrides,
39
53
  };
40
54
  }
@@ -171,6 +171,8 @@ export type GitHubPullRequestPayload = {
171
171
  type?: string;
172
172
  } | null;
173
173
  installation?: { id?: number } | null;
174
+ /** Present on `labeled` / `unlabeled` pull_request deliveries. */
175
+ label?: { name: string } | null;
174
176
  };
175
177
 
176
178
  /** Normalized `pull_request` event handed to {@link GitHubPullRequestHook}. */
@@ -188,6 +190,8 @@ export interface GitHubPullRequestEvent {
188
190
  /** True when the PR was merged (from the webhook payload). */
189
191
  merged: boolean;
190
192
  authorLogin?: string;
193
+ /** Label added/removed on `labeled` / `unlabeled` actions. */
194
+ label?: { name: string };
191
195
  }
192
196
 
193
197
  /** Normalized `issues` event. */
@@ -448,7 +452,7 @@ export interface GitHubChannelOptions {
448
452
  * Token capability tier:
449
453
  * - `"read"` — PR/issue/status read
450
454
  * - `"pr-write"` (default) — comments / PR+issue write, no `contents:write`
451
- * - `"contents-write"` — push code and create commit statuses (host-held)
455
+ * - `"contents-write"` — push code and post merge-box check runs (host-held)
452
456
  */
453
457
  permissions?: "read" | "pr-write" | "contents-write";
454
458
  };
@@ -500,12 +504,12 @@ export interface GitHubChannelOptions {
500
504
  onEvent?: GitHubEventHook;
501
505
  /**
502
506
  * Override / extend default stream-event handlers (eyes reaction, reply
503
- * posting, opt-in commit status / banner). Handlers you provide replace
507
+ * posting, opt-in check run / banner). Handlers you provide replace
504
508
  * the matching defaults.
505
509
  */
506
510
  events?: ChannelEventHandlers<GitHubChannelState>;
507
511
  /**
508
- * Acknowledgement progress surfaces. Reactions default on; commit status
512
+ * Acknowledgement progress surfaces. Reactions default on; merge-box check
509
513
  * and sticky banner are opt-in via `commitStatus` / `banner`.
510
514
  */
511
515
  progress?: GitHubProgressConfig;
package/src/channels.ts CHANGED
@@ -383,3 +383,41 @@ export function allowAll(): AuthPolicy {
383
383
  principalType: "user",
384
384
  });
385
385
  }
386
+
387
+ /**
388
+ * `Symbol.for` so the marker survives duplicated copies of the framework in
389
+ * one process (e.g. a monorepo agent resolving its own `@cursor/july`).
390
+ */
391
+ const PUBLIC_ENDPOINT_MARKER = Symbol.for("agent-serve.public-endpoint");
392
+
393
+ /**
394
+ * Like {@link allowAll}, but ALSO declares the channel publicly reachable on
395
+ * Cursor's managed hosting: `deploy` collects every channel whose auth chain
396
+ * contains this policy and the platform serves that channel's routes
397
+ * (`/v1/channels/<id>/…`) through the stable alias WITHOUT
398
+ * `X-Agent-Alias-Token`.
399
+ *
400
+ * Use it for webhook senders that cannot attach custom headers (Linear,
401
+ * Stripe, …) — **the handler must authenticate deliveries itself**, e.g. by
402
+ * verifying the provider's HMAC signature over the raw body. Everything
403
+ * else (sessions, MCP, playground) stays behind platform auth.
404
+ *
405
+ * Locally this admits every caller, exactly like `allowAll()`.
406
+ */
407
+ export function publicEndpoint(): AuthPolicy {
408
+ const policy: AuthPolicy = () => ({
409
+ authenticator: "public-endpoint",
410
+ principalId: "anonymous",
411
+ principalType: "user",
412
+ });
413
+ return Object.assign(policy, { [PUBLIC_ENDPOINT_MARKER]: true as const });
414
+ }
415
+
416
+ /** True when a policy was created by {@link publicEndpoint}. */
417
+ export function isPublicEndpointPolicy(policy: AuthPolicy): boolean {
418
+ return (
419
+ (policy as { [PUBLIC_ENDPOINT_MARKER]?: unknown })[
420
+ PUBLIC_ENDPOINT_MARKER
421
+ ] === true
422
+ );
423
+ }
package/src/index.ts CHANGED
@@ -48,6 +48,7 @@ export {
48
48
  PATCH,
49
49
  POST,
50
50
  PUT,
51
+ publicEndpoint,
51
52
  } from "./channels.js";
52
53
  export { defineConnection } from "./connections.js";
53
54
  export { defineHook } from "./hooks.js";
@@ -93,6 +94,7 @@ export {
93
94
  } from "./internal/session-engine.js";
94
95
  export { createMemoryKv, memoryCasTable } from "./kv/memory-kv.js";
95
96
  export {
97
+ createLedgerJanitor,
96
98
  LedgerCasExhaustedError,
97
99
  LedgerFencedError,
98
100
  SessionLedger,
@@ -20,6 +20,16 @@ export function isSafeSessionId(sessionId: string): boolean {
20
20
  );
21
21
  }
22
22
 
23
+ /** Thrown when a send addresses a session owned by a different principal. */
24
+ export class SessionAuthError extends Error {
25
+ constructor(sessionId: string) {
26
+ super(
27
+ `Session ${sessionId} belongs to a different principal; pass the original caller auth on follow-up`
28
+ );
29
+ this.name = "SessionAuthError";
30
+ }
31
+ }
32
+
23
33
  /** True when both sides identify the same principal (or both are null). */
24
34
  export function samePrincipal(
25
35
  a: AuthContext | null,
@@ -23,7 +23,7 @@ import { lookupSessionContinuation, runChat } from "./chat-client.js";
23
23
  import { cmdLogin } from "./cli-cursor.js";
24
24
  import { resolveDeployAuth } from "./cli-deploy.js";
25
25
  import { resolveApiKey } from "./cursor/credentials.js";
26
- import { CLI_COMMAND_NAME } from "./distribution.js";
26
+ import { CLI_COMMAND_NAME, PACKAGE_NAME } from "./distribution.js";
27
27
  import {
28
28
  discoverEvals,
29
29
  type EvalRunResult,
@@ -374,7 +374,7 @@ export async function cmdInit(options: AxCliOptions): Promise<number> {
374
374
  const cliOnPath = linked.ok;
375
375
  if (!cliOnPath) {
376
376
  process.stderr.write(
377
- `\nCould not put ${CLI_COMMAND_NAME} on PATH (${linked.stderr.trim() || `exit ${linked.exitCode}`}). Next steps include a PATH export for node_modules/.bin.\n\n`
377
+ `\nCould not put ${CLI_COMMAND_NAME} on PATH (${linked.stderr.trim() || `exit ${linked.exitCode}`}). Next steps use npx ${PACKAGE_NAME} instead.\n\n`
378
378
  );
379
379
  } else {
380
380
  process.stdout.write("\n");