@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.
- package/DISCLAIMER.md +12 -0
- package/README.md +157 -0
- package/dist/src/agentTools/diff.d.ts +11 -0
- package/dist/src/agentTools/diff.js +88 -0
- package/dist/src/agentTools/policy.d.ts +3 -0
- package/dist/src/agentTools/policy.js +12 -0
- package/dist/src/agentTools/registry.d.ts +114 -0
- package/dist/src/agentTools/registry.js +663 -0
- package/dist/src/agentTools/results.d.ts +14 -0
- package/dist/src/agentTools/results.js +117 -0
- package/dist/src/agentTools/schemas.d.ts +3 -0
- package/dist/src/agentTools/schemas.js +89 -0
- package/dist/src/agentTools/surface.d.ts +11 -0
- package/dist/src/agentTools/surface.js +251 -0
- package/dist/src/certs.d.ts +8 -0
- package/dist/src/certs.js +34 -0
- package/dist/src/ck.d.ts +2 -0
- package/dist/src/ck.js +6 -0
- package/dist/src/ckLauncher.d.ts +150 -0
- package/dist/src/ckLauncher.js +1496 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +265 -0
- package/dist/src/config.d.ts +52 -0
- package/dist/src/config.js +210 -0
- package/dist/src/connectEnvelope.d.ts +16 -0
- package/dist/src/connectEnvelope.js +70 -0
- package/dist/src/desktop.d.ts +19 -0
- package/dist/src/desktop.js +167 -0
- package/dist/src/desktopConnectProxy.d.ts +26 -0
- package/dist/src/desktopConnectProxy.js +175 -0
- package/dist/src/extensions/index.d.ts +2 -0
- package/dist/src/extensions/index.js +1 -0
- package/dist/src/extensions/registry.d.ts +8 -0
- package/dist/src/extensions/registry.js +52 -0
- package/dist/src/extensions/types.d.ts +42 -0
- package/dist/src/extensions/types.js +1 -0
- package/dist/src/fixtures/modelFusion.d.ts +103 -0
- package/dist/src/fixtures/modelFusion.js +404 -0
- package/dist/src/fixtures/replay.d.ts +9 -0
- package/dist/src/fixtures/replay.js +41 -0
- package/dist/src/fixtures/sanitizer.d.ts +9 -0
- package/dist/src/fixtures/sanitizer.js +43 -0
- package/dist/src/fixtures/schema.d.ts +38 -0
- package/dist/src/fixtures/schema.js +33 -0
- package/dist/src/gen/agent/v1/agent_pb.d.ts +21577 -0
- package/dist/src/gen/agent/v1/agent_pb.js +5325 -0
- package/dist/src/gen/aiserver/v1/aiserver_pb.d.ts +135242 -0
- package/dist/src/gen/aiserver/v1/aiserver_pb.js +34430 -0
- package/dist/src/gen/anyrun/v1/anyrun_pb.d.ts +1163 -0
- package/dist/src/gen/anyrun/v1/anyrun_pb.js +374 -0
- package/dist/src/gen/google/protobuf/google_pb.d.ts +142 -0
- package/dist/src/gen/google/protobuf/google_pb.js +54 -0
- package/dist/src/gen/internapi/v1/internapi_pb.d.ts +121 -0
- package/dist/src/gen/internapi/v1/internapi_pb.js +79 -0
- package/dist/src/logger.d.ts +8 -0
- package/dist/src/logger.js +37 -0
- package/dist/src/modelFusion/cursorHarness.d.ts +146 -0
- package/dist/src/modelFusion/cursorHarness.js +647 -0
- package/dist/src/modelFusion/index.d.ts +4 -0
- package/dist/src/modelFusion/index.js +2 -0
- package/dist/src/models/registry.d.ts +22 -0
- package/dist/src/models/registry.js +30 -0
- package/dist/src/proto.d.ts +13 -0
- package/dist/src/proto.js +61 -0
- package/dist/src/providers/openai.d.ts +64 -0
- package/dist/src/providers/openai.js +355 -0
- package/dist/src/redaction.d.ts +4 -0
- package/dist/src/redaction.js +65 -0
- package/dist/src/routeInventory.d.ts +16 -0
- package/dist/src/routeInventory.js +39 -0
- package/dist/src/routes.d.ts +37 -0
- package/dist/src/routes.js +227 -0
- package/dist/src/server.d.ts +50 -0
- package/dist/src/server.js +1353 -0
- package/dist/src/services/agent.d.ts +1 -0
- package/dist/src/services/agent.js +7 -0
- package/dist/src/services/agentRun.d.ts +60 -0
- package/dist/src/services/agentRun.js +391 -0
- package/dist/src/services/chat.d.ts +11 -0
- package/dist/src/services/chat.js +47 -0
- package/dist/src/services/models.d.ts +10 -0
- package/dist/src/services/models.js +216 -0
- package/dist/src/services/serverConfig.d.ts +2 -0
- package/dist/src/services/serverConfig.js +19 -0
- package/dist/src/testing/artifacts.d.ts +14 -0
- package/dist/src/testing/artifacts.js +92 -0
- package/dist/src/testing/cli.d.ts +4 -0
- package/dist/src/testing/cli.js +192 -0
- package/dist/src/testing/localBackend.d.ts +24 -0
- package/dist/src/testing/localBackend.js +310 -0
- package/dist/src/testing/processRunner.d.ts +7 -0
- package/dist/src/testing/processRunner.js +74 -0
- package/dist/src/testing/runner.d.ts +9 -0
- package/dist/src/testing/runner.js +85 -0
- package/dist/src/testing/scenarios.d.ts +3 -0
- package/dist/src/testing/scenarios.js +2535 -0
- package/dist/src/testing/types.d.ts +66 -0
- package/dist/src/testing/types.js +1 -0
- package/dist/src/tools/baselineInventory.d.ts +12 -0
- package/dist/src/tools/baselineInventory.js +680 -0
- package/dist/src/tools/checkModelFusionProtocol.d.ts +1 -0
- package/dist/src/tools/checkModelFusionProtocol.js +274 -0
- package/dist/src/tools/checkReleasePublishConfig.d.ts +1 -0
- package/dist/src/tools/checkReleasePublishConfig.js +99 -0
- package/dist/src/tools/generateProtoInventory.d.ts +1 -0
- package/dist/src/tools/generateProtoInventory.js +89 -0
- package/dist/src/tools/normalizeGeneratedCode.d.ts +1 -0
- package/dist/src/tools/normalizeGeneratedCode.js +18 -0
- package/dist/src/tools/releaseCheck.d.ts +26 -0
- package/dist/src/tools/releaseCheck.js +367 -0
- package/dist/src/trace.d.ts +39 -0
- package/dist/src/trace.js +106 -0
- package/dist/src/translation.d.ts +6 -0
- package/dist/src/translation.js +22 -0
- package/dist/src/upstream.d.ts +20 -0
- package/dist/src/upstream.js +270 -0
- package/docs/configuration.md +55 -0
- package/docs/cursor-app.md +263 -0
- package/docs/implementation-inventory.json +609 -0
- package/docs/learnings.md +363 -0
- package/docs/model-fusion-protocol-origin.json +126 -0
- package/docs/model-fusion-protocol.md +110 -0
- package/docs/plugin-authoring.md +24 -0
- package/docs/proto-inventory.md +1477 -0
- package/docs/protocol-surface-audit.md +92 -0
- package/docs/protocol.md +52 -0
- package/docs/refreshing-protos.md +78 -0
- package/docs/release-gates.md +110 -0
- package/docs/release-summary.json +86 -0
- package/docs/route-contract-manifest.json +288 -0
- package/docs/route-policy.json +133 -0
- package/docs/service-manifest.json +9490 -0
- package/docs/test-manifest.json +155 -0
- package/docs/testing-harness.md +204 -0
- package/docs/troubleshooting.md +36 -0
- package/docs/type-manifest-summary.json +28927 -0
- package/package.json +93 -0
- package/proto/agent/v1/agent.proto +5371 -0
- package/proto/aiserver/v1/aiserver.proto +32944 -0
- package/proto/anyrun/v1/anyrun.proto +294 -0
- package/proto/google/protobuf/google.proto +37 -0
- 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.
|