llm-cli-gateway 2.11.1 → 2.12.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.
@@ -0,0 +1,100 @@
1
+ ---
2
+ name: public-demo-session
3
+ description: Prepare and drive clean public llm-cli-gateway demo sessions for recordings, README demos, screenshots, or transcripts. Use when the user needs a redacted path, compact Codex output, direct provider calls, or a demo-safe Codex environment.
4
+ metadata:
5
+ author: verivus-oss
6
+ version: "1.0"
7
+ ---
8
+
9
+ # Public Demo Session
10
+
11
+ Use this skill when the goal is a public recording, README demo, screenshot, or clean transcript. The priority is a short, reliable demo surface, not exhaustive orchestration.
12
+
13
+ ## Goals
14
+
15
+ - Show only a public-safe workspace path such as `/llm-cli-gateway`.
16
+ - Keep the llm-cli-gateway MCP server and workspace skills available.
17
+ - Prefer direct, synchronous provider calls that return compact text.
18
+ - Avoid validation wrappers, async polling, nested Codex, and repeated stuck-job polling unless the user explicitly asks for those features.
19
+
20
+ ## Path Hygiene
21
+
22
+ Do not start Codex directly from an internal checkout path for public demos. A symlink may still leak a resolved path in some tools. Prefer a bind-mounted public path:
23
+
24
+ ```bash
25
+ bwrap \
26
+ --bind "$REAL_REPO" /llm-cli-gateway \
27
+ --chdir /llm-cli-gateway \
28
+ --setenv CODEX_HOME "$DEMO_CODEX_HOME" \
29
+ codex --dangerously-bypass-approvals-and-sandbox
30
+ ```
31
+
32
+ If the host needs Codex HTTPS/WebSocket support inside the mount namespace, bind the host CA backing store too. On this host `/etc/ssl/certs` depends on `/var/lib/ca-certificates`.
33
+
34
+ The demo `CODEX_HOME` should have a minimal `config.toml` with:
35
+
36
+ ```toml
37
+ model = "gpt-5.5"
38
+ model_reasoning_effort = "high"
39
+ project_doc_max_bytes = 0
40
+
41
+ [projects."/llm-cli-gateway"]
42
+ trust_level = "trusted"
43
+
44
+ [mcp_servers.llm-gateway]
45
+ command = "node"
46
+ args = ["/llm-cli-gateway/dist/index.js"]
47
+ ```
48
+
49
+ Keep the demo home separate from the normal Codex home. Symlink `auth.json` from the normal Codex home if needed, but do not copy private history or sessions.
50
+
51
+ ## Demo Dispatch Defaults
52
+
53
+ For simple “ask the other LLMs” demo prompts, use direct provider tools:
54
+
55
+ ```js
56
+ claude_request({prompt: "...", outputFormat: "text"})
57
+ gemini_request({prompt: "...", outputFormat: "text", skipTrust: true})
58
+ mistral_request({prompt: "...", outputFormat: "text", trust: true})
59
+ ```
60
+
61
+ Provider-specific notes:
62
+
63
+ - Do not pass `maxTurns` to `claude_request` unless the current schema explicitly supports it.
64
+ - Pass `skipTrust:true` to Gemini when the demo workspace trust prompt would interrupt the recording.
65
+ - Pass `trust:true` to Mistral/Vibe for the same reason.
66
+ - Avoid `grok_request` in demos unless the user explicitly names Grok and the local Grok CLI has been checked in this environment.
67
+ - Avoid `codex_request` from inside a Codex demo unless the user explicitly asks for nested Codex.
68
+
69
+ ## Avoid In Demo Mode
70
+
71
+ Do not use these by default for a public demo prompt:
72
+
73
+ - `ask_model`
74
+ - `validate_with_models`
75
+ - `compare_answers`
76
+ - `consensus_check`
77
+ - `second_opinion`
78
+ - `red_team_review`
79
+ - `synthesize_validation`
80
+ - `*_request_async`
81
+ - `llm_job_status`, `llm_job_result`, `llm_job_cancel`
82
+ - web search, unless the user explicitly asks for live verification
83
+
84
+ If a provider fails, state that briefly and continue with useful returned outputs. Do not turn a short demo into a recovery workflow.
85
+
86
+ ## Quick Verification
87
+
88
+ Before recording:
89
+
90
+ ```bash
91
+ codex doctor --summary --no-color
92
+ ```
93
+
94
+ Expected: MCP server present, WebSocket connected, provider endpoints reachable, and `0 warn · 0 fail`.
95
+
96
+ Confirm the opening screen shows:
97
+
98
+ ```text
99
+ directory: /llm-cli-gateway
100
+ ```
@@ -0,0 +1,227 @@
1
+ ---
2
+ name: secure-orchestration
3
+ description: Security-conscious LLM orchestration with approval gates across Claude, Codex, Gemini, Grok, and Mistral. Use for high-risk operations, permissions, auditing.
4
+ metadata:
5
+ author: verivus-oss
6
+ version: "1.6"
7
+ ---
8
+
9
+ # Secure Orchestration
10
+
11
+ Approval gate scores request risk, enforces policy thresholds. Applies uniformly to Claude, Codex, Gemini, Grok (xAI), and Mistral Vibe dispatches. Use when security matters — production codebases, sensitive data, autonomous operations.
12
+
13
+ > **Mistral Vibe note**: the gateway always emits `--agent <mode>` explicitly and defaults the programmatic mode to `auto-approve`. Set `permissionMode:"plan"` (or `chat`/`explore`) when you want stricter behaviour. Current Vibe defaults session logging on; `doctor --json` surfaces explicit `[session_logging] enabled = false` as a `next_actions` entry.
14
+
15
+ ## Dispatch Defaults
16
+
17
+ Apply these on every dispatch unless the caller has explicitly overridden a rule in the current turn:
18
+
19
+ 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.
20
+ 2. **`approvalStrategy:"mcp_managed"`** is the skill dispatch default (the gateway schema default is `"legacy"`). It runs the scored gateway gate first; Claude then uses `bypassPermissions`, Gemini uses `yolo`, and Codex still needs `fullAuto:true` for autonomous file/shell work. **The `mcp_managed` auto-flip itself is not scored as raw bypass; only caller-supplied raw bypass flags incur the +3 permission-bypass penalty below.** Raw `dangerouslySkipPermissions` / `dangerouslyBypassApprovalsAndSandbox` / caller-set `approvalMode:"yolo"` remain prohibited in production because they bypass the gateway gate entirely.
21
+ 3. **No wallclock timeout; poll every 60 s** — good security reviews take minutes to tens of minutes. `idleTimeoutMs` (no-output safeguard) remains a valid security control and is separate from wallclock timeout.
22
+ 4. **Iterate until unconditional APPROVED** (review dispatches only) — end every review prompt with "End with APPROVED or NOT APPROVED with findings." Loop: dispatch → parse verdict → on `NOT APPROVED` or conditional approval, dispatch fixes + re-review → repeat until unconditional APPROVED. Escalate after 3 rounds. This rule does **not** apply to pure implementation or non-review analysis dispatches.
23
+
24
+ ## Risk Scoring
25
+
26
+ | Factor | Points | Trigger |
27
+ |--------|--------|---------|
28
+ | Permission bypass | +3 | Raw caller-supplied bypass: `dangerouslySkipPermissions:true`, `dangerouslyBypassApprovalsAndSandbox:true`, or caller-set Gemini `approvalMode:"yolo"`. The `approvalStrategy:"mcp_managed"` auto-flip is **not** scored here. |
29
+ | Sensitive keywords | +3 | `delete`, `destroy`, `wipe`, `exfiltrate`, `credential`, `token`, `password`, `secret` |
30
+ | Full auto | +2 | `fullAuto:true` (Codex) |
31
+ | Bypass + full-auto | +2 | Both requested together |
32
+ | Weighted MCP server | varies | Per-server approval weights from the host's MCP registry (e.g. a web-search server) add to the score |
33
+ | Review tool suppression | +4 | Tool-suppression language detected in review context (e.g., "do not run tools") |
34
+ | Empty allowedTools (review) | +6 | `allowedTools:[]` in review context — reviewers need tool access |
35
+ | Critical tools disallowed (review) | +6 | Review context with `Read`, `Grep`, `Glob`, or `Bash` in `disallowedTools` |
36
+ | Ref tools MCP | +1 | Reference tools access |
37
+ | Empty allowedTools (non-review) | +0 | Recorded as "No tool permissions requested"; does not lower score |
38
+ | Explicit disallowedTools (non-critical/non-review) | +0 | Recorded as a restriction; does not lower score |
39
+
40
+ ## Policy Thresholds
41
+
42
+ | Policy | Max Score | Use When |
43
+ |--------|-----------|----------|
44
+ | `strict` | 2 | Production, security-sensitive, untrusted prompts |
45
+ | `balanced` | 5 | Normal development, trusted prompts |
46
+ | `permissive` | 7 | Experimentation, sandboxed environments |
47
+
48
+ Score > threshold → **denied**. Default: `balanced` (override: `LLM_GATEWAY_APPROVAL_POLICY` env var).
49
+
50
+ ## Enabling Approval Gates
51
+
52
+ ### Per-request
53
+
54
+ ```
55
+ claude_request({prompt:"Refactor auth module",approvalStrategy:"mcp_managed",approvalPolicy:"strict"})
56
+ ```
57
+
58
+ Response includes:
59
+ ```json
60
+ {"approval":{"id":"appr-...","status":"approved","score":0,"policy":"strict","reasons":[]}}
61
+ ```
62
+
63
+ ### Denied example
64
+
65
+ ```
66
+ codex_request({prompt:"Delete all test fixtures",fullAuto:true,dangerouslyBypassApprovalsAndSandbox:true,approvalStrategy:"mcp_managed",approvalPolicy:"strict"})
67
+ ```
68
+
69
+ Score: bypass(+3) + full-auto(+2) + combo(+2) + "delete"(+3) = **10** > strict threshold 2. Request NOT executed.
70
+
71
+ ### Async requests
72
+
73
+ Approval check happens before job spawn:
74
+
75
+ ```
76
+ gemini_request_async({prompt:"Audit auth module for vulnerabilities. End with APPROVED or NOT APPROVED with findings.",approvalStrategy:"mcp_managed",approvalPolicy:"strict"})
77
+ ```
78
+
79
+ Approved → job starts, get `job.id` to poll (every 60s per dispatch defaults). Denied → no job created, denial returned.
80
+
81
+ ### mcp_managed CLI effects
82
+
83
+ When `approvalStrategy:"mcp_managed"`:
84
+ - Claude: `--permission-mode bypassPermissions`
85
+ - Gemini: `--approval-mode yolo`
86
+ - Codex: no automatic bypass flag; use `fullAuto:true` for sandboxed autonomous execution. `dangerouslyBypassApprovalsAndSandbox:true` is still raw bypass. On `codex exec resume` (when `sessionId` or `resumeLatest` is set), `fullAuto` is silently dropped — the original session's approval policy is inherited, so audit the source session's approval posture before resuming.
87
+ - Grok: equivalent permissive flag handled by the Grok provider; raw `alwaysApprove` / `permissionMode` overrides are scored the same way as Claude/Gemini raw bypass.
88
+
89
+ Gateway approval engine becomes the gatekeeper before permissive CLI modes are applied.
90
+
91
+ ## Audit Trail
92
+
93
+ ```
94
+ approval_list({limit:50})
95
+ ```
96
+
97
+ Returns:
98
+ ```json
99
+ {"approvals":[{"id":"appr-...","ts":"...","status":"approved","policy":"balanced","cli":"claude","operation":"claude_request","score":0,"reasons":[],"promptPreview":"Refactor the auth...","promptSha256":"a1b2c3...","requestedMcpServers":["example-server"]}]}
100
+ ```
101
+
102
+ Filter: `approval_list({limit:50,cli:"codex"})`
103
+
104
+ Key fields: `promptPreview` (first 280 chars) | `promptSha256` (correlation) | `reasons` (score breakdown) | `bypassRequested` | `fullAuto`
105
+
106
+ ## Security Guidelines
107
+
108
+ **Production:** Always strict approval:
109
+ ```
110
+ claude_request({prompt:"...",approvalStrategy:"mcp_managed",approvalPolicy:"strict"})
111
+ ```
112
+
113
+ **Development:** Balanced for trusted prompts:
114
+ ```
115
+ codex_request({prompt:"...",approvalStrategy:"mcp_managed",approvalPolicy:"balanced",fullAuto:true})
116
+ ```
117
+
118
+ **Never in production (raw CLI bypass flags — bypass the gateway gate entirely):**
119
+ - `dangerouslySkipPermissions:true` (Claude)
120
+ - `dangerouslyBypassApprovalsAndSandbox:true` (Codex)
121
+ - `approvalMode:"yolo"` (Gemini)
122
+ - Raw permissive flags on `grok_request` (e.g., caller-supplied `alwaysApprove` or permissive `permissionMode`)
123
+
124
+ Use `approvalStrategy:"mcp_managed"` instead so the gateway scores and gates the request before permissive CLI modes are applied. For Codex, include `fullAuto:true` when the task needs file or shell access (note: not honored on `codex exec resume` — the resumed session inherits its original approval policy).
125
+
126
+ ## Permission Management
127
+
128
+ Before setting provider-specific tool, MCP, sandbox, or session fields, query:
129
+
130
+ ```
131
+ provider_tool_capabilities({cli:"claude"})
132
+ ```
133
+
134
+ Repeat for `codex`, `gemini`, `grok`, or `mistral` as needed. Use the returned
135
+ `controls` and `unsupportedInputs`; do not infer one provider's permission
136
+ surface from another provider's CLI.
137
+
138
+ ### Claude MCP servers
139
+
140
+ ```
141
+ claude_request({prompt:"...",mcpServers:["<server-name>"],strictMcpConfig:true})
142
+ ```
143
+
144
+ - `mcpServers`: the MCP server names to enable for this request (the available set depends on the host's Claude MCP configuration)
145
+ - `strictMcpConfig:true`: fail if a requested server is unavailable
146
+
147
+ For Codex, Grok, and Mistral Vibe, `mcpServers` is approval tracking only; each
148
+ provider owns its MCP configuration. For the current Gemini/Antigravity request
149
+ path, non-empty `mcpServers` is rejected.
150
+
151
+ ### Codex sandboxing
152
+
153
+ `fullAuto:true` enables automated changes, stays sandboxed.
154
+
155
+ ### Gemini approval modes
156
+
157
+ `approvalMode`: `default` (ask) | `auto_edit` (auto-approve edits) | `yolo` (dev only)
158
+
159
+ ### Tool restrictions
160
+
161
+ **Claude** — allowlists + blocklists:
162
+ ```
163
+ claude_request({prompt:"...",allowedTools:["Read","Grep","Glob"],disallowedTools:["Bash","Write"]})
164
+ ```
165
+
166
+ **Gemini/Antigravity** — current path rejects non-empty allowlists:
167
+ ```
168
+ gemini_request({prompt:"...",approvalStrategy:"mcp_managed"})
169
+ ```
170
+
171
+ **Grok** — provider-native allowlists only:
172
+ ```
173
+ grok_request({prompt:"...",approvalStrategy:"mcp_managed"})
174
+ ```
175
+ Do not pass Claude tool names such as `Read`, `Grep`, `Glob`, or `Bash` as Grok
176
+ `allowedTools`; query `provider_tool_capabilities({cli:"grok"})` and use
177
+ discovered provider-native tool names only when you intentionally need an
178
+ allowlist.
179
+
180
+ **Mistral Vibe** — enabled-tool allowlist only:
181
+ ```
182
+ mistral_request({prompt:"...",allowedTools:["<vibe-enabled-tool>"],approvalStrategy:"mcp_managed"})
183
+ ```
184
+ `disallowedTools` is accepted for parity but ignored because Vibe has no
185
+ deny-list flag.
186
+
187
+ ## Idle Timeout as Security Control
188
+
189
+ Tight idle timeout limits window for unintended operations:
190
+
191
+ ```
192
+ claude_request({prompt:"Audit secrets module",approvalStrategy:"mcp_managed",approvalPolicy:"strict",idleTimeoutMs:120000})
193
+ ```
194
+
195
+ Kills process after 2min inactivity. Exit code 125 (non-transient, no retry).
196
+
197
+ **Guideline:** 60-120s for security audits. Full 10min default for large analysis only.
198
+
199
+ ## Cache Observability for Audit Tracing
200
+
201
+ Three read-only MCP resources expose cache effectiveness from the flight recorder — tokens / hashes / aggregates only, **no prompt or response text**:
202
+
203
+ - `cache-state://global` — last-24h aggregate hit rate, total hits, estimated savings, per-CLI breakdown
204
+ - `cache-state://session/{sessionId}` — per-session aggregates (also surfaces as `session_get.cacheState` when the session has prior requests)
205
+ - `cache-state://prefix/{hash}` — per-stable-prefix-hash aggregates with CLI × model breakdown
206
+
207
+ The "tokens/hashes only" property is the security guarantee: these resources let an auditor reconstruct "did the model see fresh context or a cached prefix?" without ever exposing prompt or response content. Combine with `approval_list` (which already redacts to a `promptPreview` + `promptSha256`) and `llm_job_status/result` by `correlationId` to reconstruct any past dispatch fully.
208
+
209
+ For Claude sessions, `[cache_awareness] warn_on_ttl_expiry = true` in `~/.llm-cli-gateway/config.toml` adds a structured `cache_ttl_expiring_soon` warning to responses whose prior `lastRequestAt` is within 30 s of Anthropic's TTL — useful as a signal that an audit-relevant turn may have hit a cold cache and is paying full cache-creation tokens.
210
+
211
+ ### `promptParts` for prompt-discipline auditing
212
+
213
+ Switching from `prompt` to the structured `promptParts` field (`{ system?, tools?, context?, task }`, mutually exclusive with `prompt`) makes the audit story stronger: the stable prefix hash is stable across calls, so reviewing an audit trail you can see at a glance whether two requests shared a system/tools/context block or genuinely differed. Identical hashes across reviewers in a parallel dispatch = same brief; differing hashes = drift to investigate.
214
+
215
+ ## Tips
216
+
217
+ - Start `strict`, relax only when needed
218
+ - Review audit trail regularly: `approval_list`
219
+ - Use `correlationId` on every request for tracing
220
+ - `approvalStrategy:"mcp_managed"` is the skill default dispatch path — the gateway schema default is `"legacy"`, which skips the gate unless explicitly selected
221
+ - Denied requests return immediately without executing
222
+ - Gates apply equally to sync and async requests
223
+ - `idleTimeoutMs` is a security control (tight 60–120 s for security audits kills silent processes quickly) — this is **not** a wallclock timeout, so it does not conflict with the "no wallclock timeout, poll every 60 s" dispatch default
224
+ - Approval checks run before auto-deferral — denied requests reject instantly
225
+ - Review dispatches loop until unconditional APPROVED — the approval gate is separate from the reviewer's verdict, and both must pass
226
+ - **Durable audit trail**: approvals and job state are persisted (default 30 days, `LLM_GATEWAY_JOB_RETENTION_DAYS`). Combine `approval_list` with `llm_job_status/result` by `correlationId` to reconstruct any past dispatch, even across gateway restarts
227
+ - **Auto-dedup interacts with approvals**: an identical replayed request within the dedup window (`LLM_GATEWAY_DEDUP_WINDOW_MS`, default 1 h) reuses the original job. The **original** approval decision is the one of record; the dedup hit does not re-run the gate. Use `forceRefresh:true` to force a fresh approval evaluation when the security-relevant context (caller, prompt, flags) has actually changed
@@ -0,0 +1,271 @@
1
+ ---
2
+ name: session-workflow
3
+ description: Manage conversation sessions across Claude, Codex, Gemini, Grok, and Mistral. Use for multi-turn conversations, session switching, workspace management. Covers the `session_get.cacheState` projection, cache-aware `promptParts`, and `cache-state://` MCP resources.
4
+ metadata:
5
+ author: verivus-oss
6
+ version: "1.6"
7
+ ---
8
+
9
+ # Session Workflow
10
+
11
+ Sessions track conversation context across requests. One active session per CLI.
12
+
13
+ ## Dispatch Defaults
14
+
15
+ Apply these on every dispatch unless the caller has explicitly overridden a rule in the current turn:
16
+
17
+ 1. **Omit `model`** — let the gateway use its configured default per CLI.
18
+ 2. **`approvalStrategy:"mcp_managed"`** is the skill dispatch default (the gateway schema default is `"legacy"`). For Codex, also pass `fullAuto:true` when the task needs file/shell access.
19
+ 3. **No wallclock timeout; poll every 60 s** — `idleTimeoutMs` is a separate no-output safeguard.
20
+ 4. **Iterate until unconditional APPROVED** (review dispatches only) — every review prompt must end with "End with APPROVED or NOT APPROVED with findings." Loop: dispatch review → poll if deferred → parse verdict → on `NOT APPROVED` or conditional approval, dispatch fixes + re-review → repeat. Escalate after 3 rounds. This rule does **not** apply to pure implementation or non-review analysis dispatches. Sessions make the loop cheap (Claude and Gemini preserve conversation continuity).
21
+
22
+ ## Key Concepts
23
+
24
+ - **Active session** — default when no `sessionId` specified
25
+ - **Session ID** — string identifier (UUID or arbitrary)
26
+ - **`gw-*` prefix** — gateway-generated Gemini bookkeeping IDs; rejected if passed back to Gemini as `sessionId`
27
+ - **`resumable`** — Gemini response field: `true`=user-provided ID, `false`=gateway `gw-*` ID
28
+ - **Session TTL** — 30 days inactivity (configurable: `SESSION_TTL` env var, seconds)
29
+ - Stores metadata only (id, cli, timestamps, description) — not conversation content
30
+
31
+ ## Session Continuity Per CLI
32
+
33
+ | CLI | Effect | Mechanism |
34
+ |-----|--------|-----------|
35
+ | **Claude** | Real continuity | `--session-id` or `--continue` to CLI |
36
+ | **Codex** | Real continuity | `codex exec resume <UUID>` (`sessionId`) or `codex exec resume --last` (`resumeLatest:true`). `sessionId` must be a real Codex UUID from `~/.codex/sessions/`; gateway-generated `gw-*` IDs are rejected. `--full-auto` silently dropped on resume (approval policy inherits from the original session) |
37
+ | **Gemini** | Real continuity | `--resume` to CLI |
38
+ | **Grok** | Real continuity | `--resume` / `--continue` to CLI |
39
+ | **Mistral** | Real continuity | `--resume` / `--continue` to CLI; current Vibe defaults session logging on, and doctor flags explicit `[session_logging] enabled = false` |
40
+
41
+ All four CLIs now carry true multi-turn continuity. For Codex, you must either pass `resumeLatest:true` or supply a real Codex session UUID — the gateway no longer treats Codex sessions as bookkeeping-only.
42
+
43
+ ## Creating Sessions
44
+
45
+ ### Explicit
46
+
47
+ ```
48
+ session_create({cli:"claude",description:"Refactoring auth module",setAsActive:true})
49
+ ```
50
+
51
+ Returns: `{success:true,session:{id,cli,description,createdAt,isActive}}`
52
+
53
+ ### Via request
54
+
55
+ ```
56
+ claude_request({prompt:"...",createNewSession:true,approvalStrategy:"mcp_managed"})
57
+ ```
58
+
59
+ ### Implicit active session
60
+
61
+ - **Claude** — auto-continues active session via `--continue`
62
+ - **Codex** — no auto-lookup; pass `resumeLatest:true` (→ `codex exec resume --last`) or `sessionId:<UUID>` (→ `codex exec resume <UUID>`) explicitly
63
+ - **Gemini** — no auto-lookup; use `sessionId` or `resumeLatest:true` explicitly
64
+ - **Grok** — pass `sessionId` (or `resumeLatest:true`) to resume via `--resume`/`--continue`
65
+
66
+ ```
67
+ claude_request({prompt:"Continue where we left off",approvalStrategy:"mcp_managed"}) // auto-continues
68
+ codex_request({prompt:"Continue",resumeLatest:true,fullAuto:true,approvalStrategy:"mcp_managed"}) // codex exec resume --last
69
+ gemini_request({prompt:"Continue analysis",resumeLatest:true,approvalStrategy:"mcp_managed"}) // explicit resume
70
+ grok_request({prompt:"Continue",sessionId:"my-grok-session",approvalStrategy:"mcp_managed"}) // explicit resume
71
+ ```
72
+
73
+ ## Multi-Turn Patterns
74
+
75
+ ### Claude (real continuity)
76
+
77
+ ```
78
+ claude_request({prompt:"Implement rate limiter in src/rate-limiter.ts",createNewSession:true,approvalStrategy:"mcp_managed"})
79
+ // Save returned sessionId
80
+
81
+ claude_request({prompt:"Add unit tests for rate limiter",sessionId:"[saved id]",approvalStrategy:"mcp_managed"})
82
+ ```
83
+
84
+ ### Codex (real continuity via `codex exec resume`)
85
+
86
+ Pass `resumeLatest:true` to continue the most recent Codex session in cwd, or `sessionId:<UUID>` to target a specific session (UUID visible in `~/.codex/sessions/` or via `codex resume`):
87
+
88
+ ```
89
+ codex_request({prompt:"Implement rate limiter with sliding window",fullAuto:true,approvalStrategy:"mcp_managed"})
90
+ // Subsequent turn — resume the same Codex session:
91
+ codex_request({prompt:"Add tests: basic limiting, burst traffic, window expiry.",resumeLatest:true,approvalStrategy:"mcp_managed"})
92
+ // Or target a known UUID:
93
+ codex_request({prompt:"Now add metrics.",sessionId:"7f9f9a2e-1b3c-4c7a-9b0e-deadbeefcafe",approvalStrategy:"mcp_managed"})
94
+ ```
95
+
96
+ Note: `fullAuto:true` is silently dropped on resume — the original session's approval policy is inherited. If you need a fresh approval posture, pass `createNewSession:true` and re-state the context.
97
+
98
+ ### Gemini (resumable)
99
+
100
+ ```
101
+ gemini_request({prompt:"Continue analysis",resumeLatest:true,approvalStrategy:"mcp_managed"})
102
+ gemini_request({prompt:"Continue",sessionId:"latest",approvalStrategy:"mcp_managed"})
103
+ gemini_request_async({prompt:"Deep analysis...",sessionId:"my-gemini-session",approvalStrategy:"mcp_managed"})
104
+ // Response: resumable:true
105
+ ```
106
+
107
+ ### Grok (real continuity)
108
+
109
+ Grok carries real CLI continuity via `--resume` / `--continue`. Auth must already be set up (`grok login` OAuth, or `GROK_CODE_XAI_API_KEY`):
110
+
111
+ ```
112
+ grok_request({prompt:"Implement rate limiter in src/rate-limiter.ts",createNewSession:true,approvalStrategy:"mcp_managed"})
113
+ // Save returned sessionId
114
+
115
+ grok_request({prompt:"Add unit tests for the rate limiter",sessionId:"[saved id]",approvalStrategy:"mcp_managed"})
116
+ ```
117
+
118
+ ## Switching Sessions
119
+
120
+ ```
121
+ session_list() // all sessions
122
+ session_list({cli:"claude"}) // filter by CLI
123
+ session_set_active({cli:"claude",sessionId:"..."}) // switch active
124
+ session_set_active({cli:"claude",sessionId:null}) // clear active
125
+ ```
126
+
127
+ ## Parallel Workflows
128
+
129
+ Separate sessions for independent workstreams:
130
+
131
+ ```
132
+ session_create({cli:"claude",description:"Feature: user auth"}) // → authSessionId
133
+ session_create({cli:"claude",description:"Bugfix: rate limit"}) // → bugfixSessionId
134
+
135
+ claude_request({prompt:"...",sessionId:authSessionId,approvalStrategy:"mcp_managed"}) // auth context
136
+ claude_request({prompt:"...",sessionId:bugfixSessionId,approvalStrategy:"mcp_managed"}) // bugfix context
137
+ ```
138
+
139
+ ## Session TTL
140
+
141
+ Default: 30 days. Expired sessions silently evicted on next operation.
142
+
143
+ ```bash
144
+ SESSION_TTL=604800 # 7 days
145
+ SESSION_TTL=7776000 # 90 days
146
+ ```
147
+
148
+ - Based on `lastUsedAt`, not `createdAt`
149
+ - Active sessions stay alive with use
150
+ - If active session expires, active pointer cleared
151
+ - File-based and PostgreSQL backends both enforce TTL
152
+
153
+ ## Gateway Prefix (`gw-`)
154
+
155
+ Gemini requests without explicit `sessionId` → gateway generates `gw-*` ID.
156
+
157
+ Passing `gw-*` as `sessionId` → rejected:
158
+ > Session ID "gw-..." uses reserved prefix "gw-".
159
+
160
+ Check `resumable` field: `true`=safe to reuse, `false`=gateway-generated.
161
+
162
+ ## Cleanup
163
+
164
+ ```
165
+ session_delete({sessionId:"..."}) // single
166
+ session_clear_all({cli:"codex"}) // per CLI
167
+ session_clear_all() // everything
168
+ ```
169
+
170
+ ## Inspect
171
+
172
+ ```
173
+ session_get({sessionId:"..."})
174
+ ```
175
+
176
+ Returns: timestamps, description, CLI type, active status.
177
+
178
+ When the session has prior requests in the flight recorder, the response also includes a compact `cacheState` block (omitted entirely for fresh sessions — not null, not empty object):
179
+
180
+ ```json
181
+ {
182
+ "cacheState": {
183
+ "cli": "claude",
184
+ "prefixDistinct": 3,
185
+ "totalCacheReadTokens": 14210,
186
+ "totalCacheCreationTokens": 8420,
187
+ "requestCount": 7,
188
+ "hitCount": 5,
189
+ "hitRate": 0.714,
190
+ "estimatedSavingsUsd": 0.0184,
191
+ "ttlRemainingMs": 142000
192
+ }
193
+ }
194
+ ```
195
+
196
+ `ttlRemainingMs` is non-null only for Claude sessions and reflects the configured `[cache_awareness] anthropic_ttl_seconds` (default 300 = 5 min, or 3600 = 1 h). Use it to decide whether the next turn will hit a warm cache or pay full cache-creation cost. No prompt/response text is stored or returned — only tokens, hashes, and aggregates.
197
+
198
+ ## Cache-Aware Prompts and Cache Observability
199
+
200
+ Sessions and cache awareness compose. The structured `promptParts` field (mutually exclusive with `prompt`) lets you keep `system` / `tools` / `context` byte-identical across turns of a session while only the `task` mutates:
201
+
202
+ ```
203
+ claude_request({
204
+ promptParts: {
205
+ system: "<stable system instruction>",
206
+ context: "<file dump or spec — same as last turn>",
207
+ task: "Now add metrics."
208
+ },
209
+ sessionId: savedSessionId,
210
+ approvalStrategy: "mcp_managed"
211
+ })
212
+ ```
213
+
214
+ The gateway hashes the stable prefix and writes it to the flight recorder so per-session and per-prefix cache effectiveness is queryable via MCP resources:
215
+
216
+ - `cache-state://global` — last-24h aggregate hit rate, total hits, estimated savings, with per-CLI breakdown
217
+ - `cache-state://session/{sessionId}` — per-session aggregates (same shape as `session_get.cacheState`)
218
+ - `cache-state://prefix/{hash}` — per-stable-prefix-hash aggregates with CLI × model breakdown
219
+
220
+ ### Provider-specific cache direction examples
221
+
222
+ **Claude (explicit cacheControl)**
223
+
224
+ ```
225
+ claude_request({
226
+ promptParts: {
227
+ system: "<stable>",
228
+ context: "<large stable context>",
229
+ task: "Now add metrics.",
230
+ cacheControl: { system: true, context: true }
231
+ },
232
+ outputFormat: "stream-json",
233
+ sessionId: savedSessionId
234
+ })
235
+ ```
236
+
237
+ **Grok (compaction)**
238
+
239
+ ```
240
+ grok_request({
241
+ promptParts: { system: "<stable>", context: "...", task: "..." },
242
+ compactionMode: "segments",
243
+ compactionDetail: "balanced",
244
+ sessionId: savedSessionId
245
+ })
246
+ ```
247
+
248
+ See `docs/personal-mcp/PROVIDER_CACHE_SURFACES.md` for exact stream-json payload shape, telemetry differences, and the full matrix. Prefix discipline (`promptParts` without cacheControl) works for all CLIs.
249
+
250
+ ### TTL warning (Claude, opt-in)
251
+
252
+ With `[cache_awareness] warn_on_ttl_expiry = true` in `~/.llm-cli-gateway/config.toml`, a resumed Claude turn whose prior `lastRequestAt` is within 30 s of the cache TTL returns:
253
+
254
+ ```json
255
+ { "warnings": [{ "code": "cache_ttl_expiring_soon", "ttlRemainingMs": 12000, "message": "..." }] }
256
+ ```
257
+
258
+ That is a hint that the next turn will likely cold-miss; coalesce or accept the cost.
259
+
260
+ ## Tips
261
+
262
+ - Descriptive names: "Refactoring auth module" > "Session 1"
263
+ - Use `createNewSession:true` for quick one-offs
264
+ - Clean up completed workflow sessions
265
+ - Each CLI tracks active session independently
266
+ - For Codex: real `codex exec resume <UUID>` / `--last` continuity. The gateway-tracked session ID is independent of the Codex CLI's session UUID — for resume, supply a real Codex UUID or use `resumeLatest:true`. `--full-auto` is dropped on resume (approval policy inherits from the original session)
267
+ - For Grok: real `--resume`/`--continue` continuity, same model as Claude — but auth must be set up first (`grok login` or `GROK_CODE_XAI_API_KEY`)
268
+ - Expired sessions (past TTL) silently evicted
269
+ - Never pass Gemini `gw-*` IDs as `sessionId` — use own IDs for resumable Gemini workflows
270
+ - Check Gemini's `resumable` field to know if that session can continue
271
+ - Sync tools may auto-defer at 45s — deferred response preserves `sessionId`. Deferred jobs and their results are now durable (default 30-day retention via `LLM_GATEWAY_JOB_RETENTION_DAYS`), so a session that auto-defers can be picked up across gateway restarts
package/CHANGELOG.md CHANGED
@@ -2,6 +2,46 @@
2
2
 
3
3
  All notable changes to the llm-cli-gateway project.
4
4
 
5
+ ## [2.12.0] - 2026-06-30: Validation receipts, API-provider parity, and a gateway usability pass
6
+
7
+ ### Added
8
+
9
+ - **Durable, owner-scoped receipts for cross-LLM validation runs.** A terminal validation run (every dispatched provider job terminal, and the judge if one was requested) mints exactly one immutable `validation_receipts` row enveloping the existing `validation-report.v1` structuredContent, with a deterministic `canonical_sha256` over the canonical serialization (recursively sorted object keys, preserved array order, UTF-8; the re-derived `humanReadable` is excluded). Minting is eager (the first time a run is observed terminal, via the `job_result` collection hook and `synthesize_validation`), so a receipt is captured before job rows are evicted; an explicit read is the fallback.
10
+ - **New `validation_receipt` tool.** Retrieves a receipt by `validationId` with own-or-not-found ownership: `minted` | `pending` | `expired_unminted` | `not_found`. `format: "json" | "markdown"` (markdown is a read-time rendering, never stored or hashed). `includeRawResponses` inlines full provider answer text as a read-time-only expansion pulled live per job under the same owner check (never persisted, never hashed).
11
+ - **New `validation-receipt://{validationId}` MCP resource** with the same own-or-not-found owner scoping.
12
+ - **Durable run identity (prerequisite).** `validation_runs` (plus a `validation_run_jobs` reverse index) persists each run's `validationId`, owner principal, and provider/judge job links at kickoff. The report and synthesis status enums gained a terminal `completed` value.
13
+ - The receipt tool, the resource, and the run/receipt tables exist ONLY under an implemented durable backend (today `sqlite`) with a store attached at runtime; under `memory` / `postgres` / `none` no run/receipt row is written and the tool/resource are not registered (the caller still receives a `validationId` at kickoff). Silent loss is impossible by construction.
14
+ - **Grok structured-output and session/worktree parity (Grok 0.2.73).** `grok_request` / `grok_request_async` gained `jsonSchema` (emits `--json-schema`, constrains output to a JSON Schema, implies json output), `forkSession` (emits `--fork-session`, forks a resumed session into a new ID), and `worktreeRef` (emits `--worktree-ref <REF>`; requires `nativeWorktree`, else rejected). Grok `--session-id` remains intentionally unwired (the gateway owns session-id lifecycle and cross-principal isolation).
15
+ - **Antigravity project selection (agy 1.0.13).** `gemini_request` / `gemini_request_async` gained `project` (emits `--project <ID>`) and `newProject` (emits `--new-project`); the two are mutually exclusive (rejected if both set).
16
+ - **API-provider unification: the generic `api_<name>_request` surface reaches CLI-tool parity (Slices 1-4b).** Telemetry parity (per-request usage/cost), schema parity (`promptParts`, `optimizePrompt`, `optimizeResponse`, `forceRefresh`, `sessionId`, `createNewSession`), capability-typed continuity with gateway sessions (`ApiContinuity` = `server-side-id` | `stateless-resend` | `none`), and server-side-id continuation on the generic handler. The legacy standalone `xai-api-provider` module was removed; the grok-api HTTP path now runs through the shared `api-provider` / `api-http` adapter (externally-observable response shape unchanged).
17
+ - **OpenRouter usage accounting.** New optional provider config key `usage_include` opts the OpenAI-compatible adapter into `usage: { include: true }` so token counts and end-to-end `usage.cost` (USD) are returned; off by default so strict OpenAI-compatible servers are unaffected.
18
+ - **Caller-facing skills now ship in the npm package.** The published gateway serves six caller skills (`async-job-orchestration`, `multi-llm-review`, `session-workflow`, `secure-orchestration`, `implement-review-fix`, `public-demo-session`); previously `.agents` was excluded from `package.json` `files`, so every install served zero skills. Operator/maintainer skills (the `provider-*` contract guides, `gateway-restart-surfaces`) stay out of the tarball, and the release tarball guard now scans shipped skills for host-internal leaks.
19
+
20
+ ### Changed
21
+
22
+ - **Upstream contracts refreshed to the installed provider versions.** Live `--probe-installed` probes were re-run across all six provider CLIs; ACP `targetVersion` pins bumped to claude 2.1.195, codex-cli 0.142.4, agy 1.0.13, grok 0.2.73, and devin 2026.8.18 (mistral vibe 2.17.1 unchanged), kept in sync across `upstream-contracts`, `provider-tool-capabilities`, and the ACP `provider-registry`. Acknowledged newly-advertised flags (claude `--bg`/`--background`); dropped stale acknowledgements (claude `--mcp-debug`, agy `-i`/`--version`); and acknowledged grok's `ssh` subcommand inheriting the global agent flag surface.
23
+ - **`agy update` help-probe drift silenced.** `agy update --help` uses Go's `flag` package (prints "Usage of update:" and exits 2). A new `helpProbeExitTolerant` subcommand-contract marker treats that legitimate non-zero help exit as clean rather than reporting it as drift.
24
+ - **Devin is now visible across discovery.** Devin was a fully registered request tool but omitted from the server instructions and rejected by the `list_models`, `cli_versions`, `cli_upgrade`, and `provider_tool_capabilities` enums; those now include devin (pure widening), and the stale "five providers" describe/description text was refreshed.
25
+ - **Request tool and parameter descriptions sharpened for usability.** Clarified Codex `sandboxMode` (omit = read-only), Gemini `approvalMode`/`outputFormat` (only default/yolo and text work on the agy headless path), `approvalStrategy`/`approvalPolicy` semantics, `idleTimeoutMs` (gateway 10-min idle kill; Claude stream-json only), `jsonSchema` (set `outputFormat:json`), session-resume rules (gw-* fresh-session ids are not resumable; use `resumeLatest`), the generic API params, and the mistral/devin permission defaults.
26
+
27
+ ### Reliability and error guidance
28
+
29
+ - **Result-health guards against silent success.** A Claude run that exits 0 but reports `is_error:true` (e.g. `error_max_turns`) now carries a `claude_result_error` warning and a `resultIsError` signal (the text is still returned); an exit-0 run with empty assistant output emits an `empty_output` warning. Claude `--output-format json` telemetry (usage/cost) is now parsed (it was previously dropped), and the real Codex session UUID is surfaced as `codexSessionId` so callers can resume/fork without filesystem access.
30
+ - **Actionable error remediation.** `createErrorResponse` now detects auth/login failures and appends per-CLI remediation (`errorCategory: auth_error`), categorizes the 50MB output overflow as `output_overflow`, adds async-retry guidance on wall-clock timeout (124) and an interactive/idle hint on idle timeout (125), enriches Codex failures from the JSONL stream when stderr is empty (with a resume-not-found hint), and adds a fallback hint when a non-zero exit captured no stderr.
31
+ - A whitespace-only prompt is now rejected at the prep boundary instead of being passed to the CLI.
32
+
33
+ ### Fixed
34
+
35
+ - **Cross-principal read hole on the validation collection tools.** `job_status` / `job_result` now apply the `principalCanAccess` owner check that the `llm_job_*` paths already enforce; a job owned by another principal is reported as not found.
36
+
37
+ ### Deps
38
+
39
+ - npm-minor-patch group: `smol-toml` 1.6.1 to 1.7.0 (runtime), `pg` 8.21.0 to 8.22.0, plus devDeps `@types/node` to 26.0.1, `@typescript-eslint/{eslint-plugin,parser}` 8.61.1 to 8.62.1, `eslint` 10.5.0 to 10.6.0, `prettier` 3.8.4 to 3.9.3. CI: `zizmor` 1.25.2 to 1.26.1, github-actions group bumps. Dependabot now ignores `body-parser` 2.3.0 (it transitively pulled the Socket-flagged `type-is` 2.1.0 / `content-type` 2.0.0 that the release audit blocks).
40
+
41
+ ### Not yet implemented (reserved)
42
+
43
+ - Cryptographic signing and hash chaining. The `prev_sha256` / `seq` / `signature` columns are reserved (NULL); the canonical byte definition they will build on is fixed now. Semantic enrichment (`key_points`, `evidence_cited`, `uncertainty_signals`, numeric per-model confidence) is deferred to `validation-receipt.v2` (no extraction source exists today).
44
+
5
45
  ## [2.11.1] - 2026-06-22: Provider contract refresh and stable upstream scans
6
46
 
7
47
  ### Changed