@cursor/bdk 0.2.1 → 0.2.2

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 (176) hide show
  1. package/README.md +1 -6
  2. package/dist/artifacts.d.ts +5 -2
  3. package/dist/artifacts.d.ts.map +1 -1
  4. package/dist/artifacts.js +5 -2
  5. package/dist/channels/slack/manifest.d.ts.map +1 -1
  6. package/dist/channels/slack/manifest.js +3 -2
  7. package/dist/channels/slack/types.d.ts +6 -1
  8. package/dist/channels/slack/types.d.ts.map +1 -1
  9. package/dist/docs/404.html +2 -2
  10. package/dist/docs/assets/{app.JVQvbRO-.js → app.DtYVVVyK.js} +4 -4
  11. package/dist/docs/assets/chunks/@localSearchIndexroot.BgTA2tV1.js +1 -0
  12. package/dist/docs/assets/chunks/{VPLocalSearchBox.BkhY63uW.js → VPLocalSearchBox.D47xT7J4.js} +1 -1
  13. package/dist/docs/assets/chunks/{arc.Cc1unYd3.js → arc.CDJ6xzxR.js} +1 -1
  14. package/dist/docs/assets/chunks/{architectureDiagram-Q4EWVU46.CAY_TPVz.js → architectureDiagram-Q4EWVU46.l1Xnjd3N.js} +1 -1
  15. package/dist/docs/assets/chunks/{baseUniq.my2VD3Xm.js → baseUniq.x-finShZ.js} +1 -1
  16. package/dist/docs/assets/chunks/{blockDiagram-DXYQGD6D.CQXAOmqd.js → blockDiagram-DXYQGD6D.COC3qynH.js} +1 -1
  17. package/dist/docs/assets/chunks/{c4Diagram-AHTNJAMY.D0AsQFSX.js → c4Diagram-AHTNJAMY.DdQO850E.js} +1 -1
  18. package/dist/docs/assets/chunks/channel.B2nMEx82.js +1 -0
  19. package/dist/docs/assets/chunks/{chunk-4BX2VUAB.DZjhgNls.js → chunk-4BX2VUAB.Dpn8omLk.js} +1 -1
  20. package/dist/docs/assets/chunks/{chunk-4TB4RGXK.qdcp_qbo.js → chunk-4TB4RGXK.C0XWStUN.js} +1 -1
  21. package/dist/docs/assets/chunks/{chunk-55IACEB6.BHXoZhJ-.js → chunk-55IACEB6.DsKSZfIM.js} +1 -1
  22. package/dist/docs/assets/chunks/{chunk-EDXVE4YY.Cds_Vwj3.js → chunk-EDXVE4YY.dNU6JDu6.js} +1 -1
  23. package/dist/docs/assets/chunks/{chunk-FMBD7UC4.CSl0HknT.js → chunk-FMBD7UC4.DN41fxKE.js} +1 -1
  24. package/dist/docs/assets/chunks/{chunk-OYMX7WX6.DJMvAt5N.js → chunk-OYMX7WX6.COpKCZus.js} +1 -1
  25. package/dist/docs/assets/chunks/{chunk-QZHKN3VN.BYQzBK9D.js → chunk-QZHKN3VN.HDB-azx2.js} +1 -1
  26. package/dist/docs/assets/chunks/{chunk-YZCP3GAM.BAvw7N4o.js → chunk-YZCP3GAM.DEeX_a2j.js} +1 -1
  27. package/dist/docs/assets/chunks/classDiagram-6PBFFD2Q.D8iCH2ve.js +1 -0
  28. package/dist/docs/assets/chunks/classDiagram-v2-HSJHXN6E.D8iCH2ve.js +1 -0
  29. package/dist/docs/assets/chunks/clone.5XNLjby0.js +1 -0
  30. package/dist/docs/assets/chunks/{cose-bilkent-S5V4N54A.Buf3seya.js → cose-bilkent-S5V4N54A.C3yGL2A8.js} +1 -1
  31. package/dist/docs/assets/chunks/{dagre-KV5264BT.DMGGa3uA.js → dagre-KV5264BT.Bpl24en4.js} +1 -1
  32. package/dist/docs/assets/chunks/{diagram-5BDNPKRD.DDjnwRmY.js → diagram-5BDNPKRD.nFco6seU.js} +1 -1
  33. package/dist/docs/assets/chunks/{diagram-G4DWMVQ6.RwyrIr8Q.js → diagram-G4DWMVQ6.DXhA6DWR.js} +1 -1
  34. package/dist/docs/assets/chunks/{diagram-MMDJMWI5.DRvQV16Z.js → diagram-MMDJMWI5.8GoOYcwk.js} +1 -1
  35. package/dist/docs/assets/chunks/{diagram-TYMM5635.Dw8W9BTk.js → diagram-TYMM5635.DDBlqvlJ.js} +1 -1
  36. package/dist/docs/assets/chunks/{erDiagram-SMLLAGMA.DQT1Hnwf.js → erDiagram-SMLLAGMA.vl_N81P4.js} +1 -1
  37. package/dist/docs/assets/chunks/{flowDiagram-DWJPFMVM.6_Aky-ut.js → flowDiagram-DWJPFMVM.Cteg8g7I.js} +1 -1
  38. package/dist/docs/assets/chunks/{ganttDiagram-T4ZO3ILL.DHIwqnqN.js → ganttDiagram-T4ZO3ILL.D4aMhlMM.js} +1 -1
  39. package/dist/docs/assets/chunks/{gitGraphDiagram-UUTBAWPF.CGd_eJbn.js → gitGraphDiagram-UUTBAWPF.DF_J0D4q.js} +1 -1
  40. package/dist/docs/assets/chunks/{graph.ChMEylkh.js → graph.DJs0LDCf.js} +1 -1
  41. package/dist/docs/assets/chunks/{infoDiagram-42DDH7IO.Cd4gMyAz.js → infoDiagram-42DDH7IO.Bb-4RQwg.js} +1 -1
  42. package/dist/docs/assets/chunks/{ishikawaDiagram-UXIWVN3A.D1uw7MXq.js → ishikawaDiagram-UXIWVN3A.DEtQRSbc.js} +1 -1
  43. package/dist/docs/assets/chunks/{journeyDiagram-VCZTEJTY.AWLXtelc.js → journeyDiagram-VCZTEJTY.DhQQ75FA.js} +1 -1
  44. package/dist/docs/assets/chunks/{kanban-definition-6JOO6SKY.CagPhAn5.js → kanban-definition-6JOO6SKY.C4gEw3Rk.js} +1 -1
  45. package/dist/docs/assets/chunks/{layout.ClTBBkE_.js → layout.DT-PGlXa.js} +1 -1
  46. package/dist/docs/assets/chunks/{linear.SwS2rOLA.js → linear.BMMe__JW.js} +1 -1
  47. package/dist/docs/assets/chunks/{min.By_x-Btb.js → min.XyaU9K9Q.js} +1 -1
  48. package/dist/docs/assets/chunks/{mindmap-definition-QFDTVHPH.By_RAMaP.js → mindmap-definition-QFDTVHPH.CbBM6xZ-.js} +1 -1
  49. package/dist/docs/assets/chunks/{pieDiagram-DEJITSTG.DfAl9sy4.js → pieDiagram-DEJITSTG.DFJoIVou.js} +1 -1
  50. package/dist/docs/assets/chunks/{quadrantDiagram-34T5L4WZ.CNuqFRBj.js → quadrantDiagram-34T5L4WZ.CQjkDYqb.js} +1 -1
  51. package/dist/docs/assets/chunks/{requirementDiagram-MS252O5E.DJ5CmTCl.js → requirementDiagram-MS252O5E.D-mK-JQu.js} +1 -1
  52. package/dist/docs/assets/chunks/{sankeyDiagram-XADWPNL6.D-T_AKJj.js → sankeyDiagram-XADWPNL6.BStQInpd.js} +1 -1
  53. package/dist/docs/assets/chunks/{sequenceDiagram-FGHM5R23.BRn9WcrR.js → sequenceDiagram-FGHM5R23.CA_eooPJ.js} +1 -1
  54. package/dist/docs/assets/chunks/{stateDiagram-FHFEXIEX.F12urmm7.js → stateDiagram-FHFEXIEX.BXl-b7ad.js} +1 -1
  55. package/dist/docs/assets/chunks/stateDiagram-v2-QKLJ7IA2.yGM2uLZQ.js +1 -0
  56. package/dist/docs/assets/chunks/{theme.zK3OJa_C.js → theme.q37tOs4x.js} +2 -2
  57. package/dist/docs/assets/chunks/{timeline-definition-GMOUNBTQ.B7GiQBeC.js → timeline-definition-GMOUNBTQ.vjuaGOSe.js} +1 -1
  58. package/dist/docs/assets/chunks/{vennDiagram-DHZGUBPP.D0niwuY_.js → vennDiagram-DHZGUBPP.BxduJl-F.js} +1 -1
  59. package/dist/docs/assets/chunks/wardley-RL74JXVD.DKfyIejL.js +162 -0
  60. package/dist/docs/assets/chunks/{wardleyDiagram-NUSXRM2D.DKaqpT7K.js → wardleyDiagram-NUSXRM2D.D1z3ds0D.js} +1 -1
  61. package/dist/docs/assets/chunks/{xychartDiagram-5P7HB3ND.e8BPicch.js → xychartDiagram-5P7HB3ND.CRU5Ti9b.js} +1 -1
  62. package/dist/docs/assets/{reference_artifacts.md.IFFC5Zln.js → reference_artifacts.md.CFMNPreZ.js} +1 -1
  63. package/dist/docs/assets/{reference_hooks.md.Dg3dEugd.js → reference_hooks.md.DbthhtIR.js} +1 -1
  64. package/dist/docs/assets/{reference_sessions.md.BtpTjZmh.js → reference_sessions.md.DeRUItHD.js} +1 -1
  65. package/dist/docs/assets/{reference_skills.md.Cy00QMKS.js → reference_skills.md.CRFTxt9M.js} +3 -3
  66. package/dist/docs/assets/{reference_skills.md.Cy00QMKS.lean.js → reference_skills.md.CRFTxt9M.lean.js} +1 -1
  67. package/dist/docs/building-with-agents.html +35 -35
  68. package/dist/docs/deployment.html +35 -35
  69. package/dist/docs/evals.html +35 -35
  70. package/dist/docs/guides/agent-to-agent.html +35 -35
  71. package/dist/docs/guides/bitbucket.html +35 -35
  72. package/dist/docs/guides/cloud-agents.html +35 -35
  73. package/dist/docs/guides/convert-automation.html +35 -35
  74. package/dist/docs/guides/github.html +35 -35
  75. package/dist/docs/guides/gitlab.html +35 -35
  76. package/dist/docs/guides/grokbot-agents.html +35 -35
  77. package/dist/docs/guides/hooks.html +35 -35
  78. package/dist/docs/guides/improve.html +35 -35
  79. package/dist/docs/guides/jev.html +35 -35
  80. package/dist/docs/guides/mcp-oauth.html +35 -35
  81. package/dist/docs/guides/opentelemetry.html +35 -35
  82. package/dist/docs/guides/slack.html +35 -35
  83. package/dist/docs/guides/webhooks.html +35 -35
  84. package/dist/docs/hashmap.json +1 -1
  85. package/dist/docs/hillclimbing.html +35 -35
  86. package/dist/docs/index.html +35 -35
  87. package/dist/docs/llms-full.txt +8 -6
  88. package/dist/docs/quickstart.html +35 -35
  89. package/dist/docs/reference/agent-config.html +35 -35
  90. package/dist/docs/reference/artifacts.html +37 -37
  91. package/dist/docs/reference/artifacts.md +1 -1
  92. package/dist/docs/reference/channels.html +35 -35
  93. package/dist/docs/reference/cli.html +35 -35
  94. package/dist/docs/reference/connections.html +35 -35
  95. package/dist/docs/reference/evals.html +35 -35
  96. package/dist/docs/reference/extensions.html +35 -35
  97. package/dist/docs/reference/hooks.html +37 -37
  98. package/dist/docs/reference/hooks.md +3 -2
  99. package/dist/docs/reference/http-api.html +35 -35
  100. package/dist/docs/reference/instructions.html +35 -35
  101. package/dist/docs/reference/playground.html +35 -35
  102. package/dist/docs/reference/project-layout.html +35 -35
  103. package/dist/docs/reference/prompt.html +35 -35
  104. package/dist/docs/reference/schedules.html +35 -35
  105. package/dist/docs/reference/sessions.html +36 -36
  106. package/dist/docs/reference/sessions.md +4 -3
  107. package/dist/docs/reference/skills.html +38 -38
  108. package/dist/docs/reference/subagents.html +35 -35
  109. package/dist/docs/reference/tools.html +35 -35
  110. package/dist/docs/templates/agentic-owners.html +35 -35
  111. package/dist/docs/templates/security-reviewer.html +35 -35
  112. package/dist/docs/templates/thermo-quality-review.html +35 -35
  113. package/dist/docs/templates/thermo-review.html +35 -35
  114. package/dist/docs/templates/triage.html +35 -35
  115. package/dist/docs/troubleshooting.html +35 -35
  116. package/dist/extensions/cursor-cloud-agents/extension.d.ts +16 -4
  117. package/dist/extensions/cursor-cloud-agents/extension.d.ts.map +1 -1
  118. package/dist/extensions/cursor-cloud-agents/extension.js +7 -3
  119. package/dist/extensions/cursor-cloud-agents/lib/channels.d.ts +3 -0
  120. package/dist/extensions/cursor-cloud-agents/lib/channels.d.ts.map +1 -0
  121. package/dist/extensions/cursor-cloud-agents/lib/channels.js +7 -0
  122. package/dist/extensions/cursor-cloud-agents/lib/repos.d.ts +4 -2
  123. package/dist/extensions/cursor-cloud-agents/lib/repos.d.ts.map +1 -1
  124. package/dist/extensions/cursor-cloud-agents/lib/repos.js +21 -12
  125. package/dist/extensions/cursor-cloud-agents/tools/cancel.d.ts.map +1 -1
  126. package/dist/extensions/cursor-cloud-agents/tools/cancel.js +2 -0
  127. package/dist/extensions/cursor-cloud-agents/tools/launch.d.ts.map +1 -1
  128. package/dist/extensions/cursor-cloud-agents/tools/launch.js +3 -1
  129. package/dist/extensions/cursor-cloud-agents/tools/reply.d.ts.map +1 -1
  130. package/dist/extensions/cursor-cloud-agents/tools/reply.js +2 -0
  131. package/dist/files-backends/cursor-hosted.js +4 -4
  132. package/dist/internal/artifacts-store.d.ts +12 -2
  133. package/dist/internal/artifacts-store.d.ts.map +1 -1
  134. package/dist/internal/artifacts-store.js +8 -4
  135. package/dist/internal/event-mapper.d.ts +46 -4
  136. package/dist/internal/event-mapper.d.ts.map +1 -1
  137. package/dist/internal/event-mapper.js +316 -25
  138. package/dist/internal/hosted-delivery.js +1 -1
  139. package/dist/internal/session-engine.d.ts +1 -1
  140. package/dist/internal/session-engine.d.ts.map +1 -1
  141. package/dist/internal/session-engine.js +24 -3
  142. package/dist/playground/assets/index-6BbTPnaT.css +1 -0
  143. package/dist/playground/assets/{index-FFdUqTt_.js → index-CI7FAmOP.js} +43 -43
  144. package/dist/playground/index.html +2 -2
  145. package/dist/types.d.ts +39 -3
  146. package/dist/types.d.ts.map +1 -1
  147. package/docs/reference/artifacts.md +1 -1
  148. package/docs/reference/hooks.md +3 -2
  149. package/docs/reference/sessions.md +4 -3
  150. package/package.json +2 -2
  151. package/src/artifacts.ts +5 -2
  152. package/src/channels/slack/manifest.ts +2 -1
  153. package/src/channels/slack/types.ts +6 -1
  154. package/src/extensions/cursor-cloud-agents/extension.ts +23 -6
  155. package/src/extensions/cursor-cloud-agents/lib/channels.ts +11 -0
  156. package/src/extensions/cursor-cloud-agents/lib/repos.ts +19 -7
  157. package/src/extensions/cursor-cloud-agents/tools/cancel.ts +2 -0
  158. package/src/extensions/cursor-cloud-agents/tools/launch.ts +3 -1
  159. package/src/extensions/cursor-cloud-agents/tools/reply.ts +2 -0
  160. package/src/files-backends/cursor-hosted.ts +4 -4
  161. package/src/internal/artifacts-store.ts +14 -3
  162. package/src/internal/event-mapper.ts +367 -36
  163. package/src/internal/hosted-delivery.ts +1 -1
  164. package/src/internal/session-engine.ts +24 -8
  165. package/src/types.ts +45 -4
  166. package/dist/docs/assets/chunks/@localSearchIndexroot.Caw9W1pM.js +0 -1
  167. package/dist/docs/assets/chunks/channel.Sapo9HJZ.js +0 -1
  168. package/dist/docs/assets/chunks/classDiagram-6PBFFD2Q.NPiy3Hz7.js +0 -1
  169. package/dist/docs/assets/chunks/classDiagram-v2-HSJHXN6E.NPiy3Hz7.js +0 -1
  170. package/dist/docs/assets/chunks/clone.zl2qolPE.js +0 -1
  171. package/dist/docs/assets/chunks/stateDiagram-v2-QKLJ7IA2._auCa3fR.js +0 -1
  172. package/dist/docs/assets/chunks/wardley-RL74JXVD.1zhHNRsx.js +0 -162
  173. package/dist/playground/assets/index-BMV_IcEE.css +0 -1
  174. /package/dist/docs/assets/{reference_artifacts.md.IFFC5Zln.lean.js → reference_artifacts.md.CFMNPreZ.lean.js} +0 -0
  175. /package/dist/docs/assets/{reference_hooks.md.Dg3dEugd.lean.js → reference_hooks.md.DbthhtIR.lean.js} +0 -0
  176. /package/dist/docs/assets/{reference_sessions.md.BtpTjZmh.lean.js → reference_sessions.md.DeRUItHD.lean.js} +0 -0
@@ -1,6 +1,7 @@
1
1
  import { defineTool } from "../../../tools.js";
2
2
  import type { ToolDefinition } from "../../../types.js";
3
3
  import { cloudAgentsBackend } from "../lib/backend.js";
4
+ import { assertWriteChannel } from "../lib/channels.js";
4
5
  import { assertOwned } from "../lib/fleet.js";
5
6
  import { agentRef } from "../lib/inputs.js";
6
7
 
@@ -10,6 +11,7 @@ const cancel: ToolDefinition = defineTool({
10
11
  effect: "write",
11
12
  inputSchema: agentRef,
12
13
  async execute(input, ctx) {
14
+ assertWriteChannel(ctx);
13
15
  await assertOwned(ctx, input.agent_id);
14
16
  await cloudAgentsBackend().pause(input.agent_id);
15
17
  return { agentId: input.agent_id, cancelled: true };
@@ -3,6 +3,7 @@ import { defineTool } from "../../../tools.js";
3
3
  import type { ToolDefinition } from "../../../types.js";
4
4
  import extension from "../extension.js";
5
5
  import { cloudAgentsBackend } from "../lib/backend.js";
6
+ import { assertWriteChannel } from "../lib/channels.js";
6
7
  import { recordLaunch } from "../lib/fleet.js";
7
8
  import { launchRepos } from "../lib/repos.js";
8
9
 
@@ -21,7 +22,7 @@ const launch: ToolDefinition = defineTool({
21
22
  .url()
22
23
  .optional()
23
24
  .describe(
24
- "GitHub repository URL, one of the configured repositories. Defaults to the configured repository.",
25
+ "Repository clone URL (GitHub or Origin), one of the configured repositories. Defaults to the configured repository when there is exactly one.",
25
26
  ),
26
27
  starting_ref: z
27
28
  .string()
@@ -39,6 +40,7 @@ const launch: ToolDefinition = defineTool({
39
40
  .describe("Display name; derived from the prompt when omitted."),
40
41
  }),
41
42
  async execute(input, ctx) {
43
+ assertWriteChannel(ctx);
42
44
  const { cloud, model } = extension.config;
43
45
  const launched = await cloudAgentsBackend().launch({
44
46
  prompt: input.prompt,
@@ -3,6 +3,7 @@ import { defineTool } from "../../../tools.js";
3
3
  import type { ToolDefinition } from "../../../types.js";
4
4
  import extension from "../extension.js";
5
5
  import { cloudAgentsBackend } from "../lib/backend.js";
6
+ import { assertWriteChannel } from "../lib/channels.js";
6
7
  import { assertOwned } from "../lib/fleet.js";
7
8
  import { agentRef } from "../lib/inputs.js";
8
9
 
@@ -21,6 +22,7 @@ const reply: ToolDefinition = defineTool({
21
22
  ),
22
23
  }),
23
24
  async execute(input, ctx) {
25
+ assertWriteChannel(ctx);
24
26
  await assertOwned(ctx, input.agent_id);
25
27
  const { runId } = await cloudAgentsBackend().followUp(
26
28
  input.agent_id,
@@ -925,10 +925,10 @@ export function cursorHostedFiles(options: CursorHostedFilesOptions = {}): Hoste
925
925
  if (files.wire !== "flush") {
926
926
  return false;
927
927
  }
928
- // The flush wire's deletes are conditional server-side: when this
929
- // process has no observed etag, the server resolves the current one —
930
- // the lookup that costs a stat round trip on `/v0` — and a key that
931
- // is already absent settles as `already_deleted`.
928
+ // With an observed etag the flush delete is If-Match on that version.
929
+ // Without one, the server tombstones whatever live object occupies
930
+ // the key (no directory walk here) and an absent key settles as
931
+ // `already_deleted`.
932
932
  const cached = observedEtags.get(key);
933
933
  const response = await flushPost<FlushWireResponse>(options, "/flush", {
934
934
  store: files.store,
@@ -107,6 +107,12 @@ export interface CreateArtifactsStoreOptions {
107
107
  config?: ArtifactsConfig;
108
108
  /** Explicit sink override (tests). Omitted → hosted store or local disk. */
109
109
  sink?: FileSink;
110
+ /**
111
+ * Whether `tag` enforces {@link ArtifactsConfig.max} by listing and
112
+ * deleting. Default true. Hosted architecture v2 passes false: the
113
+ * installation store is allowed to grow, and a tag must not list the prefix.
114
+ */
115
+ enforceCap?: () => boolean;
110
116
  }
111
117
 
112
118
  /** Kind used by freeform tags when no kinds are declared. */
@@ -114,8 +120,11 @@ export const ARTIFACT_DEFAULT_KIND = "artifact";
114
120
 
115
121
  /**
116
122
  * Upsert + list semantics over one {@link ArtifactRowStore}: kind/schema
117
- * validation, deterministic ids for keyed tags, and `max` eviction. The
118
- * engine wraps this into the per-context {@link ArtifactsApi} facades and
123
+ * validation, deterministic ids for keyed tags, and `max` eviction.
124
+ * Architecture v1 and self-hosted v2 evict. Hosted architecture v2 sets
125
+ * {@link CreateArtifactsStoreOptions.enforceCap} false and does not list
126
+ * or delete on tag. The engine wraps this into the per-context
127
+ * {@link ArtifactsApi} facades and
119
128
  * owns the `artifact.tagged` event append.
120
129
  */
121
130
  export class ArtifactsStore {
@@ -123,6 +132,7 @@ export class ArtifactsStore {
123
132
  private readonly content: ArtifactContentStore;
124
133
  private readonly config: ArtifactsConfig | undefined;
125
134
  private readonly max: number;
135
+ private readonly enforceCap: () => boolean;
126
136
  /** Serializes tags so read-modify-write upserts and eviction don't race. */
127
137
  private tagChain: Promise<unknown> = Promise.resolve();
128
138
 
@@ -137,6 +147,7 @@ export class ArtifactsStore {
137
147
  }
138
148
  this.config = options.config;
139
149
  this.max = resolveArtifactsMax(options.config);
150
+ this.enforceCap = options.enforceCap ?? (() => true);
140
151
  }
141
152
 
142
153
  tag(input: ArtifactTagInput): Promise<ArtifactRecord> {
@@ -181,7 +192,7 @@ export class ArtifactsStore {
181
192
  }
182
193
  }
183
194
  await this.rows.put(record);
184
- if (existing === undefined) {
195
+ if (existing === undefined && this.enforceCap()) {
185
196
  await this.evictPastCap();
186
197
  }
187
198
  return record;
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  import type { InteractionUpdate as SdkInteractionUpdate, ToolCall } from "@cursor/sdk";
8
- import type { SessionEventPayload, TurnUsage } from "../types.js";
8
+ import type { ActionCall, ActionResult, SessionEventPayload, TurnUsage } from "../types.js";
9
9
 
10
10
  const CUSTOM_TOOLS_PROVIDER = "custom-user-tools";
11
11
  /** Authored server tools on cloud turns; keep in sync with `SERVER_TOOLS_MCP_SERVER_NAME`. */
@@ -39,7 +39,8 @@ type NestedTaskUpdate =
39
39
  modelCallId: string;
40
40
  }
41
41
  | { type: "step-started"; stepId: number }
42
- | { type: "step-completed"; stepId: number; stepDurationMs: number };
42
+ | { type: "step-completed"; stepId: number; stepDurationMs: number }
43
+ | { type: "tool-requests-listed"; callCount: number };
43
44
 
44
45
  type ToolCallDeltaUpdate = {
45
46
  type: "tool-call-delta";
@@ -48,8 +49,13 @@ type ToolCallDeltaUpdate = {
48
49
  taskUpdate: NestedTaskUpdate;
49
50
  };
50
51
 
51
- /** SDK updates plus nested task deltas once the SDK exposes them. */
52
- type InteractionUpdate = SdkInteractionUpdate | ToolCallDeltaUpdate;
52
+ type ToolRequestsListedUpdate = {
53
+ type: "tool-requests-listed";
54
+ callCount: number;
55
+ };
56
+
57
+ /** SDK updates plus list-closed / nested task deltas ahead of `@cursor/sdk`. */
58
+ type InteractionUpdate = SdkInteractionUpdate | ToolCallDeltaUpdate | ToolRequestsListedUpdate;
53
59
 
54
60
  function asToolCallDelta(update: InteractionUpdate): ToolCallDeltaUpdate | undefined {
55
61
  if ((update as { type: string }).type !== "tool-call-delta") {
@@ -58,6 +64,12 @@ function asToolCallDelta(update: InteractionUpdate): ToolCallDeltaUpdate | undef
58
64
  return update as ToolCallDeltaUpdate;
59
65
  }
60
66
 
67
+ function modelCallIdOf(update: { modelCallId?: unknown }): string | undefined {
68
+ return typeof update.modelCallId === "string" && update.modelCallId !== ""
69
+ ? update.modelCallId
70
+ : undefined;
71
+ }
72
+
61
73
  export interface ToolCallDescription {
62
74
  toolName: string;
63
75
  args?: unknown;
@@ -74,6 +86,17 @@ interface NestedAccumulators {
74
86
  reasoning: string;
75
87
  }
76
88
 
89
+ type ToolWave = {
90
+ key: string;
91
+ modelCallId?: string;
92
+ parentCallId?: string;
93
+ calls: ActionCall[];
94
+ results: ActionResult[];
95
+ expectedCalls?: number;
96
+ requestedEmitted: boolean;
97
+ completedEmitted: boolean;
98
+ };
99
+
77
100
  export class TurnEventMapper {
78
101
  private textAccumulator = "";
79
102
  private reasoningAccumulator = "";
@@ -82,6 +105,17 @@ export class TurnEventMapper {
82
105
  private summaryTitle: string | undefined;
83
106
  /** Per-parent-call accumulators for nested subagent streams. */
84
107
  private readonly nested = new Map<string, NestedAccumulators>();
108
+ /** One wave per model step (or per call when the step id is missing). */
109
+ private readonly waves = new Map<string, ToolWave>();
110
+ /**
111
+ * Keys of waves that already emitted `actions.completed`, plus each
112
+ * finished call's singleton key. A late duplicate cannot reopen the
113
+ * batch or emit a second pair for the same callId. A new late callId
114
+ * still gets its own singleton.
115
+ */
116
+ private readonly sealedWaveKeys = new Set<string>();
117
+ /** Listed-call count from `tool-requests-listed`, keyed by parentCallId. */
118
+ private readonly listedCounts = new Map<string, number>();
85
119
 
86
120
  /**
87
121
  * @param isStubbed Reports whether a finished tool call was answered by the
@@ -173,17 +207,25 @@ export class TurnEventMapper {
173
207
  }
174
208
  case "tool-call-started": {
175
209
  this.flushAssistantText("tool_call");
176
- this.emitToolStarted(update.callId, update.toolCall);
210
+ this.noteToolStarted(update.callId, update.toolCall, {
211
+ modelCallId: modelCallIdOf(update),
212
+ });
177
213
  return;
178
214
  }
179
215
  case "tool-call-completed": {
180
- this.emitToolCompleted(update.callId, update.toolCall);
216
+ this.noteToolCompleted(update.callId, update.toolCall, {
217
+ modelCallId: modelCallIdOf(update),
218
+ });
181
219
  return;
182
220
  }
183
221
  case "step-started": {
184
222
  this.emit({ type: "step.started", data: { stepId: update.stepId } });
185
223
  return;
186
224
  }
225
+ case "tool-requests-listed": {
226
+ this.noteRequestsListed(undefined, update.callCount);
227
+ return;
228
+ }
187
229
  case "step-completed": {
188
230
  this.emit({
189
231
  type: "step.completed",
@@ -223,6 +265,8 @@ export class TurnEventMapper {
223
265
 
224
266
  /** Close out the turn: finalize any open assistant text block. */
225
267
  finish(): void {
268
+ this.sealOpenRequests();
269
+ this.flushOpenResults();
226
270
  this.flushAssistantText("stop");
227
271
  for (const parentCallId of this.nested.keys()) {
228
272
  this.flushNestedAssistantText(parentCallId, "stop");
@@ -273,15 +317,26 @@ export class TurnEventMapper {
273
317
  }
274
318
  case "tool-call-started": {
275
319
  this.flushNestedAssistantText(parentCallId, "tool_call");
276
- this.emitToolStarted(update.callId, update.toolCall, parentCallId);
320
+ this.noteToolStarted(update.callId, update.toolCall, {
321
+ parentCallId,
322
+ modelCallId: modelCallIdOf(update),
323
+ });
277
324
  return;
278
325
  }
279
326
  case "tool-call-completed": {
280
- this.emitToolCompleted(update.callId, update.toolCall, parentCallId);
327
+ this.noteToolCompleted(update.callId, update.toolCall, {
328
+ parentCallId,
329
+ modelCallId: modelCallIdOf(update),
330
+ });
281
331
  return;
282
332
  }
283
333
  case "partial-tool-call":
334
+ return;
284
335
  case "step-started":
336
+ return;
337
+ case "tool-requests-listed":
338
+ this.noteRequestsListed(parentCallId, update.callCount);
339
+ return;
285
340
  case "step-completed":
286
341
  return;
287
342
  default: {
@@ -290,22 +345,36 @@ export class TurnEventMapper {
290
345
  }
291
346
  }
292
347
 
293
- private emitToolStarted(callId: string, toolCall: ToolCall, parentCallId?: string): void {
348
+ private noteToolStarted(
349
+ callId: string,
350
+ toolCall: ToolCall,
351
+ ids?: { parentCallId?: string; modelCallId?: string },
352
+ ): void {
353
+ const parentCallId = ids?.parentCallId;
354
+ const modelCallId = ids?.modelCallId;
294
355
  const description = describeToolCall(toolCall);
295
- this.emit({
296
- type: "actions.requested",
297
- data: {
298
- calls: [
299
- {
300
- callId,
301
- toolName: description.toolName,
302
- args: description.args,
303
- parentCallId,
304
- },
305
- ],
356
+ const call: ActionCall = {
357
+ callId,
358
+ toolName: description.toolName,
359
+ args: description.args,
360
+ parentCallId,
361
+ modelCallId,
362
+ };
363
+ if (modelCallId !== undefined) {
364
+ this.sealOpenRequests({
306
365
  parentCallId,
307
- },
308
- });
366
+ exceptModelCallId: modelCallId,
367
+ });
368
+ }
369
+ const wave = this.waveForStart({ parentCallId, modelCallId, callId });
370
+ if (wave === undefined) {
371
+ return;
372
+ }
373
+ if (wave.calls.every(existing => existing.callId !== callId)) {
374
+ wave.calls.push(call);
375
+ }
376
+ this.adoptSingletonResults(wave, parentCallId, callId);
377
+ this.maybeSealListedWave(wave);
309
378
  // Subagent bookends only fire for the top-level task tool, not nested
310
379
  // tools inside a subagent (nested subagents aren't supported).
311
380
  if (description.subagent !== undefined && parentCallId === undefined) {
@@ -318,22 +387,44 @@ export class TurnEventMapper {
318
387
  },
319
388
  });
320
389
  }
390
+ if (wave.modelCallId === undefined) {
391
+ this.sealWaveRequests(wave);
392
+ }
321
393
  }
322
394
 
323
- private emitToolCompleted(callId: string, toolCall: ToolCall, parentCallId?: string): void {
395
+ private noteToolCompleted(
396
+ callId: string,
397
+ toolCall: ToolCall,
398
+ ids?: { parentCallId?: string; modelCallId?: string },
399
+ ): void {
400
+ const parentCallId = ids?.parentCallId;
401
+ const modelCallId = ids?.modelCallId;
324
402
  const description = describeToolCall(toolCall);
325
403
  const result = describeToolResult(toolCall);
326
- this.emit({
327
- type: "action.result",
328
- data: {
329
- callId,
330
- toolName: description.toolName,
331
- output: result.output,
332
- isError: result.isError,
333
- ...(this.isStubbed(callId) ? { stubbed: true as const } : {}),
334
- parentCallId,
335
- },
336
- });
404
+ const actionResult: ActionResult = {
405
+ callId,
406
+ toolName: description.toolName,
407
+ output: result.output,
408
+ isError: result.isError,
409
+ stubbed: this.isStubbed(callId) ? true : undefined,
410
+ parentCallId,
411
+ modelCallId,
412
+ };
413
+ const wave = this.waveForComplete({ parentCallId, modelCallId, callId });
414
+ if (wave === undefined) {
415
+ return;
416
+ }
417
+ const isNew = wave.results.every(existing => existing.callId !== callId);
418
+ if (isNew) {
419
+ wave.results.push(actionResult);
420
+ }
421
+ if (wave.requestedEmitted && isNew) {
422
+ this.emit({
423
+ type: "action.result",
424
+ data: actionResult,
425
+ });
426
+ this.maybeEmitActionsCompleted(wave);
427
+ }
337
428
  if (description.subagent !== undefined && parentCallId === undefined) {
338
429
  this.flushNestedAssistantText(callId, "stop");
339
430
  this.nested.delete(callId);
@@ -344,6 +435,242 @@ export class TurnEventMapper {
344
435
  }
345
436
  }
346
437
 
438
+ private callBelongsToWave(wave: ToolWave, callId: string): boolean {
439
+ return (
440
+ wave.calls.some(existing => existing.callId === callId) ||
441
+ wave.results.some(existing => existing.callId === callId)
442
+ );
443
+ }
444
+
445
+ private singletonWave(parentCallId: string | undefined, callId: string): ToolWave | undefined {
446
+ const key = waveKey({ parentCallId, callId });
447
+ if (this.sealedWaveKeys.has(key) && !this.waves.has(key)) {
448
+ return undefined;
449
+ }
450
+ return this.ensureWave(key, { parentCallId });
451
+ }
452
+
453
+ /**
454
+ * Starts join the open model-step wave until `actions.requested` seals.
455
+ * A later start for that same step is its own singleton pair so completed
456
+ * cannot list tools the matching requested envelope never had.
457
+ */
458
+ private waveForStart(ids: {
459
+ parentCallId?: string;
460
+ modelCallId?: string;
461
+ callId: string;
462
+ }): ToolWave | undefined {
463
+ const groupedKey = waveKey(ids);
464
+ const grouped = this.waves.get(groupedKey);
465
+ if (grouped !== undefined && !grouped.requestedEmitted) {
466
+ return grouped;
467
+ }
468
+ if (grouped !== undefined && this.callBelongsToWave(grouped, ids.callId)) {
469
+ return grouped;
470
+ }
471
+ if (grouped !== undefined && grouped.requestedEmitted) {
472
+ return this.singletonWave(ids.parentCallId, ids.callId);
473
+ }
474
+ if (this.sealedWaveKeys.has(groupedKey)) {
475
+ return this.singletonWave(ids.parentCallId, ids.callId);
476
+ }
477
+ return this.ensureWave(groupedKey, {
478
+ modelCallId: ids.modelCallId,
479
+ parentCallId: ids.parentCallId,
480
+ });
481
+ }
482
+
483
+ /**
484
+ * Completes join a model-step wave only when that call already started
485
+ * there. A start-less complete on an open sibling batch would flush with
486
+ * `actions.requested` and then block `actions.completed` forever.
487
+ */
488
+ private waveForComplete(ids: {
489
+ parentCallId?: string;
490
+ modelCallId?: string;
491
+ callId: string;
492
+ }): ToolWave | undefined {
493
+ const groupedKey = waveKey(ids);
494
+ const grouped = this.waves.get(groupedKey);
495
+ if (grouped !== undefined && this.callBelongsToWave(grouped, ids.callId)) {
496
+ return grouped;
497
+ }
498
+ if (grouped !== undefined) {
499
+ return this.singletonWave(ids.parentCallId, ids.callId);
500
+ }
501
+ if (this.sealedWaveKeys.has(groupedKey)) {
502
+ return this.waves.get(waveKey({ parentCallId: ids.parentCallId, callId: ids.callId }));
503
+ }
504
+ return this.ensureWave(groupedKey, {
505
+ modelCallId: ids.modelCallId,
506
+ parentCallId: ids.parentCallId,
507
+ });
508
+ }
509
+
510
+ private adoptSingletonResults(
511
+ wave: ToolWave,
512
+ parentCallId: string | undefined,
513
+ callId: string,
514
+ ): void {
515
+ const singletonKey = waveKey({ parentCallId, callId });
516
+ if (singletonKey === wave.key) {
517
+ return;
518
+ }
519
+ const singleton = this.waves.get(singletonKey);
520
+ if (singleton === undefined) {
521
+ return;
522
+ }
523
+ for (const result of singleton.results) {
524
+ if (wave.results.every(existing => existing.callId !== result.callId)) {
525
+ wave.results.push(result);
526
+ }
527
+ }
528
+ this.waves.delete(singletonKey);
529
+ }
530
+
531
+ private ensureWave(key: string, ids: { modelCallId?: string; parentCallId?: string }): ToolWave {
532
+ let wave = this.waves.get(key);
533
+ if (wave === undefined) {
534
+ const expectedCalls = this.listedCounts.get(ids.parentCallId ?? "");
535
+ wave = {
536
+ key,
537
+ modelCallId: ids.modelCallId,
538
+ parentCallId: ids.parentCallId,
539
+ expectedCalls,
540
+ calls: [],
541
+ results: [],
542
+ requestedEmitted: false,
543
+ completedEmitted: false,
544
+ };
545
+ this.waves.set(key, wave);
546
+ }
547
+ return wave;
548
+ }
549
+
550
+ private noteRequestsListed(parentCallId: string | undefined, callCount: number): void {
551
+ const listed = Math.max(0, callCount);
552
+ if (listed === 0) {
553
+ this.sealOpenRequests({ parentCallId });
554
+ this.listedCounts.delete(parentCallId ?? "");
555
+ return;
556
+ }
557
+ this.listedCounts.set(parentCallId ?? "", listed);
558
+ for (const wave of this.waves.values()) {
559
+ if (wave.parentCallId !== parentCallId || wave.requestedEmitted) {
560
+ continue;
561
+ }
562
+ wave.expectedCalls = listed;
563
+ this.maybeSealListedWave(wave);
564
+ }
565
+ }
566
+
567
+ private maybeSealListedWave(wave: ToolWave): void {
568
+ const expected = wave.expectedCalls ?? this.listedCounts.get(wave.parentCallId ?? "");
569
+ if (expected !== undefined && wave.calls.length >= expected) {
570
+ this.sealWaveRequests(wave);
571
+ }
572
+ }
573
+
574
+ private sealOpenRequests(ids?: { parentCallId?: string; exceptModelCallId?: string }): void {
575
+ const parentCallId = ids?.parentCallId;
576
+ const exceptModelCallId = ids?.exceptModelCallId;
577
+ for (const wave of this.waves.values()) {
578
+ if (wave.parentCallId !== parentCallId) {
579
+ continue;
580
+ }
581
+ if (exceptModelCallId !== undefined && wave.modelCallId === exceptModelCallId) {
582
+ continue;
583
+ }
584
+ this.sealWaveRequests(wave);
585
+ }
586
+ }
587
+
588
+ private sealWaveRequests(wave: ToolWave): void {
589
+ if (wave.requestedEmitted || wave.calls.length === 0) {
590
+ return;
591
+ }
592
+ wave.requestedEmitted = true;
593
+ this.listedCounts.delete(wave.parentCallId ?? "");
594
+ this.emit({
595
+ type: "actions.requested",
596
+ data: {
597
+ calls: wave.calls,
598
+ parentCallId: wave.parentCallId,
599
+ modelCallId: wave.modelCallId,
600
+ },
601
+ });
602
+ for (const result of wave.results) {
603
+ this.emit({
604
+ type: "action.result",
605
+ data: result,
606
+ });
607
+ }
608
+ this.maybeEmitActionsCompleted(wave);
609
+ }
610
+
611
+ private maybeEmitActionsCompleted(wave: ToolWave): void {
612
+ if (
613
+ wave.completedEmitted ||
614
+ !wave.requestedEmitted ||
615
+ wave.calls.length === 0 ||
616
+ wave.results.length < wave.calls.length
617
+ ) {
618
+ return;
619
+ }
620
+ const callIds = new Set(wave.calls.map(call => call.callId));
621
+ if (!wave.results.every(result => callIds.has(result.callId))) {
622
+ return;
623
+ }
624
+ if (!wave.calls.every(call => wave.results.some(result => result.callId === call.callId))) {
625
+ return;
626
+ }
627
+ this.emitActionsCompleted(wave);
628
+ }
629
+
630
+ private emitActionsCompleted(wave: ToolWave): void {
631
+ wave.completedEmitted = true;
632
+ this.emit({
633
+ type: "actions.completed",
634
+ data: {
635
+ results: wave.results,
636
+ parentCallId: wave.parentCallId,
637
+ modelCallId: wave.modelCallId,
638
+ },
639
+ });
640
+ this.forgetWave(wave);
641
+ }
642
+
643
+ private forgetWave(wave: ToolWave): void {
644
+ this.waves.delete(wave.key);
645
+ this.sealedWaveKeys.add(wave.key);
646
+ for (const callId of new Set([
647
+ ...wave.calls.map(call => call.callId),
648
+ ...wave.results.map(result => result.callId),
649
+ ])) {
650
+ this.sealedWaveKeys.add(waveKey({ parentCallId: wave.parentCallId, callId }));
651
+ }
652
+ wave.calls = [];
653
+ wave.results = [];
654
+ }
655
+
656
+ private flushOpenResults(): void {
657
+ for (const wave of [...this.waves.values()]) {
658
+ if (wave.calls.length === 0 && wave.results.length > 0) {
659
+ wave.calls = wave.results.map(result => ({
660
+ callId: result.callId,
661
+ toolName: result.toolName,
662
+ parentCallId: result.parentCallId,
663
+ modelCallId: result.modelCallId,
664
+ }));
665
+ }
666
+ this.sealWaveRequests(wave);
667
+ this.maybeEmitActionsCompleted(wave);
668
+ if (!wave.completedEmitted) {
669
+ this.forgetWave(wave);
670
+ }
671
+ }
672
+ }
673
+
347
674
  private ensureNested(parentCallId: string): NestedAccumulators {
348
675
  let acc = this.nested.get(parentCallId);
349
676
  if (acc === undefined) {
@@ -415,10 +742,10 @@ export function describeToolCall(toolCall: ToolCall): ToolCallDescription {
415
742
  toolName: "task",
416
743
  args: {
417
744
  description: toolCall.args.description,
418
- ...(subagentName === undefined ? {} : { subagentType: subagentName }),
745
+ subagentType: subagentName,
419
746
  },
420
747
  subagent: {
421
- ...(subagentName === undefined ? {} : { name: subagentName }),
748
+ name: subagentName,
422
749
  description: toolCall.args.description,
423
750
  },
424
751
  };
@@ -462,6 +789,10 @@ export function describeToolResult(toolCall: ToolCall): ToolResultDescription {
462
789
  return { output: result.value, isError: false };
463
790
  }
464
791
 
792
+ function waveKey(ids: { parentCallId?: string; modelCallId?: string; callId: string }): string {
793
+ return `${ids.parentCallId ?? ""}:${ids.modelCallId ?? `call:${ids.callId}`}`;
794
+ }
795
+
465
796
  function addUsage(total: TurnUsage | undefined, usage: TurnUsage): TurnUsage {
466
797
  if (total === undefined) {
467
798
  return { ...usage };
@@ -615,7 +615,7 @@ export function registerHostedDeliveryRoute(input: {
615
615
  }
616
616
  };
617
617
 
618
- // Hosted callers reach this only through AnyRun's signed pod-port URL.
618
+ // Hosted callers reach this only through Anyrun's signed pod-port URL.
619
619
  // The marker is a second, route-specific fence distinct from channel auth.
620
620
  const auth: AuthPolicy = request => {
621
621
  if (request.headers.get(HOSTED_DELIVERY_HEADER) !== "1") {