llm-cli-gateway 2.12.2 → 2.13.1

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.
@@ -3,7 +3,7 @@ name: multi-llm-review
3
3
  description: Parallel code reviews across Claude, Codex, Gemini, Grok, and Mistral. Use for quality analysis, bug finding, or security audit.
4
4
  metadata:
5
5
  author: verivus-oss
6
- version: "1.6"
6
+ version: "1.7"
7
7
  ---
8
8
 
9
9
  # Multi-LLM Code Review
@@ -24,10 +24,89 @@ Parallel reviews using gateway MCP tools. Each LLM has different strengths — c
24
24
 
25
25
  Apply these on every dispatch unless the caller has explicitly overridden a rule in the current turn:
26
26
 
27
- 1. **Omit `model`** let the gateway use its configured default per CLI. Nominating a model risks deprecated IDs (`o3`, `o3-pro`, `gpt-4o`, …) and capability mismatches. Call `list_models` only when the caller has asked for a specific variant.
28
- 2. **`approvalStrategy:"mcp_managed"`** is the skill dispatch default (the gateway schema default is `"legacy"`). It gates the request before execution; Claude then runs with `--permission-mode bypassPermissions`, Gemini with `--approval-mode yolo`, and Codex still needs `fullAuto:true` for autonomous file/shell work. Prefer this over raw bypass flags.
29
- 3. **No wallclock timeout; poll every 60 s** let sync auto-defer at 45 s or use `*_request_async`. Poll `llm_job_status` once every 60 seconds. Do **not** cancel jobs for taking too long; cancel only on explicit instruction or hard failure. `idleTimeoutMs` (no-output safeguard) is separate.
30
- 4. **Iterate until unconditional APPROVED** (review dispatches only)every review prompt must end with "End with APPROVED or NOT APPROVED with findings." On `NOT APPROVED` or conditional approval, consolidate findings, dispatch fixes (Codex + `fullAuto:true`), re-dispatch the review to the same reviewer. Repeat until unconditional APPROVED. Escalate after 3 rounds without convergence. This rule does **not** apply to pure implementation or non-review analysis dispatches.
27
+ 1. **Use the stdio gateway MCP surface only** call the host's `mcp__gtwy__*` tools (or the equivalent stdio `gtwy` namespace exposed to the current agent). Do not use connector/shadow gateway tools when the user asked for stdio gateway validation.
28
+ 2. **Omit `model`** let the gateway use its configured default per CLI. Nominating a model risks deprecated IDs (`o3`, `o3-pro`, `gpt-4o`, ...) and capability mismatches. Call `list_models` only when the caller has asked for a specific variant.
29
+ 3. **`approvalStrategy:"mcp_managed"`** is the skill dispatch default (the gateway schema default is `"legacy"`). It gates the request before execution. Grant full non-interactive verification permissions on every review round because permission grants are not assumed durable.
30
+ 4. **Full verification access for reviewers is required**reviewers need read, test/build, code-search, docs lookup, web/search, and provider-safe gateway introspection access where appropriate. Do not suppress tools or pass empty allowlists. If a provider-specific permission or MCP server required for verification is rejected, treat that as a review dispatch failure and fix the gateway/permission setup before proceeding, unless the user explicitly authorizes a degraded review.
31
+ 5. **No wallclock timeout; poll every 90 s** — use `*_request_async`. Poll `llm_job_status` no more than once every 90 seconds. Do **not** cancel reviewer jobs for taking too long; cancel only on explicit user instruction or a terminal provider/runtime failure. `idleTimeoutMs` (no-output safeguard) is separate.
32
+ 6. **Iterate until unconditional APPROVED** (review dispatches only) — every review prompt must end with a strict verdict requirement. On `NOT APPROVED`, `CHANGES_REQUIRED`, `BLOCKER`, or conditional approval, consolidate findings, apply fixes, refresh the verification report and exact diff/commit evidence, then re-dispatch the same reviewers. Repeat until unconditional approval or a concrete blocker remains after evidence-based rebuttal. This rule does **not** apply to pure implementation or non-review analysis dispatches.
33
+
34
+ ## Standard Validation Gate
35
+
36
+ Use this gate whenever the user asks for "the other LLMs", "cross review",
37
+ "red team", "validation", or similar review work.
38
+
39
+ ### Evidence Packet
40
+
41
+ Before dispatch, prepare a stable packet and pass the same packet to every
42
+ reviewer:
43
+
44
+ - **Corrective-program verification report**: the local verification report used
45
+ as the spec for the review. It must list claims, commands run, test names,
46
+ command-result digests, and code/doc evidence.
47
+ - **Exact change set**: commit SHA(s) when available, the diff range or explicit
48
+ `git diff` command, and the changed-file list. If the work is uncommitted,
49
+ state that clearly and include the exact dirty-file list.
50
+ - **Scope and invariants**: DAG step(s), issue/PR references, security
51
+ invariants, docs that define intended behavior, and out-of-scope files.
52
+ - **Review log from prior rounds**: verbatim reviewer findings plus per-finding
53
+ responses marked `FIXED`, `DISAGREE`, or `BLOCKER`, each with file:line, test,
54
+ command, or doc evidence.
55
+
56
+ The packet is not a substitute for inspection. The prompt must explicitly say
57
+ that the verification report and summary are claims, not evidence.
58
+
59
+ ### Reviewer Contract
60
+
61
+ Every reviewer prompt must require:
62
+
63
+ - Verify claims against actual code, tests, docs, and upstream documentation.
64
+ - Open/read the changed files and relevant neighboring code directly.
65
+ - Run or inspect the cited tests/builds; approval cannot be based on a summary,
66
+ intent, plan-compliance, or "should be fixed" language.
67
+ - Cite concrete evidence for each finding: `file:line`, test name + command, or
68
+ upstream doc URL. If a claim cannot be verified, report it as at least a major
69
+ finding.
70
+ - Return strict structured output with `verdict`, `findings`, `inspected`, and
71
+ `unconditional_approval_blockers`.
72
+
73
+ Recommended final verdict enum:
74
+
75
+ ```json
76
+ {
77
+ "verdict": "APPROVED | CHANGES_REQUIRED | BLOCKER",
78
+ "findings": [
79
+ {
80
+ "id": "F1",
81
+ "severity": "blocker | major | minor | nit",
82
+ "file": "src/example.ts",
83
+ "line": 123,
84
+ "claim": "Exact claim reviewed",
85
+ "issue": "What is wrong",
86
+ "evidence": "file:line, command/test output digest, URL, or unable to verify: reason",
87
+ "suggested_fix": "Concrete fix"
88
+ }
89
+ ],
90
+ "inspected": ["files, tests, commands, docs actually inspected"],
91
+ "unconditional_approval_blockers": ["F1"]
92
+ }
93
+ ```
94
+
95
+ ### Evidence-Based Triage
96
+
97
+ For every finding:
98
+
99
+ - If correct: fix it, run the relevant local gates, update the verification
100
+ report, and start a new review round with a fresh exact change set.
101
+ - If disputed: respond to that reviewer through the gateway with code/doc/test
102
+ evidence. Do not rebut with assertion, intent, "by design", or "the code should
103
+ already do that" unless accompanied by the exact evidence. The reviewer must
104
+ withdraw the finding or provide counter-evidence.
105
+ - If unresolvable in scope: record it as a concrete `BLOCKER` with evidence and
106
+ stop advancing the change until the user decides scope.
107
+
108
+ "Approved with nits" is not unconditional approval. Fix the nits or get the
109
+ reviewer to withdraw them as non-blocking with evidence.
31
110
 
32
111
  ## Workflow
33
112
 
@@ -61,28 +140,30 @@ CLIs share Claude's tool names or MCP semantics. The same data is available as
61
140
 
62
141
  ### 2. Send Parallel Reviews
63
142
 
64
- Sync tools auto-defer at 45s if response contains `status:"deferred"`, poll `jobId` via `llm_job_status` every 60s, fetch with `llm_job_result`.
143
+ Sync tools auto-defer at 45s, but review gates should prefer async tools. Poll
144
+ `jobId` via `llm_job_status` no more than once every 90s, fetch with
145
+ `llm_job_result` only after a terminal status.
65
146
 
66
147
  **Tip — share the stable prefix across reviewers:** when the same long brief / file dump is sent to every reviewer, switch from `prompt` to the structured `promptParts` field. The gateway concatenates in canonical order `system → tools → context → task`, so every reviewer sees byte-identical stable prefix bytes, raising implicit cache hit rate at each provider. `prompt` and `promptParts` are mutually exclusive — the runtime returns `provide exactly one of \`prompt\` or \`promptParts\`` if both are supplied. After the round, read `cache-state://prefix/{hash}` (tokens/hashes only, no prompt text) to confirm reviewers actually shared the prefix.
67
148
 
68
149
  **Claude — Quality & Architecture:**
69
150
  ```
70
- claude_request({prompt:"Review changes in {path} for architecture, design patterns, maintainability, documentation gaps. Read the files directly. Specific line numbers and fixes. End with APPROVED or NOT APPROVED with findings.",approvalStrategy:"mcp_managed",optimizePrompt:true,optimizeResponse:true})
151
+ claude_request_async({prompt:"Review the attached evidence packet and exact change set for architecture, design patterns, maintainability, and documentation gaps. The packet is a claim, not evidence. Verify against the changed files, neighboring source, tests, and docs directly. Cite file:line/test/doc evidence. Return strict JSON and end with APPROVED, CHANGES_REQUIRED, or BLOCKER.",approvalStrategy:"mcp_managed",optimizePrompt:true})
71
152
  ```
72
153
 
73
154
  **Codex — Logic & Correctness:**
74
155
  ```
75
- codex_request({prompt:"Analyze {path} for logic bugs, off-by-one, missing error handling, race conditions, test gaps. Read the files directly. Severity: critical/high/medium/low. End with APPROVED or NOT APPROVED with findings.",fullAuto:true,approvalStrategy:"mcp_managed",optimizePrompt:true,optimizeResponse:true})
156
+ codex_request_async({prompt:"Review the attached evidence packet and exact change set for logic bugs, off-by-one errors, missing error handling, races, and test gaps. The packet is a claim, not evidence. Verify against code/tests/docs directly. Cite file:line/test/doc evidence. Return strict JSON and end with APPROVED, CHANGES_REQUIRED, or BLOCKER.",fullAuto:true,approvalStrategy:"mcp_managed",optimizePrompt:true})
76
157
  ```
77
158
 
78
159
  **Gemini — Security & Edge Cases:**
79
160
  ```
80
- gemini_request({prompt:"Security audit {path}: injection, auth bypasses, data leaks, OWASP Top 10, crash-causing edge cases. Read the files directly. End with APPROVED or NOT APPROVED with findings.",approvalStrategy:"mcp_managed",optimizePrompt:true,optimizeResponse:true})
161
+ gemini_request_async({prompt:"Review the attached evidence packet and exact change set for security issues: injection, auth bypasses, data leaks, OWASP Top 10, and crash-causing edge cases. The packet is a claim, not evidence. Verify against code/tests/docs/upstream docs directly. Cite evidence. Return strict JSON and end with APPROVED, CHANGES_REQUIRED, or BLOCKER.",approvalStrategy:"mcp_managed",optimizePrompt:true})
81
162
  ```
82
163
 
83
164
  **Grok — Independent Diversity (optional 4th reviewer):**
84
165
  ```
85
- grok_request({prompt:"Independent review of {path}. Read the files directly. Flag issues the other reviewers may have missed, contradict findings you disagree with, and call out blind spots. End with APPROVED or NOT APPROVED with findings.",approvalStrategy:"mcp_managed",optimizePrompt:true,optimizeResponse:true})
166
+ grok_request_async({prompt:"Independent review of the attached evidence packet and exact change set. The packet is a claim, not evidence. Verify against code/tests/docs directly, flag blind spots, and contradict weak findings with evidence. Return strict JSON and end with APPROVED, CHANGES_REQUIRED, or BLOCKER.",approvalStrategy:"mcp_managed",optimizePrompt:true})
86
167
  ```
87
168
 
88
169
  Add Grok when consensus matters (high-stakes changes, security-critical paths) or to break ties between the other three. Auth must already be set up (`grok login` OAuth or `GROK_CODE_XAI_API_KEY`).
@@ -122,7 +203,10 @@ grok_request_async({prompt:"Independent diversity review of all TS files in src/
122
203
  mistral_request_async({prompt:"Independent Vibe review of all TS files in src/... End with APPROVED or NOT APPROVED with findings.",approvalStrategy:"mcp_managed",correlationId:"review-mistral"})
123
204
  ```
124
205
 
125
- Poll with `llm_job_status` every 60s, retrieve with `llm_job_result` when terminal. Jobs are durable — if your polling wrapper times out, re-issue the same call (the gateway auto-dedups onto the live job) or fetch by `jobId` later (default 30-day retention).
206
+ Poll with `llm_job_status` no more than once every 90s, retrieve with
207
+ `llm_job_result` when terminal. Jobs are durable — if your polling wrapper times
208
+ out, re-issue the same call (the gateway auto-dedups onto the live job) or fetch
209
+ by `jobId` later (default 30-day retention).
126
210
 
127
211
  For iterative Gemini reviews, pass `sessionId` for resumability:
128
212
 
@@ -135,23 +219,34 @@ gemini_request_async({prompt:"Deep security audit of src/... End with APPROVED o
135
219
 
136
220
  These patterns undermine review quality and trigger review integrity warnings:
137
221
 
138
- - **Don't inline code in `<code>` blocks** provide file paths and let reviewers read files directly
222
+ - **Don't use non-stdio gateway surfaces** when the requested validation path is
223
+ the local stdio gateway.
224
+ - **Don't provide only a summary** — provide the verification report and exact
225
+ change set, while requiring reviewers to verify against files/tests/docs
226
+ directly.
227
+ - **Don't under-grant review access** — full read, test/build, and MCP
228
+ verification access is the default. A partial-access review is not a valid
229
+ approval unless the user explicitly accepts that limitation.
139
230
  - **Don't suppress tools** — never include "do not run tools" or "respond only based on code provided" in review prompts
140
231
  - **Don't use `allowedTools:[]`** for reviews — reviewers need tool access to
141
232
  verify claims
142
233
  - **Don't copy tool names between providers** — Claude's `Read` / `Grep` /
143
234
  `Glob` names are not Grok, Codex, Gemini, or Vibe allowlist names. Check
144
235
  `provider_tool_capabilities` first, or omit provider tool allowlists.
145
- - **Do provide file paths** `"Review changes in src/auth.ts"` instead of dumping file contents
236
+ - **Don't cancel reviewer jobs** just because they are slow. Let them reach a
237
+ terminal status unless the user explicitly says to stop them.
238
+ - **Do provide file paths and exact diff identifiers** — `"Review commit
239
+ abc123, diff range base..head, changed files src/auth.ts ..."` instead of
240
+ vague summaries.
146
241
 
147
242
  ## Iteration Loop (mandatory)
148
243
 
149
244
  Reviews are not one-shot. The caller runs this loop:
150
245
 
151
246
  1. Dispatch reviewer(s) with the verdict clause in the prompt
152
- 2. Poll every 60s if deferred; fetch result
247
+ 2. Poll every 90s if deferred; fetch result only after terminal status
153
248
  3. Parse verdict from each reviewer — APPROVED / NOT APPROVED / conditional
154
- 4. **Any NOT APPROVED or conditional** → consolidate findingsdispatch fixes (Codex + `fullAuto:true`) → re-dispatch same review → goto 2
249
+ 4. **Any NOT APPROVED, CHANGES_REQUIRED, BLOCKER, or conditional** → triage each finding with evidence fix or rebut with code/doc/test citations refresh verification report and exact diff → re-dispatch same review → goto 2
155
250
  5. **All APPROVED (unconditional)** → done
156
251
  6. After 3 rounds without convergence, escalate to the user
157
252
 
@@ -169,6 +264,6 @@ Reviews are not one-shot. The caller runs this loop:
169
264
  - Use all five async variants for true parallel reviews when you want Grok's
170
265
  independent perspective and Mistral Vibe's fifth review
171
266
  - Pass `sessionId` to `gemini_request_async` / `grok_request_async` for resumable follow-up
172
- - Check for `status:"deferred"` in sync responses — poll `jobId` every 60s if present
267
+ - Check for `status:"deferred"` in sync responses — poll `jobId` no more than once every 90s if present
173
268
  - Gateway `mcpServers` default to the host's configured Claude MCP set; pass explicit server names only when the review needs those capabilities
174
269
  - **Re-issuing after a polling timeout is safe** — auto-dedup (default 1 h window, `LLM_GATEWAY_DEDUP_WINDOW_MS`) reattaches the new call to the existing job. Use `forceRefresh:true` only when inputs genuinely changed
package/CHANGELOG.md CHANGED
@@ -2,7 +2,56 @@
2
2
 
3
3
  All notable changes to the llm-cli-gateway project.
4
4
 
5
- ## [Unreleased]
5
+ ## [2.13.1] - 2026-07-01: code-scanning hardening and review-gate discipline
6
+
7
+ ### Security
8
+
9
+ - **CodeQL remediation for URL, ReDoS, logging, and OAuth cookie alerts.**
10
+ API endpoint joining no longer uses slash-trimming regexes; ACP message
11
+ redaction now uses a stack-based single-pass JSON payload scanner with
12
+ truncated structured-payload fallback; central gateway stderr logging redacts
13
+ message strings, structured args, and nested `Error` name/message/stack
14
+ values; the xAI missing-key error no longer leaks the configured env var name;
15
+ and OAuth consent CSRF cookies now include `HttpOnly`, `Secure`,
16
+ `SameSite=Lax`, `Path=/oauth`, and `Max-Age=300`.
17
+ - **Regression coverage for the fixed alert surfaces.** Added focused tests for
18
+ endpoint slash normalization, nested and truncated ACP payload redaction,
19
+ logger redaction before stderr output, generic xAI missing-key messaging, and
20
+ complete OAuth CSRF cookie attributes.
21
+
22
+ ### Docs
23
+
24
+ - **Standardized cross-LLM review validation.** The shipped multi-LLM review
25
+ skill and best-practices guide now require the stdio gateway surface, no
26
+ provider model nomination, full non-interactive verification access,
27
+ 90-second polling, exact evidence packets, no cancellation of slow reviewers,
28
+ evidence-based rebuttals, and iteration until unconditional approval or a
29
+ concrete blocker.
30
+
31
+ ## [2.13.0] - 2026-07-01: Cursor Agent CLI provider
32
+
33
+ ### Added
34
+
35
+ - **Cursor Agent CLI (7th gateway provider).** `cursor_request` / `cursor_request_async`
36
+ drive `cursor-agent --print` headless mode with model selection, `plan`/`ask`
37
+ mode, output format, force/auto-review/sandbox/trust controls, workspace and
38
+ extra `--add-dir` roots, and session resume (`--resume`/`--continue`). Cursor
39
+ has no `promptParts` surface (plain `prompt` only, like Devin) and is not
40
+ wired for request-time MCP server injection because Cursor owns MCP config via
41
+ `cursor-agent mcp`. Covered by doctor, provider capability inventory,
42
+ provider-login guidance, upstream-contract drift detection, and CLI upgrade
43
+ support (`cursor-agent update`).
44
+ - **Cursor native ACP discovery and gated transport.** The installed Cursor Agent
45
+ CLI exposes a native `cursor-agent acp` stdio entrypoint; initialize plus
46
+ `session/new` smoke passed locally. Gateway request tools still default to the
47
+ CLI headless path. Explicit `transport:"acp"` stays config-gated and rejects
48
+ unsupported Cursor CLI-only controls instead of silently dropping them.
49
+ - **Cursor review hardening.** Cursor validation reviewers now run through
50
+ `cursor-agent --print --mode ask --sandbox enabled`; high-impact Cursor flags
51
+ (`force`, `trust`, `sandbox:"disabled"`) are gated under
52
+ `approvalStrategy:"mcp_managed"`; remote HTTP/OAuth workspace inputs must use
53
+ registered workspace aliases/roots; and docs/schema warn that gateway-created
54
+ `gw-*` ids are not resumable Cursor chat ids.
6
55
 
7
56
  ## [2.12.2] - 2026-07-01: backpressure hardening and release-gate stability
8
57
 
package/README.md CHANGED
@@ -9,9 +9,9 @@
9
9
  > _"Without consultation, plans are frustrated, but with many counselors they succeed."_
10
10
  > — Proverbs 15:22 (LSB)
11
11
 
12
- A Model Context Protocol (MCP) gateway for running Claude Code, Codex, Gemini, Grok, Mistral (Vibe), and Devin CLIs from one MCP endpoint, with durable async jobs, session continuity, cache-aware prompting, observability, and personal-appliance setup tooling.
12
+ A Model Context Protocol (MCP) gateway for running Claude Code, Codex, Gemini, Grok, Mistral (Vibe), Devin, and Cursor Agent CLIs from one MCP endpoint, with durable async jobs, session continuity, cache-aware prompting, observability, and personal-appliance setup tooling.
13
13
 
14
- **Why developers try it:** one local MCP endpoint for cross-LLM validation, multi-agent coding workflows, and repeatable assistant-led setup across six provider CLIs.
14
+ **Why developers try it:** one local MCP endpoint for cross-LLM validation, multi-agent coding workflows, and repeatable assistant-led setup across registered provider CLIs.
15
15
 
16
16
  **Current signals:** CI and security workflows pass on `main`, OpenSSF Scorecard is published, OpenSSF Best Practices is passing, releases use Sigstore signing, and the package is MIT licensed.
17
17
 
@@ -38,7 +38,7 @@ Or use directly with `npx` from an MCP client:
38
38
 
39
39
  `llm-cli-gateway` is a single-user MCP gateway for cross-LLM validation and multi-agent coding workflows. It is more than a thin CLI wrapper:
40
40
 
41
- - Runs six provider CLIs through consistent sync and async MCP tools.
41
+ - Runs registered provider CLIs through consistent sync and async MCP tools.
42
42
  - Persists long-running jobs, supports restart-safe result collection, deduplication, cancellation, and sync-to-async deferral.
43
43
  - Tracks sessions, real CLI resume paths, structured response metadata, and cache telemetry.
44
44
  - Supports cache-aware `promptParts`, including explicit Claude `cache_control` when opted in.
@@ -157,7 +157,7 @@ docker compose -f docker/personal.compose.yml run --rm doctor
157
157
 
158
158
  ### Core Capabilities
159
159
 
160
- - **Multi-LLM Orchestration**: Unified interface for Claude Code, Codex, Gemini, Grok, Mistral (Vibe), and Devin CLIs
160
+ - **Multi-LLM Orchestration**: Unified interface for Claude Code, Codex, Gemini, Grok, Mistral (Vibe), Devin, and Cursor Agent CLIs
161
161
  - **Session Management**: Track and resume conversations across all CLIs with persistent storage
162
162
  - **Gateway-owned worktrees**: Run any sync or async provider request inside a managed git worktree, with per-session reuse and cleanup
163
163
  - **Token Optimization**: Automatic 44% reduction on prompts, 37% on responses (opt-in)
@@ -169,11 +169,11 @@ docker compose -f docker/personal.compose.yml run --rm doctor
169
169
  - **SQLite Flight Recorder**: Every request/response logged to `~/.llm-cli-gateway/logs.db` with correlation IDs, token usage, duration, retry counts, and circuit breaker state. Browse with [Datasette](https://datasette.io/): `datasette ~/.llm-cli-gateway/logs.db`
170
170
  - **Structured Metadata**: Tool responses include machine-readable `structuredContent` (model, cli, correlationId, sessionId, durationMs, token counts)
171
171
  - **Cache observability resources**: `cache-state://global`, `cache-state://session/{id}`, and `cache-state://prefix/{hash}` MCP resources return aggregate cache hit/miss/savings — tokens and hashes only, no prompt text. `session_get` includes a `cacheState` block when the session has prior requests.
172
- - **Provider capability inventory**: `provider_tool_capabilities` and `provider-tools://catalog` expose the gateway request fields, supported/degraded provider controls, local skill/tool discovery, and safe config-surface hints for Claude Code, Codex CLI, Gemini/Antigravity, Grok CLI/API, Mistral Vibe, and Cognition Devin. `doctor --json` includes a compact `provider_capabilities` summary for setup assistants.
172
+ - **Provider capability inventory**: `provider_tool_capabilities` and `provider-tools://catalog` expose the gateway request fields, supported/degraded provider controls, local skill/tool discovery, and safe config-surface hints for Claude Code, Codex CLI, Gemini/Antigravity, Grok CLI/API, Mistral Vibe, Cognition Devin, and Cursor Agent. `doctor --json` includes a compact `provider_capabilities` summary for setup assistants.
173
173
 
174
174
  ### Cache-aware operation
175
175
 
176
- Every `*_request` and `*_request_async` tool except `devin_request` / `devin_request_async` accepts an optional `promptParts` field that structures the prompt for better cache hit rates (the Devin headless path takes a plain `prompt` only). The gateway concatenates the parts in canonical order (`system → tools → context → task`) so that the stable prefix bytes precede the volatile task tail unchanged across calls, letting each provider's automatic prompt-caching land on the same content hash each time.
176
+ Every `*_request` and `*_request_async` tool except `devin_request` / `devin_request_async` and `cursor_request` / `cursor_request_async` accepts an optional `promptParts` field that structures the prompt for better cache hit rates (the Devin and Cursor headless paths take a plain `prompt` only). The gateway concatenates the parts in canonical order (`system → tools → context → task`) so that the stable prefix bytes precede the volatile task tail unchanged across calls, letting each provider's automatic prompt-caching land on the same content hash each time.
177
177
 
178
178
  ```json
179
179
  {
@@ -188,7 +188,7 @@ Every `*_request` and `*_request_async` tool except `devin_request` / `devin_req
188
188
 
189
189
  `prompt` and `promptParts` are mutually exclusive — pass exactly one.
190
190
 
191
- Per-CLI capability matrix (prefix discipline is automatic via `promptParts` for all providers except Devin, which has no `promptParts` surface; explicit levers are provider-specific):
191
+ Per-CLI capability matrix (prefix discipline is automatic via `promptParts` for all providers except Devin and Cursor, which have no `promptParts` surface; explicit levers are provider-specific):
192
192
 
193
193
  | CLI | Prefix discipline | Explicit lever(s) |
194
194
  | ------- | ----------------- | ----------------- |
@@ -197,6 +197,8 @@ Per-CLI capability matrix (prefix discipline is automatic via `promptParts` for
197
197
  | gemini | yes | none (implicit server-side) |
198
198
  | grok | yes | `compactionMode` / `compactionDetail` (context compaction: `summary|transcript|segments`; `segments` writes per-segment markdown) |
199
199
  | mistral | yes | none (implicit) |
200
+ | devin | no | plain `prompt` only |
201
+ | cursor | no | plain `prompt` only |
200
202
 
201
203
  **Claude example (explicit cacheControl)**
202
204
 
@@ -960,9 +962,36 @@ Run a Cognition Devin CLI request synchronously (headless print mode, `devin -p`
960
962
  - `idleTimeoutMs` (integer, optional): Kill a stuck process after output inactivity; 30,000 to 3,600,000 ms
961
963
  - `forceRefresh` (boolean, optional): Bypass dedup and force a fresh CLI run, default: false
962
964
 
963
- ##### `claude_request_async` / `codex_request_async` / `gemini_request_async` / `grok_request_async` / `mistral_request_async` / `devin_request_async`
965
+ ##### `cursor_request`
964
966
 
965
- Start a long-running Claude, Codex, Gemini, Grok, Mistral, or Devin request without waiting for completion in the same MCP call.
967
+ Run a Cursor Agent CLI request synchronously. Defaults to headless print mode (`cursor-agent --print`) and auto-defers to a pollable job past the sync deadline when async jobs are enabled. Set `transport: "acp"` to use Cursor's native `cursor-agent acp` transport when `[acp]` and `[acp.providers.cursor].runtime_enabled` are enabled; current ACP routing accepts only prompt/model/session inputs and rejects CLI-only options such as `mode`, `workspace`, `sandbox`, `force`, and `trust`.
968
+
969
+ **Parameters:**
970
+
971
+ - `prompt` (string, required): Prompt text for Cursor Agent CLI (1-100,000 chars)
972
+ - `model` (string, optional): Model name or alias (for example `gpt-5`, `sonnet-4-thinking`, or `latest`)
973
+ - `mode` (string, optional): Cursor mode, `"plan"` or `"ask"` (`--mode`)
974
+ - `outputFormat` (string, optional): `"text"` (default), `"json"`, or `"stream-json"`
975
+ - `transport` (string, optional): `"cli"` (default) or `"acp"`; ACP fails closed unless enabled in gateway config and rejects unsupported Cursor CLI-only controls instead of dropping them
976
+ - `force` (boolean, optional): Emit `--force` for non-interactive operation
977
+ - `autoReview` (boolean, optional): Emit `--auto-review`
978
+ - `sandbox` (string, optional): `"enabled"` or `"disabled"` (`--sandbox`)
979
+ - `trust` (boolean, optional): Emit `--trust` for this invocation
980
+ - `workspace` (string, optional): Cursor workspace path or name (`--workspace`); remote HTTP/OAuth callers must pass a registered workspace alias, while local stdio callers may pass paths
981
+ - `addDir` (string[], optional): Additional workspace roots (one `--add-dir` per entry); remote HTTP/OAuth callers must use registered workspace roots
982
+ - `sessionId` (string, optional): Cursor chat/session ID to resume (`--resume <id>`). The `gw-*` id minted for a brand-new gateway session is not resumable through `sessionId`; continue with `resumeLatest: true`
983
+ - `resumeLatest` (boolean, optional): Resume the most recent Cursor chat (`--continue`)
984
+ - `createNewSession` (boolean, optional): Force a new session
985
+ - `approvalStrategy` (string, optional): `"legacy"` (default) or `"mcp_managed"`; under MCP-managed approval, high-impact Cursor flags (`force`, `trust`, or `sandbox: "disabled"`) are denied unless bypass approval is explicitly allowed
986
+ - `approvalPolicy` (string, optional): `"strict"`, `"balanced"`, or `"permissive"` override for MCP-managed approval
987
+ - `optimizePrompt` / `optimizeResponse` (boolean, optional): Token-efficiency optimisation, default: false
988
+ - `correlationId` (string, optional): Request trace ID (auto-generated if omitted)
989
+ - `idleTimeoutMs` (integer, optional): Kill a stuck process after output inactivity; 30,000 to 3,600,000 ms
990
+ - `forceRefresh` (boolean, optional): Bypass dedup and force a fresh CLI run, default: false
991
+
992
+ ##### `claude_request_async` / `codex_request_async` / `gemini_request_async` / `grok_request_async` / `mistral_request_async` / `devin_request_async` / `cursor_request_async`
993
+
994
+ Start a long-running Claude, Codex, Gemini, Grok, Mistral, Devin, or Cursor request without waiting for completion in the same MCP call.
966
995
 
967
996
  Use this flow when analysis/runtime can exceed client tool-call limits:
968
997
 
@@ -1021,7 +1050,7 @@ Return the gateway's declared provider CLI contracts, optionally probing the ins
1021
1050
 
1022
1051
  **Parameters:**
1023
1052
 
1024
- - `cli` (string, optional): Filter (`claude|codex|gemini|grok|mistral|devin`)
1053
+ - `cli` (string, optional): Filter (`claude|codex|gemini|grok|mistral|devin|cursor`)
1025
1054
  - `probeInstalled` (boolean, optional, default `false`): Run local `--help` probes and compare advertised flags against the declared contract — strongly recommended after any provider CLI upgrade. The probe reports `missingFlags`, `extraFlags`, `acknowledgedExtraFlags` (known upstream-only flags filtered from `extraFlags`), `discoveredFlags`, and stale-marker `warnings`.
1026
1055
 
1027
1056
  #### Session Management Tools
@@ -1032,7 +1061,7 @@ Create a new session for a specific CLI.
1032
1061
 
1033
1062
  **Parameters:**
1034
1063
 
1035
- - `cli` (string, required): CLI to create session for ("claude", "codex", "gemini", "grok", "mistral", "devin")
1064
+ - `cli` (string, required): CLI to create session for ("claude", "codex", "gemini", "grok", "mistral", "devin", "cursor")
1036
1065
  - `description` (string, optional): Description for the session
1037
1066
  - `setAsActive` (boolean, optional): Set as active session, default: true
1038
1067
 
@@ -1052,7 +1081,7 @@ List all sessions, optionally filtered by CLI.
1052
1081
 
1053
1082
  **Parameters:**
1054
1083
 
1055
- - `cli` (string, optional): Filter by CLI ("claude", "codex", "gemini", "grok", "mistral", "devin")
1084
+ - `cli` (string, optional): Filter by CLI ("claude", "codex", "gemini", "grok", "mistral", "devin", "cursor")
1056
1085
 
1057
1086
  **Response includes:**
1058
1087
 
@@ -1101,7 +1130,7 @@ List available models for each CLI.
1101
1130
 
1102
1131
  **Parameters:**
1103
1132
 
1104
- - `cli` (string, optional): Specific provider to list models for (`"claude"`, `"codex"`, `"gemini"`, `"grok"`, `"mistral"`, `"devin"`, or an enabled API provider name). When one or more `[providers.<name>]` API providers are enabled, the unfiltered response also carries an `apiProviders` array (each entry tagged `providerKind: "api"`); see [API providers (HTTP)](#api-providers-http).
1133
+ - `cli` (string, optional): Specific provider to list models for (`"claude"`, `"codex"`, `"gemini"`, `"grok"`, `"mistral"`, `"devin"`, `"cursor"`, or an enabled API provider name). When one or more `[providers.<name>]` API providers are enabled, the unfiltered response also carries an `apiProviders` array (each entry tagged `providerKind: "api"`); see [API providers (HTTP)](#api-providers-http).
1105
1134
 
1106
1135
  **Response includes:**
1107
1136
 
@@ -1149,7 +1178,7 @@ inputs.
1149
1178
 
1150
1179
  **Parameters:**
1151
1180
 
1152
- - `cli` (string, optional): Provider filter (`"claude"`, `"codex"`, `"gemini"`, `"grok"`, `"mistral"`, `"devin"`, `"grok_api"`, or an enabled API provider name)
1181
+ - `cli` (string, optional): Provider filter (`"claude"`, `"codex"`, `"gemini"`, `"grok"`, `"mistral"`, `"devin"`, `"cursor"`, `"grok_api"`, or an enabled API provider name)
1153
1182
  - `includeSkills` (boolean, default `true`): Include bounded local skill discovery
1154
1183
  - `includeProviderTools` (boolean, default `true`): Include provider-native tools extracted from discovered skills
1155
1184
  - `includeUnsupported` (boolean, default `true`): Include explicit unsupported/degraded input records
@@ -1170,6 +1199,7 @@ Equivalent MCP resources:
1170
1199
  - `provider-tools://grok_api`
1171
1200
  - `provider-tools://mistral`
1172
1201
  - `provider-tools://devin`
1202
+ - `provider-tools://cursor`
1173
1203
  - `provider-tools://<api-provider>` for each enabled `[providers.<name>]` API provider
1174
1204
 
1175
1205
  `doctor --json` also emits a compact `provider_capabilities` block with the
@@ -1186,7 +1216,7 @@ Report installed CLI versions.
1186
1216
 
1187
1217
  **Parameters:**
1188
1218
 
1189
- - `cli` (string, optional): Specific CLI to inspect ("claude", "codex", "gemini", "grok", "mistral", "devin")
1219
+ - `cli` (string, optional): Specific CLI to inspect ("claude", "codex", "gemini", "grok", "mistral", "devin", "cursor")
1190
1220
 
1191
1221
  ##### `cli_upgrade`
1192
1222
 
@@ -1194,7 +1224,7 @@ Plan or run an upgrade for one CLI.
1194
1224
 
1195
1225
  **Parameters:**
1196
1226
 
1197
- - `cli` (string, required): CLI to upgrade ("claude", "codex", "gemini", "grok", "mistral", "devin")
1227
+ - `cli` (string, required): CLI to upgrade ("claude", "codex", "gemini", "grok", "mistral", "devin", "cursor")
1198
1228
  - `target` (string, optional): Package tag/version/target, default: `latest`
1199
1229
  - `dryRun` (boolean, optional): Return the upgrade plan without running it, default: `true`
1200
1230
  - `timeoutMs` (number, optional): Upgrade timeout when `dryRun=false`
@@ -1210,6 +1240,7 @@ Plan or run an upgrade for one CLI.
1210
1240
  - Grok explicit target: `grok update --version <target>`
1211
1241
  - Mistral (Vibe): dispatches to the detected installer (`pip`/`uv`/`brew`); errors with guidance when none is detected (Vibe ships no self-update command)
1212
1242
  - Devin latest: `devin update` (self-update; explicit version targets are unsupported)
1243
+ - Cursor latest: `cursor-agent update` (self-update; explicit version targets are unsupported)
1213
1244
 
1214
1245
  **Example dry run:**
1215
1246
 
@@ -1227,7 +1258,7 @@ In addition to the spawnable CLI tools, the gateway can route requests to first-
1227
1258
 
1228
1259
  ### Configuring a provider
1229
1260
 
1230
- API providers are declared as `[providers.<name>]` blocks in `~/.llm-cli-gateway/config.toml` (override with `LLM_GATEWAY_CONFIG`). The `<name>` becomes the provider's identity across every tool and resource and **must not** collide with a spawnable CLI name (`claude`, `codex`, `gemini`, `grok`, `mistral`, `devin`); a collision is rejected with a warning and the provider is disabled.
1261
+ API providers are declared as `[providers.<name>]` blocks in `~/.llm-cli-gateway/config.toml` (override with `LLM_GATEWAY_CONFIG`). The `<name>` becomes the provider's identity across every tool and resource and **must not** collide with a spawnable CLI name (`claude`, `codex`, `gemini`, `grok`, `mistral`, `devin`, `cursor`); a collision is rejected with a warning and the provider is disabled.
1231
1262
 
1232
1263
  ```toml
1233
1264
  # OpenRouter (OpenAI-compatible). The key is read from the named env var at
@@ -1,7 +1,5 @@
1
1
  export function redactAcpMessage(input) {
2
- let out = input;
3
- out = out.replace(/\{[\s\S]*?\}/g, "<redacted-json>");
4
- out = out.replace(/\[[\s\S]*?\]/g, "<redacted-json>");
2
+ let out = redactJsonLikeBodies(input);
5
3
  out = out.replace(/\b(bearer|token|api[_-]?key|secret)\b\s*[:=]?\s*\S+/gi, "$1 <redacted>");
6
4
  out = out.replace(/\b(sk|xai|gsk|key)-[A-Za-z0-9_-]{8,}\b/gi, "<redacted-token>");
7
5
  out = out.replace(/(^|[^A-Za-z0-9._~\\/-])[A-Za-z]:\\[^\s"')\]}>]*/g, "$1<redacted-path>");
@@ -11,6 +9,64 @@ export function redactAcpMessage(input) {
11
9
  out = out.replace(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi, "<redacted-email>");
12
10
  return out;
13
11
  }
12
+ function redactJsonLikeBodies(input) {
13
+ let out = "";
14
+ let cursor = 0;
15
+ let bodyStart = -1;
16
+ let stack = [];
17
+ let inString = false;
18
+ let escaped = false;
19
+ for (let index = 0; index < input.length; index += 1) {
20
+ const char = input.charAt(index);
21
+ if (bodyStart === -1) {
22
+ const close = char === "{" ? "}" : char === "[" ? "]" : "";
23
+ if (!close)
24
+ continue;
25
+ bodyStart = index;
26
+ stack = [close];
27
+ inString = false;
28
+ escaped = false;
29
+ continue;
30
+ }
31
+ if (escaped) {
32
+ escaped = false;
33
+ continue;
34
+ }
35
+ if (char === "\\") {
36
+ escaped = inString;
37
+ continue;
38
+ }
39
+ if (char === '"') {
40
+ inString = !inString;
41
+ continue;
42
+ }
43
+ if (inString)
44
+ continue;
45
+ const nestedClose = char === "{" ? "}" : char === "[" ? "]" : "";
46
+ if (nestedClose) {
47
+ stack.push(nestedClose);
48
+ continue;
49
+ }
50
+ if (char !== stack[stack.length - 1])
51
+ continue;
52
+ stack.pop();
53
+ if (stack.length > 0)
54
+ continue;
55
+ out += input.slice(cursor, bodyStart);
56
+ out += "<redacted-json>";
57
+ cursor = index + 1;
58
+ bodyStart = -1;
59
+ }
60
+ if (bodyStart !== -1 && isLikelyJsonPayload(input.slice(bodyStart))) {
61
+ out += input.slice(cursor, bodyStart);
62
+ out += "<redacted-json>";
63
+ cursor = input.length;
64
+ }
65
+ return cursor === 0 ? input : out + input.slice(cursor);
66
+ }
67
+ function isLikelyJsonPayload(span) {
68
+ return /"(?:jsonrpc|method|params|prompt|content|body|token|secret|api[_-]?key|credential|auth|cwd|path|[A-Za-z0-9_-]+)"\s*:/i.test(span);
69
+ }
14
70
  export function redactAcpDebug(value) {
15
71
  const sensitiveKey = /(payload|body|prompt|content|token|secret|api[_-]?key|credential|auth|cwd|path)/i;
16
72
  if (typeof value === "string") {
@@ -77,6 +77,19 @@ const ACP_PROVIDER_REGISTRY = Object.freeze({
77
77
  adapterCandidates: Object.freeze([]),
78
78
  caveat: "Native ACP entrypoint `devin acp` (stdio JSON-RPC). Manual initialize + session/new smoke passed with the installed CLI managing credentials (`devin auth login`; WINDSURF_API_KEY for empty-env); empty-env smoke is expected to fail. Third native runtime pilot; runtime routing stays config-gated.",
79
79
  }),
80
+ cursor: Object.freeze({
81
+ provider: "cursor",
82
+ displayName: "Cursor Agent CLI",
83
+ status: "native_smoke_passed",
84
+ supportKind: "native",
85
+ targetVersion: "cursor-agent 2026.06.29-2ad2186",
86
+ entrypoint: Object.freeze({ command: "cursor-agent", args: Object.freeze(["acp"]) }),
87
+ runtimeEnabledDefault: false,
88
+ shipRuntimePilot: true,
89
+ runtimePriority: 4,
90
+ adapterCandidates: Object.freeze([]),
91
+ caveat: "Native ACP entrypoint `cursor-agent acp` (stdio JSON-RPC) is available as a hidden advanced command. Manual initialize + session/new smoke passed locally (protocolVersion 1, session created; no agentInfo returned). Fourth native runtime pilot; runtime routing stays config-gated.",
92
+ }),
80
93
  });
81
94
  export function getAcpProviderRegistry() {
82
95
  return ACP_PROVIDER_REGISTRY;
package/dist/api-http.js CHANGED
@@ -40,8 +40,8 @@ export function isLoopbackUrl(value) {
40
40
  }
41
41
  }
42
42
  export function buildEndpointUrl(baseUrl, path) {
43
- const trimmedBase = baseUrl.replace(/\/+$/, "");
44
- const trimmedPath = path.replace(/^\/+/, "");
43
+ const trimmedBase = stripTrailingSlashChars(baseUrl);
44
+ const trimmedPath = stripLeadingSlashChars(path);
45
45
  const url = new URL(`${trimmedBase}/${trimmedPath}`);
46
46
  if (url.protocol !== "https:" &&
47
47
  !(url.protocol === "http:" && LOOPBACK_HOSTS.includes(url.hostname))) {
@@ -49,6 +49,20 @@ export function buildEndpointUrl(baseUrl, path) {
49
49
  }
50
50
  return url;
51
51
  }
52
+ function stripTrailingSlashChars(value) {
53
+ let end = value.length;
54
+ while (end > 0 && value.charCodeAt(end - 1) === 47) {
55
+ end -= 1;
56
+ }
57
+ return value.slice(0, end);
58
+ }
59
+ function stripLeadingSlashChars(value) {
60
+ let start = 0;
61
+ while (start < value.length && value.charCodeAt(start) === 47) {
62
+ start += 1;
63
+ }
64
+ return value.slice(start);
65
+ }
52
66
  export function isHttpTransient(error) {
53
67
  const status = typeof error?.status === "number" ? error.status : null;
54
68
  if (status === 429 || (status !== null && status >= 500))
@@ -2,7 +2,7 @@ import type { Logger } from "./logger.js";
2
2
  import type { ReviewIntegrityResult } from "./review-integrity.js";
3
3
  export type ApprovalPolicy = "strict" | "balanced" | "permissive";
4
4
  export type ApprovalStrategy = "legacy" | "mcp_managed";
5
- export type ApprovalCli = "claude" | "codex" | "gemini" | "grok" | "mistral" | "devin";
5
+ export type ApprovalCli = "claude" | "codex" | "gemini" | "grok" | "mistral" | "devin" | "cursor";
6
6
  export type ApprovalStatus = "approved" | "denied";
7
7
  export interface ApprovalRequest {
8
8
  cli: ApprovalCli;
@@ -7,7 +7,7 @@ import { type ApiProvider, type ApiRequest, type ApiUsage } from "./api-provider
7
7
  import { type JobLimitsConfig } from "./config.js";
8
8
  export declare function extractApiHttpStatus(error: unknown): number | null;
9
9
  export declare function extractApiErrorBody(error: unknown): string | undefined;
10
- export type LlmCli = "claude" | "codex" | "gemini" | "grok" | "mistral" | "devin";
10
+ export type LlmCli = "claude" | "codex" | "gemini" | "grok" | "mistral" | "devin" | "cursor";
11
11
  export type JobProvider = LlmCli | (string & {});
12
12
  export type AsyncJobStatus = "queued" | "running" | "completed" | "failed" | "canceled" | "orphaned";
13
13
  export declare function isAsyncJobInProgress(status: AsyncJobStatus): boolean;
@@ -1,5 +1,5 @@
1
1
  import type { Logger } from "./logger.js";
2
- import type { CliType } from "./session-manager.js";
2
+ import { type CliType } from "./provider-types.js";
3
3
  import { type ProviderLoginStatus } from "./provider-status.js";
4
4
  import type { ProviderLoginGuidance } from "./provider-login-guidance.js";
5
5
  export interface CliVersionInfo {