@velum-labs/cursorkit 0.1.0

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 (142) hide show
  1. package/DISCLAIMER.md +12 -0
  2. package/README.md +157 -0
  3. package/dist/src/agentTools/diff.d.ts +11 -0
  4. package/dist/src/agentTools/diff.js +88 -0
  5. package/dist/src/agentTools/policy.d.ts +3 -0
  6. package/dist/src/agentTools/policy.js +12 -0
  7. package/dist/src/agentTools/registry.d.ts +114 -0
  8. package/dist/src/agentTools/registry.js +663 -0
  9. package/dist/src/agentTools/results.d.ts +14 -0
  10. package/dist/src/agentTools/results.js +117 -0
  11. package/dist/src/agentTools/schemas.d.ts +3 -0
  12. package/dist/src/agentTools/schemas.js +89 -0
  13. package/dist/src/agentTools/surface.d.ts +11 -0
  14. package/dist/src/agentTools/surface.js +251 -0
  15. package/dist/src/certs.d.ts +8 -0
  16. package/dist/src/certs.js +34 -0
  17. package/dist/src/ck.d.ts +2 -0
  18. package/dist/src/ck.js +6 -0
  19. package/dist/src/ckLauncher.d.ts +150 -0
  20. package/dist/src/ckLauncher.js +1496 -0
  21. package/dist/src/cli.d.ts +2 -0
  22. package/dist/src/cli.js +265 -0
  23. package/dist/src/config.d.ts +52 -0
  24. package/dist/src/config.js +210 -0
  25. package/dist/src/connectEnvelope.d.ts +16 -0
  26. package/dist/src/connectEnvelope.js +70 -0
  27. package/dist/src/desktop.d.ts +19 -0
  28. package/dist/src/desktop.js +167 -0
  29. package/dist/src/desktopConnectProxy.d.ts +26 -0
  30. package/dist/src/desktopConnectProxy.js +175 -0
  31. package/dist/src/extensions/index.d.ts +2 -0
  32. package/dist/src/extensions/index.js +1 -0
  33. package/dist/src/extensions/registry.d.ts +8 -0
  34. package/dist/src/extensions/registry.js +52 -0
  35. package/dist/src/extensions/types.d.ts +42 -0
  36. package/dist/src/extensions/types.js +1 -0
  37. package/dist/src/fixtures/modelFusion.d.ts +103 -0
  38. package/dist/src/fixtures/modelFusion.js +404 -0
  39. package/dist/src/fixtures/replay.d.ts +9 -0
  40. package/dist/src/fixtures/replay.js +41 -0
  41. package/dist/src/fixtures/sanitizer.d.ts +9 -0
  42. package/dist/src/fixtures/sanitizer.js +43 -0
  43. package/dist/src/fixtures/schema.d.ts +38 -0
  44. package/dist/src/fixtures/schema.js +33 -0
  45. package/dist/src/gen/agent/v1/agent_pb.d.ts +21577 -0
  46. package/dist/src/gen/agent/v1/agent_pb.js +5325 -0
  47. package/dist/src/gen/aiserver/v1/aiserver_pb.d.ts +135242 -0
  48. package/dist/src/gen/aiserver/v1/aiserver_pb.js +34430 -0
  49. package/dist/src/gen/anyrun/v1/anyrun_pb.d.ts +1163 -0
  50. package/dist/src/gen/anyrun/v1/anyrun_pb.js +374 -0
  51. package/dist/src/gen/google/protobuf/google_pb.d.ts +142 -0
  52. package/dist/src/gen/google/protobuf/google_pb.js +54 -0
  53. package/dist/src/gen/internapi/v1/internapi_pb.d.ts +121 -0
  54. package/dist/src/gen/internapi/v1/internapi_pb.js +79 -0
  55. package/dist/src/logger.d.ts +8 -0
  56. package/dist/src/logger.js +37 -0
  57. package/dist/src/modelFusion/cursorHarness.d.ts +146 -0
  58. package/dist/src/modelFusion/cursorHarness.js +647 -0
  59. package/dist/src/modelFusion/index.d.ts +4 -0
  60. package/dist/src/modelFusion/index.js +2 -0
  61. package/dist/src/models/registry.d.ts +22 -0
  62. package/dist/src/models/registry.js +30 -0
  63. package/dist/src/proto.d.ts +13 -0
  64. package/dist/src/proto.js +61 -0
  65. package/dist/src/providers/openai.d.ts +64 -0
  66. package/dist/src/providers/openai.js +355 -0
  67. package/dist/src/redaction.d.ts +4 -0
  68. package/dist/src/redaction.js +65 -0
  69. package/dist/src/routeInventory.d.ts +16 -0
  70. package/dist/src/routeInventory.js +39 -0
  71. package/dist/src/routes.d.ts +37 -0
  72. package/dist/src/routes.js +227 -0
  73. package/dist/src/server.d.ts +50 -0
  74. package/dist/src/server.js +1353 -0
  75. package/dist/src/services/agent.d.ts +1 -0
  76. package/dist/src/services/agent.js +7 -0
  77. package/dist/src/services/agentRun.d.ts +60 -0
  78. package/dist/src/services/agentRun.js +391 -0
  79. package/dist/src/services/chat.d.ts +11 -0
  80. package/dist/src/services/chat.js +47 -0
  81. package/dist/src/services/models.d.ts +10 -0
  82. package/dist/src/services/models.js +216 -0
  83. package/dist/src/services/serverConfig.d.ts +2 -0
  84. package/dist/src/services/serverConfig.js +19 -0
  85. package/dist/src/testing/artifacts.d.ts +14 -0
  86. package/dist/src/testing/artifacts.js +92 -0
  87. package/dist/src/testing/cli.d.ts +4 -0
  88. package/dist/src/testing/cli.js +192 -0
  89. package/dist/src/testing/localBackend.d.ts +24 -0
  90. package/dist/src/testing/localBackend.js +310 -0
  91. package/dist/src/testing/processRunner.d.ts +7 -0
  92. package/dist/src/testing/processRunner.js +74 -0
  93. package/dist/src/testing/runner.d.ts +9 -0
  94. package/dist/src/testing/runner.js +85 -0
  95. package/dist/src/testing/scenarios.d.ts +3 -0
  96. package/dist/src/testing/scenarios.js +2535 -0
  97. package/dist/src/testing/types.d.ts +66 -0
  98. package/dist/src/testing/types.js +1 -0
  99. package/dist/src/tools/baselineInventory.d.ts +12 -0
  100. package/dist/src/tools/baselineInventory.js +680 -0
  101. package/dist/src/tools/checkModelFusionProtocol.d.ts +1 -0
  102. package/dist/src/tools/checkModelFusionProtocol.js +274 -0
  103. package/dist/src/tools/checkReleasePublishConfig.d.ts +1 -0
  104. package/dist/src/tools/checkReleasePublishConfig.js +99 -0
  105. package/dist/src/tools/generateProtoInventory.d.ts +1 -0
  106. package/dist/src/tools/generateProtoInventory.js +89 -0
  107. package/dist/src/tools/normalizeGeneratedCode.d.ts +1 -0
  108. package/dist/src/tools/normalizeGeneratedCode.js +18 -0
  109. package/dist/src/tools/releaseCheck.d.ts +26 -0
  110. package/dist/src/tools/releaseCheck.js +367 -0
  111. package/dist/src/trace.d.ts +39 -0
  112. package/dist/src/trace.js +106 -0
  113. package/dist/src/translation.d.ts +6 -0
  114. package/dist/src/translation.js +22 -0
  115. package/dist/src/upstream.d.ts +20 -0
  116. package/dist/src/upstream.js +270 -0
  117. package/docs/configuration.md +55 -0
  118. package/docs/cursor-app.md +263 -0
  119. package/docs/implementation-inventory.json +609 -0
  120. package/docs/learnings.md +363 -0
  121. package/docs/model-fusion-protocol-origin.json +126 -0
  122. package/docs/model-fusion-protocol.md +110 -0
  123. package/docs/plugin-authoring.md +24 -0
  124. package/docs/proto-inventory.md +1477 -0
  125. package/docs/protocol-surface-audit.md +92 -0
  126. package/docs/protocol.md +52 -0
  127. package/docs/refreshing-protos.md +78 -0
  128. package/docs/release-gates.md +110 -0
  129. package/docs/release-summary.json +86 -0
  130. package/docs/route-contract-manifest.json +288 -0
  131. package/docs/route-policy.json +133 -0
  132. package/docs/service-manifest.json +9490 -0
  133. package/docs/test-manifest.json +155 -0
  134. package/docs/testing-harness.md +204 -0
  135. package/docs/troubleshooting.md +36 -0
  136. package/docs/type-manifest-summary.json +28927 -0
  137. package/package.json +93 -0
  138. package/proto/agent/v1/agent.proto +5371 -0
  139. package/proto/aiserver/v1/aiserver.proto +32944 -0
  140. package/proto/anyrun/v1/anyrun.proto +294 -0
  141. package/proto/google/protobuf/google.proto +37 -0
  142. package/proto/internapi/v1/internapi.proto +32 -0
@@ -0,0 +1,363 @@
1
+ # Implementation Learnings
2
+
3
+ This document captures behavior discovered while making `cursorkit` work with
4
+ `cursor-agent` and preparing a safe path for the Cursor desktop app. Treat these
5
+ as observed implementation notes, not a stable Cursor contract.
6
+
7
+ ## Guiding Rule
8
+
9
+ Unknown Cursor backend behavior must remain pass-through. A route should become
10
+ typed/intercepted only after we know its path, framing, decoded protobuf type,
11
+ response shape, and failure behavior. The full generated proto is the schema
12
+ source of truth, but the route allowlist is still deliberately narrow.
13
+
14
+ ## Proto And Codegen
15
+
16
+ - The checked-in `proto/` directory is the default full extracted proto surface.
17
+ - Generated TypeScript lives under `src/gen/` and should be regenerated from the
18
+ full proto rather than hand-written or reduced to a slice.
19
+ - Runtime interception is not determined by proto presence. It is determined by
20
+ `src/routes.ts` and `docs/route-policy.json`.
21
+ - When Cursor version drift breaks decoding, refresh the proto, regenerate
22
+ inventory/codegen, then validate affected routes with real traffic.
23
+
24
+ ## CLI Endpoint Behavior
25
+
26
+ - `cursor-agent` supports an explicit backend override with `--endpoint`.
27
+ - The desktop app has no known supported equivalent of `cursor-agent --endpoint`.
28
+ - The CLI and desktop app should be treated as different integration targets,
29
+ even when they share some RPCs.
30
+ - ACP (`agent acp`) is available locally and uses newline-delimited JSON-RPC over
31
+ stdio. Endpoint and model options can be passed on the root command before
32
+ `acp`, and the harness now drives
33
+ `initialize`/`authenticate`/`session/new`/`session/prompt` directly.
34
+
35
+ ## Routing The Local Model Backend At A Fusion Gateway
36
+
37
+ The bridge's local-model backend (`MODEL_BASE_URL`) can point at any
38
+ OpenAI-compatible endpoint, including a model-fusion gateway. This has been
39
+ verified end to end: real `cursor-agent acp` → this bridge → HandoffKit's Fusion
40
+ Harness Gateway → a multi-model fusion run → the synthesized answer streamed
41
+ back to `cursor-agent` via `session/update`.
42
+
43
+ Observed requirements for that flow:
44
+
45
+ - Start the bridge with `MODEL_BASE_URL=<gateway>/v1`, `MODEL_NAME=<local id>`,
46
+ and `MODEL_PROVIDER_MODEL=<model sent upstream>`. The bridge calls
47
+ `<MODEL_BASE_URL>/chat/completions` with `model = MODEL_PROVIDER_MODEL`.
48
+ - Interception only happens when `cursor-agent --model` equals the registered
49
+ local id (`MODEL_NAME`). Other models pass upstream.
50
+ - `BRIDGE_HARDCODED_RESPONSE` short-circuits the backend. The `acp`/`traffic`
51
+ harness suites set it, so they prove connectivity and route inventory, not real
52
+ backend content. To prove a real backend (fusion or otherwise) flows through,
53
+ omit `BRIDGE_HARDCODED_RESPONSE`.
54
+ - ACP still depends on a logged-in Cursor session. `authenticate` uses
55
+ `methodId: "cursor_login"`; without login the flow is `auth_profile_blocked`,
56
+ not a hard failure.
57
+
58
+ The gateway side (dialect translation, streaming, fusion synthesis) lives in
59
+ HandoffKit. Cursorkit's role is unchanged: intercept the local model route and
60
+ proxy it to the configured OpenAI-compatible backend.
61
+
62
+ ## Framing
63
+
64
+ - Cursor backend RPCs can use Connect envelopes or raw protobuf payloads.
65
+ - Cursor Agent model routes have been observed using raw `application/proto`.
66
+ - Other routes, including chat streaming paths, can use `application/connect+proto`.
67
+ - Interceptors must preserve the incoming framing. If the request came in as raw
68
+ protobuf, the response should be raw protobuf. If it came in as Connect, the
69
+ response should be Connect-framed.
70
+ - A route decoder that only works for Connect envelopes can still fail in the CLI
71
+ because the CLI often expects raw protobuf.
72
+
73
+ ## Current Intercepted Routes
74
+
75
+ The verified allowlist currently includes:
76
+
77
+ - `/aiserver.v1.AiService/AvailableModels`
78
+ - `/aiserver.v1.AiService/GetUsableModels`
79
+ - `/aiserver.v1.AiService/GetDefaultModelForCli`
80
+ - `/aiserver.v1.AiService/GetDefaultModel`
81
+ - `/aiserver.v1.AiService/NameAgent`
82
+ - `/aiserver.v1.ServerConfigService/GetServerConfig`
83
+ - `/agent.v1.AgentService/RunSSE`
84
+ - `/aiserver.v1.BidiService/BidiAppend`
85
+ - `/aiserver.v1.ChatService/StreamUnifiedChatWithTools`
86
+
87
+ Everything else should pass through unchanged unless a plugin explicitly handles
88
+ it.
89
+
90
+ ## Model Listing And Picker Behavior
91
+
92
+ - `--list-models` and the interactive `/model` picker do not rely on exactly the
93
+ same client-side state.
94
+ - Seeing a model in `cursor-agent --list-models` does not guarantee it will
95
+ appear in the interactive picker.
96
+ - The interactive picker relies on metadata from `AvailableModels`, not only the
97
+ flat usable-model list.
98
+ - Local models need conservative picker metadata, including:
99
+ - `clientDisplayName`
100
+ - `inputboxShortModelName`
101
+ - `namedModelSectionIndex`
102
+ - `vendorName` / `vendor`
103
+ - `legacySlugs`
104
+ - `idAliases`
105
+ - at least one default non-max variant
106
+ - `displayNameOutsidePicker`
107
+ - `variantStringRepresentation`
108
+ - `legacySlug`
109
+ - `tooltipData`
110
+ - at least one parameter definition and matching variant parameter value
111
+ - The CLI's model picker can be sensitive to punctuation-heavy model IDs. A full
112
+ model ID such as `mlx-community/Qwen3.5-4B-8bit` may fail as a filter while
113
+ partial filters such as `qwen` or `mlx-community` work.
114
+ - Use a stable display alias such as `local-qwen` for Cursor-facing display and
115
+ selection, while keeping the real OpenAI-compatible model ID for backend calls.
116
+ - The bridge now supports that split directly: `id` is the Cursor-facing id,
117
+ while `providerModel` is the model name sent to the OpenAI-compatible backend.
118
+ - The picker can briefly show no matches before its model list settles. E2E tests
119
+ should distinguish "model is listed" from "model is already selected".
120
+
121
+ ## Usable Models
122
+
123
+ - Cursor Agent reads `/aiserver.v1.AiService/GetUsableModels` for its usable
124
+ model list.
125
+ - Local usable-model entries need `apiKeyCredentials.baseUrl`; otherwise the CLI
126
+ can treat the model as unavailable.
127
+ - `GetDefaultModelForCli` should preserve an upstream default when available, but
128
+ fall back to the first local model when upstream is absent or unusable.
129
+
130
+ ## Server Config
131
+
132
+ - Cursor clients can negotiate HTTP/2 over TLS in desktop proxy mode. The bridge
133
+ now starts a TLS HTTP/2 server with `allowHTTP1` for desktop mode, so existing
134
+ HTTP/1 clients and HTTP/2 desktop calls share the same route handlers.
135
+ - HTTP/2 requests include pseudo-headers such as `:method` and `:path`. These
136
+ must be stripped before proxying to Node's HTTP/1 upstream client.
137
+ - `agentUrlConfig.agentUrl` and `agentUrlConfig.agentnUrl` should point back to
138
+ the bridge origin.
139
+ - For desktop proxy mode, the public origin should be `https://api2.cursor.sh`,
140
+ not `https://127.0.0.1:9443`, because the app believes it is talking to the
141
+ real backend host.
142
+ - Upstream server config decoding can fail because upstream content/framing can
143
+ be unexpected. The bridge should synthesize a minimal local server config
144
+ rather than making local model routing fail.
145
+
146
+ ## Agent Run Flow
147
+
148
+ - Cursor Agent opens `/agent.v1.AgentService/RunSSE` for an agent run.
149
+ - In observed CLI behavior, actual run request details can arrive through
150
+ `/aiserver.v1.BidiService/BidiAppend`.
151
+ - `BidiAppend.data` can contain a hex-encoded `AgentClientMessage` protobuf.
152
+ - The bridge tracks pending agent runs by request ID so `RunSSE` can wait for the
153
+ corresponding `BidiAppend` payload.
154
+ - Local agent responses use `AgentServerMessage` frames with text deltas followed
155
+ by a turn-ended update.
156
+
157
+ ## Cursor Agent Traffic Probe
158
+
159
+ - The unified harness `traffic` suite runs real `cursor-agent --list-models` and
160
+ `cursor-agent --print` commands through the bridge with route inventory
161
+ enabled.
162
+ - In a Qwen MLX probe, `--list-models` exercised all known model-list routes:
163
+ `/aiserver.v1.AiService/AvailableModels`,
164
+ `/aiserver.v1.AiService/GetUsableModels`, and
165
+ `/aiserver.v1.AiService/GetDefaultModelForCli`.
166
+ - A normal `--print` prompt exercised `/agent.v1.AgentService/RunSSE` and
167
+ `/aiserver.v1.BidiService/BidiAppend`; `RunSSE` used Connect protobuf framing
168
+ while `BidiAppend` used raw protobuf framing.
169
+ - The CLI also made healthy pass-through calls to dashboard, analytics, and
170
+ `/v1/traces` routes. These should remain pass-through unless a typed
171
+ interceptor has a clear product reason and decoded request/response fixtures.
172
+ - Treat pass-through HTTP 200 route inventory as discovery evidence, not a
173
+ failure. Only HTTP error statuses should be reported as failed routes.
174
+ - Route reports now include an aggregated `routeSummary` with per-path count,
175
+ method, status, framing, policy, and outcome sets. Prefer this summary over
176
+ scanning duplicate raw log lines.
177
+
178
+ ## Cursor Agent ACP Probe
179
+
180
+ - The unified harness `acp` suite starts the bridge and drives
181
+ `cursor-agent acp` over JSON-RPC instead of a PTY.
182
+ - A verified local run against `mlx-community/Qwen3.5-4B-8bit` on port 8080
183
+ completed `initialize`, `authenticate`, `session/new`, and `session/prompt`,
184
+ then returned `traffic-probe-ok`.
185
+ - The ACP prompt exercised the same core local routes as the PTY traffic probe:
186
+ all three known model-list RPCs, `/agent.v1.AgentService/RunSSE`, and
187
+ `/aiserver.v1.BidiService/BidiAppend`.
188
+ - ACP is now the preferred structured smoke path for non-interactive agent runs,
189
+ but it does not replace the TUI e2e test for interactive `/model` picker
190
+ behavior.
191
+ - ACP carries a much richer agent surface than plain `cursor-agent --print` in
192
+ current probes. A real ACP run sent 59 MCP tool definitions; the plain traffic
193
+ probe sent zero.
194
+ - Local agent translation now forwards Cursor-provided selected context, inline
195
+ prompt context, custom system prompts, hook context, and MCP tool metadata into
196
+ the OpenAI-compatible request when those fields are present.
197
+ - Live tool execution is not complete yet. Implementing that requires translating
198
+ OpenAI tool calls into `AgentServerMessage` tool-call updates, receiving
199
+ Cursor/client tool results, and continuing the local model loop. Treat
200
+ `agentRunDiagnostics.mcpToolCount > 0` as evidence that tool definitions were
201
+ present, not evidence that the local model can execute tools.
202
+
203
+ ## Chat Flow
204
+
205
+ - `StreamUnifiedChatWithTools` is intercepted only when the selected model is
206
+ registered locally.
207
+ - Non-local model requests should pass upstream.
208
+ - Local chat responses stream `StreamUnifiedChatResponseWithTools` messages in
209
+ Connect envelopes.
210
+ - The OpenAI-compatible backend must emit `data:` SSE lines and a `[DONE]` event
211
+ for the current parser.
212
+
213
+ ## Upstream Robustness
214
+
215
+ - Upstream model or server-config payloads can be unavailable, unauthorized, or
216
+ decodable under a different framing than expected.
217
+ - Local model availability should not depend on successfully decoding upstream
218
+ model lists. When upstream model decoding fails, merge against an empty local
219
+ response and log a warning.
220
+ - Local server config should similarly fall back to a minimal safe config when
221
+ upstream config is unavailable or undecodable.
222
+ - Once `api2.cursor.sh` is redirected to localhost for desktop experiments, the
223
+ bridge's own upstream connection can loop back into itself. Desktop mode needs
224
+ a separate physical upstream connection target.
225
+ - `CURSOR_UPSTREAM_CONNECT_HOST` and `CURSOR_UPSTREAM_CONNECT_PORT` allow the
226
+ bridge to connect to a real upstream address while preserving Host and TLS SNI
227
+ from `CURSOR_UPSTREAM_BASE_URL`.
228
+
229
+ ## Desktop App Proxy
230
+
231
+ - The desktop app path is not the CLI path. Start with route observation, not
232
+ guessed interceptors.
233
+ - Desktop proxy mode makes the bridge present itself as Cursor backend hosts
234
+ locally. Observed desktop resource traffic can use `api3.cursor.sh`, while
235
+ model/agent backend defaults still use `api2.cursor.sh`.
236
+ - The local certificate must include `api2.cursor.sh` and `api3.cursor.sh` in
237
+ the SAN list.
238
+ - The CLI should never silently edit `/etc/hosts`, install trust, or install
239
+ `pf` rules. It should print commands and leave privileged changes to the user.
240
+ - `ck` is the safe launcher path for desktop testing. It owns only the bridge
241
+ child process and an isolated Cursor profile under `.cursor-rpc/ck/`.
242
+ - `ck` now uses a first-class local HTTP CONNECT proxy for isolated desktop
243
+ launches. Cursor is launched with `--proxy-server=http://127.0.0.1:<proxyPort>`.
244
+ CONNECT requests for Cursor backend hosts are tunneled into the bridge; other
245
+ hosts pass through normally.
246
+ - The CONNECT proxy path was validated to route desktop startup, auth,
247
+ telemetry, model-list, default-model, dashboard, MCP discovery, and normal
248
+ pass-through traffic through the bridge without privileged system changes.
249
+ - `--host-resolver-rules` is still useful historical context and a fallback
250
+ primitive, but it is no longer the preferred isolated `ck` path.
251
+ - Chromium `--host-resolver-rules` can map ports, e.g.
252
+ `MAP api2.cursor.sh 127.0.0.1:<bridgePort>`. This fixes renderer traffic for
253
+ isolated non-privileged launches; a host-only mapping silently leaves HTTPS on
254
+ port 443 and misses an unprivileged bridge.
255
+ - Agent execution in current desktop builds should be tested through the CONNECT
256
+ proxy path first. The old renderer-only route-resolver failure is solved for
257
+ observed desktop traffic; the remaining harness gap is reliable programmatic
258
+ submission into Cursor's Monaco-backed Agent composer.
259
+ - `NODE_OPTIONS=--require` was tested as a non-privileged extension-host hook
260
+ path and did not load in Cursor/Electron helper processes. Do not rely on it
261
+ for desktop Agent routing.
262
+ - Browser login redirects are handled by macOS through Cursor's registered URL
263
+ handler, and that callback does not necessarily preserve the isolated
264
+ `--user-data-dir` flags. If an isolated `ck` window stays on the login screen
265
+ after browser confirmation, use `ck --use-default-profile` to reuse existing
266
+ Cursor auth state for the routing test.
267
+ - Desktop app testing should distinguish backend routing from picker
268
+ visibility. `ck test` watches route inventory and reports whether any desktop
269
+ traffic, and specifically the known model-list RPCs, reached the bridge.
270
+ `desktop-ui-experimental` verifies the real model picker through CDP.
271
+ - `ck route`, `ck route status`, and `ck route rollback` are the non-mutating
272
+ operator workflow for the manual routing fallback. They print/check commands
273
+ but do not run `sudo`, trust certs, edit `/etc/hosts`, configure `pf`, or kill
274
+ Cursor.
275
+ - Run `ck route` before redirecting `api2.cursor.sh` so the current real upstream
276
+ address can be captured for `CURSOR_UPSTREAM_CONNECT_HOST`. Once DNS points to
277
+ localhost, automatic upstream detection is no longer reliable.
278
+ - `ck --debug-port` and the `desktop-ui-experimental` harness suite provide a
279
+ real Cursor desktop app automation surface through Chromium CDP. This works for
280
+ isolated profiles; default-profile launches may attach to an existing Cursor
281
+ process and ignore the new debug port.
282
+ - `ck --seed-auth-from-default` seeds only `cursorAuth/*` rows from the logged-in
283
+ default Cursor profile into the isolated test profile. This gets the isolated
284
+ CDP run past login without copying the whole profile.
285
+ - The desktop model picker is settings-backed in observed builds. Opening the
286
+ picker did not issue a fresh model-list request through renderer HTTP; renderer
287
+ resource timing showed WorkOS assets and `api3.cursor.sh` telemetry, while the
288
+ picker contents came from cached profile state.
289
+ - Simply appending to `availableDefaultModels2` is not enough. The model also
290
+ needs to be activated in settings state, including `aiSettings.userAddedModels`
291
+ and visibility/fallback model lists. This matches the Settings UI behavior
292
+ where models can be toggled on before appearing in the picker.
293
+ - The strict desktop UI probe now waits for Cursor to initialize the isolated
294
+ profile, merges the local model additively into the settings-backed model
295
+ state, reloads the workbench, dismisses safe onboarding prompts, opens a new
296
+ Agent composer, clicks the active model picker trigger, and asserts both the
297
+ local model and built-in Cursor models are visible.
298
+ - A desktop cutover needs:
299
+ - trusted local cert for `api2.cursor.sh`
300
+ - local routing of `api2.cursor.sh:443` to the bridge
301
+ - a non-looping upstream connect target
302
+ - route inventory logging enabled
303
+ - Rollback should be known before testing: quit Cursor, stop the bridge, remove
304
+ the hosts entry, disable any redirect, then reopen Cursor.
305
+
306
+ ## Route Inventory
307
+
308
+ - Desktop mode enables redacted route inventory logs automatically.
309
+ - Route inventory records metadata only:
310
+ - method
311
+ - path
312
+ - content type
313
+ - status
314
+ - framing
315
+ - route policy
316
+ - handling outcome
317
+ - It must not capture bodies by default. Bodies can include prompts, file paths,
318
+ cookies, tokens, and workspace data.
319
+
320
+ ## Security And Safety
321
+
322
+ - Bind to localhost by default.
323
+ - Require `BRIDGE_UNSAFE_ALLOW_NON_LOCALHOST=true` before binding to a
324
+ non-localhost host.
325
+ - Do not commit `.cursor-rpc/`, generated certs, keys, captures, or unsanitized
326
+ traffic.
327
+ - Redact authorization, cookies, API keys, and token-like query values in logs.
328
+ - Prefer explicit local-only docs over convenience automation for privileged
329
+ networking and trust-store changes.
330
+
331
+ ## Testing Lessons
332
+
333
+ - `pnpm check` should remain the baseline: TypeScript build, unit/integration
334
+ tests, and formatting.
335
+ - `pnpm e2e:cursor-agent` catches real CLI regressions that unit tests miss,
336
+ especially picker state and framing behavior.
337
+ - The e2e harness should tolerate the model already being selected, because the
338
+ TUI footer and picker state can differ.
339
+ - Regression tests now cover:
340
+ - desktop config defaults
341
+ - generated cert SANs
342
+ - route inventory metadata
343
+ - upstream connect host preserving Host and SNI
344
+ - malformed upstream model payload fallback
345
+ - malformed upstream server config fallback
346
+ - HTTPS typed model and chat interception
347
+ - harness suite expansion for ACP
348
+ - route inventory aggregation for report artifacts
349
+ - agent-run context injection into local OpenAI-compatible requests
350
+ - `ck` debug-port and isolated instance launch planning
351
+ - isolated Cursor auth seeding from default profile `cursorAuth/*` rows
352
+ - strict desktop model picker CDP automation
353
+
354
+ ## Known Unknowns
355
+
356
+ - The desktop app's full model-picker and chat route set is not yet verified.
357
+ - The desktop app may use routes beyond the current CLI-derived allowlist.
358
+ - More app-specific model metadata may be required after observing real desktop
359
+ traffic.
360
+ - HTTP/2 and ALPN behavior for the desktop app should be observed before adding
361
+ HTTP/2 support.
362
+ - `StreamUnifiedChatWithToolsSSE` remains a route to capture before enabling any
363
+ typed behavior around it.
@@ -0,0 +1,126 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "canonicalSpec": "https://github.com/velum-labs/openclaw-shared/blob/main/spec/2026-06-16-model-fusion-protocol-packaging-spec.md",
4
+ "origin": {
5
+ "repo": "fusionkit",
6
+ "status": "contract-idl-origin",
7
+ "notes": "fusionkit remains the source of truth for model-fusion JSON Schema durable records and OpenAPI 3.1 HTTP/service contracts until stable generated packages are published."
8
+ },
9
+ "serviceIdl": {
10
+ "sourceOfTruth": "openapi-3.1",
11
+ "openapi": "v1-http-json-source-of-truth",
12
+ "localCursorkitStatus": "consumes-published-protocol-package"
13
+ },
14
+ "codegen": {
15
+ "typescript": {
16
+ "openapi": {
17
+ "generator": "openapi-typescript",
18
+ "generatedTypes": "@velum-labs/model-fusion-protocol",
19
+ "clientRuntime": "openapi-fetch",
20
+ "driftCheck": "corepack pnpm model-fusion:protocol:check"
21
+ },
22
+ "jsonSchema": {
23
+ "source": "fusionkit JSON Schema bundle",
24
+ "targetPackage": "@velum-labs/model-fusion-protocol",
25
+ "localStatus": "temporary fixture validators with schema bundle provenance until generated validators are published"
26
+ }
27
+ },
28
+ "python": {
29
+ "openapi": {
30
+ "preferredGenerators": [
31
+ "openapi-python-client"
32
+ ],
33
+ "targetPackage": "velum-model-fusion-protocol"
34
+ },
35
+ "jsonSchema": {
36
+ "preferredGenerators": [
37
+ "datamodel-code-generator"
38
+ ],
39
+ "targetPackage": "velum-model-fusion-protocol"
40
+ }
41
+ }
42
+ },
43
+ "persistedRecordFormat": {
44
+ "sourceOfTruth": "json-schema",
45
+ "notes": "JSON Schema remains the persisted audit and benchmark record format."
46
+ },
47
+ "protobufBuf": {
48
+ "v1Required": false,
49
+ "status": "reserved-for-future-internal-streaming-connect-grpc",
50
+ "notes": "Do not require protobuf/Buf for v1 package or HTTP/JSON service consumption."
51
+ },
52
+ "schemaBundleHash": "sha256:955da2d6891c88d4c40746a8206439e2dae2efc1e7ffefca015e84d4ce265671",
53
+ "persistedJsonSchemas": [
54
+ "harness-run-request.v1",
55
+ "harness-run-result.v1",
56
+ "cursor-run-request.v1",
57
+ "cursor-run-result.v1"
58
+ ],
59
+ "packages": {
60
+ "typescript": {
61
+ "name": "@velum-labs/model-fusion-protocol",
62
+ "registry": "npm-or-github-packages",
63
+ "generatedFrom": "fusionkit JSON Schema and OpenAPI 3.1 contracts",
64
+ "consumerPlan": "Cursorkit consumes the published package and verifies package metadata, version, and schema bundle hash before merge/release.",
65
+ "currentStatus": "published-consumed"
66
+ },
67
+ "python": {
68
+ "name": "velum-model-fusion-protocol",
69
+ "preferredPrivateIndexes": [
70
+ "Cloudsmith",
71
+ "CodeArtifact",
72
+ "Gemfury"
73
+ ],
74
+ "shortTermOptions": [
75
+ "GitHub Releases wheels",
76
+ "uv git deps"
77
+ ],
78
+ "generatedFrom": "fusionkit JSON Schema and OpenAPI 3.1 contracts",
79
+ "consumerPlan": "Python consumers should use a private PyPI-compatible index; GitHub Packages alone is not sufficient for Python packages."
80
+ }
81
+ },
82
+ "serviceBoundaries": [
83
+ {
84
+ "service": "model_fusion.v1.HarnessExecutorService",
85
+ "owner": "handoffkit",
86
+ "consumer": "fusionkit",
87
+ "status": "fusionkit-origin-planned",
88
+ "purpose": "Coding task execution handoff from benchmark orchestration to executor harnesses."
89
+ },
90
+ {
91
+ "service": "CursorHarnessHttpApi",
92
+ "owner": "cursorkit",
93
+ "consumer": "fusionkit",
94
+ "status": "consumes-published-protocol-package",
95
+ "canonicalSource": "fusionkit",
96
+ "purpose": "Cursor adapter execution output, transcript/artifact evidence, and mapped harness result handoff."
97
+ },
98
+ {
99
+ "service": "model_fusion.v1.MlxProviderService",
100
+ "owner": "providerkit",
101
+ "consumer": "fusionkit",
102
+ "status": "fusionkit-origin-planned",
103
+ "purpose": "Provider capability and model-call metadata for MLX-backed candidates."
104
+ },
105
+ {
106
+ "service": "model_fusion.v1.BenchmarkExecutionService",
107
+ "owner": "fusionkit",
108
+ "consumer": "fusionkit",
109
+ "status": "fusionkit-origin-planned",
110
+ "purpose": "Benchmark execution and join envelopes for evaluation records."
111
+ }
112
+ ],
113
+ "driftChecks": {
114
+ "cursorkit": "corepack pnpm model-fusion:protocol:check",
115
+ "fusionkitTarget": "Generate TS and Python bindings from fusionkit JSON Schema/OpenAPI contracts and verify schema bundle hashes before publish."
116
+ },
117
+ "followUpWorkOutsideCursorkit": [
118
+ "Move the canonical Cursor harness OpenAPI 3.1 source into fusionkit-owned protocol source.",
119
+ "Maintain @velum-labs/model-fusion-protocol releases for TypeScript consumers.",
120
+ "Publish velum-model-fusion-protocol wheels through a private PyPI-compatible index or short-term GitHub Releases wheels plus uv git dependencies.",
121
+ "Generate TS OpenAPI client/types with openapi-typescript plus openapi-fetch from fusionkit OpenAPI contracts.",
122
+ "Generate TS durable-record validators/types from the fusionkit JSON Schema bundle.",
123
+ "Generate Python OpenAPI client/models and JSON Schema/Pydantic validators from fusionkit contracts.",
124
+ "Publish JSON Schema bundle metadata with generated packages so consumers can verify schema bundle hashes."
125
+ ]
126
+ }
@@ -0,0 +1,110 @@
1
+ # Model Fusion Protocol Consumption
2
+
3
+ `fusionkit` remains the model-fusion contract origin. For v1, JSON Schema is the
4
+ source of truth for durable audit and benchmark records, and OpenAPI 3.1 is the
5
+ source of truth for HTTP/JSON service APIs. Cursorkit should consume stable
6
+ generated artifacts from fusionkit instead of copying contract shapes across
7
+ repositories. Cursorkit consumes the published
8
+ `@velum-labs/model-fusion-protocol` package and verifies its protocol metadata
9
+ against the local schema-bundle pin.
10
+
11
+ ## Package targets
12
+
13
+ - TypeScript consumers should depend on `@velum-labs/model-fusion-protocol` from
14
+ GitHub Packages. Cursorkit pins version `0.1.0` as a dev dependency and checks
15
+ its `protocol-package.json` metadata.
16
+ - Python consumers need a private PyPI-compatible path. Preferred options are
17
+ Cloudsmith, CodeArtifact, or Gemfury. Short-term fallback options are GitHub
18
+ Releases wheels or `uv` git dependencies; GitHub Packages alone is not enough
19
+ for Python package consumption.
20
+
21
+ ## Durable records vs service APIs
22
+
23
+ - JSON Schema remains the persisted audit and benchmark record format for records
24
+ such as
25
+ `cursor-run-request.v1`, `cursor-run-result.v1`, `harness-run-request.v1`, and
26
+ `harness-run-result.v1`.
27
+ - OpenAPI 3.1 describes the v1 HTTP/JSON service surfaces and should reference
28
+ or embed those JSON Schema record contracts.
29
+ - Service/API clients and request/response models should be generated from
30
+ OpenAPI specs. The TypeScript package should use generated OpenAPI
31
+ client/types, for example `openapi-typescript` paired with `openapi-fetch`.
32
+ Python should use generated OpenAPI client/models, for example
33
+ `openapi-python-client`.
34
+ - Durable record validators and record types should be generated from the JSON
35
+ Schema bundle. Python packages should expose JSON Schema/Pydantic validators,
36
+ for example via `datamodel-code-generator` where appropriate.
37
+ - Protobuf/Buf is reserved for later internal streaming, Connect, or gRPC paths
38
+ if a service boundary hardens. It is not required for v1 package or HTTP/JSON
39
+ consumption in cursorkit.
40
+
41
+ ## Service boundaries
42
+
43
+ The minimum model-fusion protocol surface is:
44
+
45
+ - `HarnessExecutorService`: fusionkit to handoffkit coding-task execution.
46
+ - `CursorHarnessHttpApi`: fusionkit to cursorkit adapter output over HTTP/JSON.
47
+ - `MlxProviderService`: provider capability and model-call metadata.
48
+ - Benchmark execution/join envelopes: fusionkit benchmark orchestration and eval
49
+ joins.
50
+
51
+ Cursorkit does not own the v1 model-fusion OpenAPI source. Fusionkit owns the
52
+ canonical OpenAPI 3.1 source and generated SDK package. Cursorkit consumes the
53
+ published package for protocol metadata and keeps fixture-only local validators
54
+ with schema-bundle provenance until the runtime adapter is switched to generated
55
+ package imports.
56
+
57
+ ## Published package consumption
58
+
59
+ `@velum-labs/model-fusion-protocol@0.1.0` is published in GitHub Packages and is
60
+ installed as a cursorkit dev dependency. The protocol check reads the installed
61
+ package's `protocol-package.json` and verifies:
62
+
63
+ 1. package name is `@velum-labs/model-fusion-protocol`;
64
+ 2. package version matches `package.json#modelFusionProtocol.version`;
65
+ 3. schema bundle hash matches `MODEL_FUSION_SCHEMA_BUNDLE_HASH`.
66
+
67
+ ## Drift checks
68
+
69
+ Run:
70
+
71
+ ```bash
72
+ corepack pnpm model-fusion:protocol:check
73
+ ```
74
+
75
+ The check verifies:
76
+
77
+ - the local schema bundle hash matches
78
+ `docs/model-fusion-protocol-origin.json`;
79
+ - committed model-fusion JSON fixtures use the same schema bundle hash;
80
+ - local OpenAPI/protobuf mirrors are not present as the v1 contract path;
81
+ - `@velum-labs/model-fusion-protocol` is installed and its package metadata,
82
+ version, and schema bundle hash match the local manifest;
83
+ - the Python packaging plan remains documented until fusionkit publishes a
84
+ private PyPI-compatible wheel.
85
+ - the docs state the corrected v1 decision: JSON Schema for durable records,
86
+ OpenAPI 3.1 for HTTP/JSON APIs, and protobuf/Buf future-facing only.
87
+
88
+ When the runtime adapter moves from fixture validators to generated package
89
+ imports, keep this check as the guard that the consumed package version,
90
+ OpenAPI/JSON Schema contracts, and JSON schema bundle hash agree.
91
+
92
+ ## Follow-up outside cursorkit
93
+
94
+ These items belong in fusionkit or the shared release infrastructure, not in this
95
+ PR:
96
+
97
+ - move the canonical Cursor harness OpenAPI 3.1 source into fusionkit-owned
98
+ protocol source;
99
+ - maintain the `@velum-labs/model-fusion-protocol` TypeScript package release;
100
+ - publish `velum-model-fusion-protocol` wheels through a private
101
+ PyPI-compatible index, or use GitHub Releases wheels plus `uv` git dependencies
102
+ as a short-term bridge;
103
+ - generate TypeScript OpenAPI client/types with `openapi-typescript` plus
104
+ `openapi-fetch` from fusionkit OpenAPI contracts;
105
+ - generate TypeScript durable-record validators/types from the fusionkit JSON
106
+ Schema bundle;
107
+ - generate Python OpenAPI client/models and JSON Schema/Pydantic validators from
108
+ fusionkit contracts;
109
+ - publish JSON Schema bundle metadata with generated packages so consumers can
110
+ verify schema bundle hashes instead of copying validators by hand.
@@ -0,0 +1,24 @@
1
+ # Plugin Authoring
2
+
3
+ The extension API is experimental and local-only. Plugins run as trusted in-process code; remote plugin loading is not supported.
4
+
5
+ ## Minimal Shape
6
+
7
+ ```ts
8
+ import type { CursorExtension } from "@velum-labs/cursorkit/extensions";
9
+
10
+ export default {
11
+ name: "my-extension",
12
+ setup(context) {
13
+ context.logger.info("extension loaded");
14
+ },
15
+ } satisfies CursorExtension;
16
+ ```
17
+
18
+ ## Rules
19
+
20
+ - Model IDs must be unique.
21
+ - Route handlers must be unique per path.
22
+ - Middleware is metadata-only unless it explicitly declares `bodyAccess: "consume"`.
23
+ - New endpoint work starts observe-only, then decode-only with fixtures, then intercept-capable behind config.
24
+ - Experimental APIs may change before the package becomes publishable.