llm-cli-gateway 2.10.0 → 2.11.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.
- package/CHANGELOG.md +75 -1
- package/README.md +46 -14
- package/dist/acp/event-normalizer.d.ts +42 -0
- package/dist/acp/event-normalizer.js +71 -0
- package/dist/acp/flight-redaction.d.ts +25 -0
- package/dist/acp/flight-redaction.js +40 -0
- package/dist/acp/host-services.d.ts +16 -0
- package/dist/acp/host-services.js +29 -0
- package/dist/acp/permission-bridge.d.ts +15 -0
- package/dist/acp/permission-bridge.js +90 -0
- package/dist/acp/process-manager.js +7 -1
- package/dist/acp/provider-registry.d.ts +1 -1
- package/dist/acp/provider-registry.js +18 -5
- package/dist/acp/runtime.d.ts +35 -0
- package/dist/acp/runtime.js +125 -0
- package/dist/acp/session-map.d.ts +42 -0
- package/dist/acp/session-map.js +67 -0
- package/dist/acp/smoke-harness.d.ts +28 -0
- package/dist/acp/smoke-harness.js +90 -0
- package/dist/api-http.d.ts +18 -0
- package/dist/api-http.js +122 -0
- package/dist/api-provider.d.ts +83 -0
- package/dist/api-provider.js +258 -0
- package/dist/api-request.d.ts +30 -0
- package/dist/api-request.js +51 -0
- package/dist/approval-manager.d.ts +1 -1
- package/dist/approval-manager.js +6 -7
- package/dist/async-job-manager.d.ts +19 -4
- package/dist/async-job-manager.js +211 -35
- package/dist/claude-mcp-config.d.ts +2 -2
- package/dist/claude-mcp-config.js +42 -52
- package/dist/cli-updater.js +16 -1
- package/dist/config.d.ts +20 -0
- package/dist/config.js +93 -35
- package/dist/doctor.d.ts +1 -1
- package/dist/flight-recorder.d.ts +1 -0
- package/dist/flight-recorder.js +11 -0
- package/dist/index.d.ts +56 -5
- package/dist/index.js +639 -38
- package/dist/job-store.d.ts +15 -0
- package/dist/job-store.js +39 -5
- package/dist/mcp-registry.d.ts +17 -0
- package/dist/mcp-registry.js +5 -0
- package/dist/metrics.js +7 -2
- package/dist/model-registry.js +11 -0
- package/dist/prompt-parts.d.ts +6 -6
- package/dist/provider-login-guidance.js +21 -0
- package/dist/provider-status.js +4 -1
- package/dist/provider-tool-capabilities.d.ts +8 -3
- package/dist/provider-tool-capabilities.js +107 -17
- package/dist/request-helpers.d.ts +6 -6
- package/dist/request-helpers.js +1 -4
- package/dist/session-manager-pg.js +2 -9
- package/dist/session-manager.d.ts +9 -4
- package/dist/session-manager.js +13 -4
- package/dist/upstream-contracts.js +184 -24
- package/dist/validation-normalizer.d.ts +2 -2
- package/dist/validation-orchestrator.d.ts +2 -0
- package/dist/validation-orchestrator.js +28 -7
- package/dist/validation-tools.d.ts +61 -0
- package/dist/validation-tools.js +36 -21
- package/migrations/005_provider_type_open_api_names.sql +28 -0
- package/npm-shrinkwrap.json +6 -5
- package/package.json +12 -9
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,81 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the llm-cli-gateway project.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## [2.11.1] - 2026-06-22: Provider contract refresh and stable upstream scans
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- **Upstream contracts refreshed for all providers.** Live `--probe-installed` probes were run across the installed provider CLIs. Synced contract surfaces and bumped ACP `targetVersion` pins:
|
|
10
|
+
- claude → 2.1.185 (added `--ax-screen-reader`, `--safe-mode` to acknowledged flags; `agents --all` to subcommand contract)
|
|
11
|
+
- codex → 0.141.0 (aligned `exec resume` / `exec review` / top-level `review` flag lists with current advertised surfaces)
|
|
12
|
+
- gemini (agy) → 1.0.10
|
|
13
|
+
- grok → 0.2.60 (474c2bbfc)
|
|
14
|
+
- mistral (vibe) → 2.17.1
|
|
15
|
+
- devin → 2026.7.23 (3bd47f77) (added new flags to `acknowledgedUpstreamFlags`)
|
|
16
|
+
- **Mistral upstream scan source stabilized.** The scanner now tracks GitHub's latest-release API for Mistral Vibe instead of the volatile HTML releases page, so `--live --fail-on-critical` no longer trips on release-page chrome/hash churn.
|
|
17
|
+
- **Provider skill docs updated.** All five `.agents/skills/provider-*` files bumped to metadata version 1.2. Descriptions now call out `/subcommand` behaviour changes; added current-version "Tested against" notes and usage examples for the cache levers (Grok compaction, Claude `cacheControl`).
|
|
18
|
+
- **Cache usage documentation expanded with concrete examples.** Added exact parameters, emitted CLI flags, and stream-json payload shapes for Grok `compactionMode`/`compactionDetail` and Claude `promptParts.cacheControl` (including the `assembleClaudeCacheBlocks` NDJSON structure with `cache_control: {type:"ephemeral", ttl:"1h"}`) to:
|
|
19
|
+
- `docs/personal-mcp/PROVIDER_CACHE_SURFACES.md`
|
|
20
|
+
- `README.md` (updated Cache-aware operation section and matrix)
|
|
21
|
+
- `.agents/skills/session-workflow/SKILL.md`
|
|
22
|
+
- Cross-LLM reviews (Claude, Codex, Mistral) on the implementation plan were incorporated: kept provider-specific levers (no new unifying `CacheDirective` abstraction); focused on documentation and discoverability.
|
|
23
|
+
- **Internal caching hygiene.** Promoted the ad-hoc capability cache in `provider-tool-capabilities.ts` to a documented reusable TTL structure (`CAPABILITY_CACHE`, helpers, test accessors) for easier future reuse.
|
|
24
|
+
|
|
25
|
+
### Tests / CI
|
|
26
|
+
|
|
27
|
+
- `npm run upstream:contracts`, relevant cache/session/provider-tool tests, and build all green.
|
|
28
|
+
|
|
29
|
+
## [2.11.0] - 2026-06-18: API providers, Devin, ACP runtime, and a strip-at-publish internal-MCP boundary
|
|
30
|
+
|
|
31
|
+
The accumulated work since 2.10.0: a generic HTTP API-provider surface, a sixth
|
|
32
|
+
CLI provider (Devin), the Phase B ACP runtime (gated, dormant by default), and a
|
|
33
|
+
release-time strip that keeps gateway-internal MCP server names out of the
|
|
34
|
+
published npm artifact. Default local-stdio behaviour is unchanged; the new
|
|
35
|
+
API/ACP surfaces are opt-in. Every change in this release was landed via PR with
|
|
36
|
+
green CI and an adversarial multi-LLM review.
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
|
|
40
|
+
- **API providers (Slices 0–5).** An `ApiProvider` interface with OpenAI /
|
|
41
|
+
Anthropic / xAI adapters, a generic `api_<name>_request[_async]` tool surface,
|
|
42
|
+
an `HttpJobRunner` that treats HTTP calls as first-class async jobs, API-provider
|
|
43
|
+
discovery/catalog, and the ability to use API providers as validation
|
|
44
|
+
reviewers/judges. Open-string provider-identity widening (`kind:"api"`).
|
|
45
|
+
- **Devin (Slices D0–D1).** Devin as a sixth gateway provider —
|
|
46
|
+
`devin_request[_async]` — with permission-mode aliases corrected against the
|
|
47
|
+
real CLI and a passing native-ACP smoke (third ACP runtime pilot).
|
|
48
|
+
- **ACP runtime, Phase B (Slices B1–B7).** Read-only smoke harness,
|
|
49
|
+
deny-by-default HostServices boundary, permission bridge to ApprovalManager,
|
|
50
|
+
session map, session/update event normalizer, flight-recorder redaction, and
|
|
51
|
+
gated runtime pilots (Mistral + Grok + Devin) for first live ACP prompt routing.
|
|
52
|
+
Dormant by default.
|
|
53
|
+
|
|
54
|
+
### Changed
|
|
55
|
+
|
|
56
|
+
- **Strip internal MCP names from the published artifact.** Gateway-internal MCP
|
|
57
|
+
server names and host commands are centralised in `src/mcp-registry.ts` and
|
|
58
|
+
stripped at release time, so the published tarball contains zero internal names.
|
|
59
|
+
Enforced by an explicit, non-bypassable tarball token-guard
|
|
60
|
+
(`scripts/verify-no-internal-mcp.mjs`). `ClaudeMcpServerName` widens to `string`
|
|
61
|
+
and the request `mcpServers` schemas accept arbitrary names; no implicit default
|
|
62
|
+
server is injected.
|
|
63
|
+
- **Vibe `permissionMode` accepts any `--agent` name.** Replaces the closed
|
|
64
|
+
agent-mode enum (which had stale `chat`/`explore`/`lean` values) with an open
|
|
65
|
+
string — Vibe owns its agent registry (builtins plus install-gated and custom
|
|
66
|
+
agents), so the gateway passes the name through and lets Vibe validate it.
|
|
67
|
+
- **Gemini `mcpServers` accepted for approval tracking.** `gemini_request` no
|
|
68
|
+
longer rejects non-empty `mcpServers`; the names feed the approval policy but are
|
|
69
|
+
never passed to the Antigravity CLI (which manages its own MCP configuration).
|
|
70
|
+
|
|
71
|
+
### Security / supply chain
|
|
72
|
+
|
|
73
|
+
- `hono` pinned to `^4.12.25` via `overrides` (clears the advisories on 4.12.22),
|
|
74
|
+
with a hono-floor tripwire in the release security audit.
|
|
75
|
+
|
|
76
|
+
### Deps
|
|
77
|
+
|
|
78
|
+
- Dev-dependency bumps (keeps `type-is@2.1.0` out via the `body-parser` floor) and
|
|
79
|
+
a `github/codeql-action` group bump.
|
|
6
80
|
|
|
7
81
|
## [2.10.0] - 2026-06-15: Per-principal isolation on the request handlers
|
|
8
82
|
|
package/README.md
CHANGED
|
@@ -188,17 +188,47 @@ Every `*_request` and `*_request_async` tool accepts an optional `promptParts` f
|
|
|
188
188
|
|
|
189
189
|
`prompt` and `promptParts` are mutually exclusive — pass exactly one.
|
|
190
190
|
|
|
191
|
-
Per-CLI capability matrix:
|
|
191
|
+
Per-CLI capability matrix (prefix discipline is automatic via `promptParts` for all; explicit levers are provider-specific):
|
|
192
|
+
|
|
193
|
+
| CLI | Prefix discipline | Explicit lever(s) |
|
|
194
|
+
| ------- | ----------------- | ----------------- |
|
|
195
|
+
| claude | yes | `promptParts.cacheControl` + `outputFormat: "stream-json"` (Anthropic `cache_control` breakpoints on stable blocks; `ttl="1h"` forced) |
|
|
196
|
+
| codex | yes | none (OpenAI implicit) |
|
|
197
|
+
| gemini | yes | none (implicit server-side) |
|
|
198
|
+
| grok | yes | `compactionMode` / `compactionDetail` (context compaction: `summary|transcript|segments`; `segments` writes per-segment markdown) |
|
|
199
|
+
| mistral | yes | none (implicit) |
|
|
200
|
+
|
|
201
|
+
**Claude example (explicit cacheControl)**
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
claude_request({
|
|
205
|
+
promptParts: {
|
|
206
|
+
system: "You are a helpful code reviewer.",
|
|
207
|
+
context: "<long stable file dump>",
|
|
208
|
+
task: "Review the diff.",
|
|
209
|
+
cacheControl: { system: true, context: true } // task is never marked
|
|
210
|
+
},
|
|
211
|
+
outputFormat: "stream-json"
|
|
212
|
+
})
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Gateway emits the `stream-json` stdin path with `cache_control: {type:"ephemeral", ttl:"1h"}` on marked blocks only.
|
|
216
|
+
|
|
217
|
+
**Grok example (compaction)**
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
grok_request({
|
|
221
|
+
promptParts: { system: "...", context: "...", task: "..." },
|
|
222
|
+
compactionMode: "segments",
|
|
223
|
+
compactionDetail: "balanced"
|
|
224
|
+
})
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Emits `--compaction-mode segments --compaction-detail balanced`.
|
|
192
228
|
|
|
193
|
-
|
|
194
|
-
| ------- | ------------------------------------------ | ---------------------------------------------------------------------------- |
|
|
195
|
-
| claude | yes | yes, opt-in via `promptParts.cacheControl` and `outputFormat: "stream-json"` |
|
|
196
|
-
| codex | yes | n/a (OpenAI implicit cache, no CLI lever) |
|
|
197
|
-
| gemini | yes | n/a (implicit prefix cache server-side) |
|
|
198
|
-
| grok | yes | n/a (no surfaced cache lever) |
|
|
199
|
-
| mistral | yes | n/a (no surfaced cache lever) |
|
|
229
|
+
See `docs/personal-mcp/PROVIDER_CACHE_SURFACES.md` for full surfaces, telemetry differences (e.g. Grok `-p` vs ACP), exact stream-json payload shapes, and cross-LLM review notes.
|
|
200
230
|
|
|
201
|
-
Opt-in flags (all default off) live under `[cache_awareness]` in `~/.llm-cli-gateway/config.toml`.
|
|
231
|
+
Opt-in flags (all default off) live under `[cache_awareness]` in `~/.llm-cli-gateway/config.toml`.
|
|
202
232
|
|
|
203
233
|
### Reliability & Performance
|
|
204
234
|
|
|
@@ -278,8 +308,10 @@ Vibe-specific notes:
|
|
|
278
308
|
`VIBE_MODELS`, injects `VIBE_ACTIVE_MODEL` only when a model is explicitly
|
|
279
309
|
requested or Vibe config needs recovery, and retries once after a
|
|
280
310
|
model-not-found failure with refreshed discovery.
|
|
281
|
-
- **`permissionMode`
|
|
282
|
-
|
|
311
|
+
- **`permissionMode` is the Vibe `--agent` name.** Builtins are
|
|
312
|
+
`default | plan | accept-edits | auto-approve`; Vibe also accepts install-gated
|
|
313
|
+
builtins (e.g. `lean`) and custom agents from `~/.vibe/agents`, so any name is
|
|
314
|
+
passed through and Vibe validates availability. The gateway's
|
|
283
315
|
programmatic-mode default is `auto-approve`; pick a stricter mode
|
|
284
316
|
explicitly if you need approval gates.
|
|
285
317
|
- **`allowedTools` is allow-list only** — the gateway emits one
|
|
@@ -391,7 +423,7 @@ Execute a Claude Code request with optional session management.
|
|
|
391
423
|
- `excludeDynamicSystemPromptSections` (boolean, optional): Trim dynamic system prompt sections
|
|
392
424
|
- `approvalStrategy` (string, optional): `"legacy"` (default) or `"mcp_managed"`
|
|
393
425
|
- `approvalPolicy` (string, optional): `"strict"`, `"balanced"`, or `"permissive"`
|
|
394
|
-
- `mcpServers` (string[], optional):
|
|
426
|
+
- `mcpServers` (string[], optional): Names of MCP servers to expose to Claude (default: none). The gateway resolves each name to a launch command from its local registry / Codex MCP config; unknown names are reported as unavailable. Configure the servers your deployment uses in the gateway environment.
|
|
395
427
|
- `strictMcpConfig` (boolean, optional): Require Claude to use only supplied MCP config, default: true (request fails if any requested server is unavailable)
|
|
396
428
|
- `optimizePrompt` (boolean, optional): Optimize prompt for token efficiency (44% reduction), default: false
|
|
397
429
|
- `optimizeResponse` (boolean, optional): Optimize response for token efficiency (37% reduction), default: false
|
|
@@ -825,7 +857,7 @@ consumes = ["OUT:mcp-reconnected"]
|
|
|
825
857
|
Run a Mistral Vibe agentic coding request. Like `grok_request` in shape, but with Vibe's specific surface:
|
|
826
858
|
|
|
827
859
|
- `model` (string, optional): Vibe model alias (for example `mistral-medium-3.5` or `latest`). The resolved value is injected via the `VIBE_ACTIVE_MODEL` environment variable; omit it to let the gateway discover Vibe config and avoid stale hardcoded defaults.
|
|
828
|
-
- `permissionMode`: `default | plan | accept-edits | auto-approve
|
|
860
|
+
- `permissionMode`: the Vibe `--agent` name — builtins `default | plan | accept-edits | auto-approve`, or any install-gated/custom agent. Emitted as `--agent <name>`. Defaults to `auto-approve` in programmatic mode.
|
|
829
861
|
- `allowedTools` (string[], optional): One `--enabled-tools <tool>` flag per entry (allow-list only).
|
|
830
862
|
- `disallowedTools` (string[], optional): Accepted for parity with the other providers; ignored at the CLI boundary with a logged warning.
|
|
831
863
|
- `outputFormat` (string, optional): Vibe 2.x values are `"text"`, `"json"`, or `"streaming"`; legacy aliases `"plain"` and `"stream-json"` are accepted and normalized before spawn.
|
|
@@ -861,7 +893,7 @@ Async request tools accept the same approval strategy fields as their sync varia
|
|
|
861
893
|
|
|
862
894
|
- `approvalStrategy`: `"legacy"` (default) or `"mcp_managed"`
|
|
863
895
|
- `approvalPolicy`: `"strict"|"balanced"|"permissive"` override
|
|
864
|
-
- `mcpServers`:
|
|
896
|
+
- `mcpServers`: Names of requested MCP servers, resolved against the gateway's local registry / Codex MCP config
|
|
865
897
|
- `claude_request_async` also supports `strictMcpConfig` and fails fast when requested servers are unavailable
|
|
866
898
|
|
|
867
899
|
##### `llm_job_status`
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ContentBlock, SessionUpdateNotification } from "./types.js";
|
|
2
|
+
export type AcpProgressEvent = {
|
|
3
|
+
readonly kind: "agent_message";
|
|
4
|
+
readonly text: string;
|
|
5
|
+
} | {
|
|
6
|
+
readonly kind: "agent_thought";
|
|
7
|
+
readonly text: string;
|
|
8
|
+
} | {
|
|
9
|
+
readonly kind: "user_message";
|
|
10
|
+
readonly text: string;
|
|
11
|
+
} | {
|
|
12
|
+
readonly kind: "tool_call";
|
|
13
|
+
readonly toolCallId: string;
|
|
14
|
+
readonly title: string;
|
|
15
|
+
readonly status?: string;
|
|
16
|
+
readonly toolKind?: string;
|
|
17
|
+
} | {
|
|
18
|
+
readonly kind: "tool_update";
|
|
19
|
+
readonly toolCallId: string;
|
|
20
|
+
readonly status?: string;
|
|
21
|
+
readonly title?: string;
|
|
22
|
+
} | {
|
|
23
|
+
readonly kind: "plan";
|
|
24
|
+
readonly entryCount: number;
|
|
25
|
+
} | {
|
|
26
|
+
readonly kind: "mode";
|
|
27
|
+
readonly currentModeId: string;
|
|
28
|
+
} | {
|
|
29
|
+
readonly kind: "usage";
|
|
30
|
+
readonly size: number;
|
|
31
|
+
readonly used: number;
|
|
32
|
+
} | {
|
|
33
|
+
readonly kind: "other";
|
|
34
|
+
readonly sessionUpdate: string;
|
|
35
|
+
};
|
|
36
|
+
export declare function summarizeContentBlock(block: ContentBlock): string;
|
|
37
|
+
export declare function normalizeSessionUpdate(notification: SessionUpdateNotification): AcpProgressEvent;
|
|
38
|
+
export declare class AcpEventNormalizer {
|
|
39
|
+
private text;
|
|
40
|
+
handle(notification: SessionUpdateNotification): AcpProgressEvent;
|
|
41
|
+
get finalText(): string;
|
|
42
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
function str(value) {
|
|
2
|
+
return typeof value === "string" ? value : undefined;
|
|
3
|
+
}
|
|
4
|
+
export function summarizeContentBlock(block) {
|
|
5
|
+
if (block.type === "text") {
|
|
6
|
+
return str(block.text) ?? "";
|
|
7
|
+
}
|
|
8
|
+
return `[${block.type}]`;
|
|
9
|
+
}
|
|
10
|
+
function chunkText(update) {
|
|
11
|
+
const content = update.content;
|
|
12
|
+
if (content && typeof content === "object") {
|
|
13
|
+
return summarizeContentBlock(content);
|
|
14
|
+
}
|
|
15
|
+
return "";
|
|
16
|
+
}
|
|
17
|
+
export function normalizeSessionUpdate(notification) {
|
|
18
|
+
const update = notification.update;
|
|
19
|
+
const variant = str(update.sessionUpdate) ?? "";
|
|
20
|
+
switch (variant) {
|
|
21
|
+
case "agent_message_chunk":
|
|
22
|
+
return { kind: "agent_message", text: chunkText(update) };
|
|
23
|
+
case "agent_thought_chunk":
|
|
24
|
+
return { kind: "agent_thought", text: chunkText(update) };
|
|
25
|
+
case "user_message_chunk":
|
|
26
|
+
return { kind: "user_message", text: chunkText(update) };
|
|
27
|
+
case "tool_call":
|
|
28
|
+
return {
|
|
29
|
+
kind: "tool_call",
|
|
30
|
+
toolCallId: str(update.toolCallId) ?? "",
|
|
31
|
+
title: str(update.title) ?? "",
|
|
32
|
+
status: str(update.status),
|
|
33
|
+
toolKind: str(update.kind),
|
|
34
|
+
};
|
|
35
|
+
case "tool_call_update":
|
|
36
|
+
return {
|
|
37
|
+
kind: "tool_update",
|
|
38
|
+
toolCallId: str(update.toolCallId) ?? "",
|
|
39
|
+
status: str(update.status),
|
|
40
|
+
title: str(update.title),
|
|
41
|
+
};
|
|
42
|
+
case "plan":
|
|
43
|
+
return {
|
|
44
|
+
kind: "plan",
|
|
45
|
+
entryCount: Array.isArray(update.entries) ? update.entries.length : 0,
|
|
46
|
+
};
|
|
47
|
+
case "current_mode_update":
|
|
48
|
+
return { kind: "mode", currentModeId: str(update.currentModeId) ?? "" };
|
|
49
|
+
case "usage_update":
|
|
50
|
+
return {
|
|
51
|
+
kind: "usage",
|
|
52
|
+
size: typeof update.size === "number" ? update.size : 0,
|
|
53
|
+
used: typeof update.used === "number" ? update.used : 0,
|
|
54
|
+
};
|
|
55
|
+
default:
|
|
56
|
+
return { kind: "other", sessionUpdate: variant };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export class AcpEventNormalizer {
|
|
60
|
+
text = "";
|
|
61
|
+
handle(notification) {
|
|
62
|
+
const event = normalizeSessionUpdate(notification);
|
|
63
|
+
if (event.kind === "agent_message") {
|
|
64
|
+
this.text += event.text;
|
|
65
|
+
}
|
|
66
|
+
return event;
|
|
67
|
+
}
|
|
68
|
+
get finalText() {
|
|
69
|
+
return this.text;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ContentBlock } from "./types.js";
|
|
2
|
+
import type { FlightLogResult, FlightLogStart } from "../flight-recorder.js";
|
|
3
|
+
import type { CliType } from "../session-manager.js";
|
|
4
|
+
export declare function summarizeAcpPromptForFlight(prompt: ReadonlyArray<ContentBlock>): string;
|
|
5
|
+
export declare function summarizeAcpResponseForFlight(responseText: string): string;
|
|
6
|
+
export declare function redactAcpTextForFlight(text: string): string;
|
|
7
|
+
export interface AcpFlightStartParams {
|
|
8
|
+
readonly correlationId: string;
|
|
9
|
+
readonly provider: CliType;
|
|
10
|
+
readonly model: string;
|
|
11
|
+
readonly prompt: ReadonlyArray<ContentBlock>;
|
|
12
|
+
readonly gatewaySessionId?: string;
|
|
13
|
+
readonly asyncJobId?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function buildAcpFlightStart(params: AcpFlightStartParams): FlightLogStart;
|
|
16
|
+
export interface AcpFlightResultParams {
|
|
17
|
+
readonly responseText: string;
|
|
18
|
+
readonly durationMs: number;
|
|
19
|
+
readonly status: "completed" | "failed";
|
|
20
|
+
readonly exitCode: number;
|
|
21
|
+
readonly inputTokens?: number;
|
|
22
|
+
readonly outputTokens?: number;
|
|
23
|
+
readonly errorMessage?: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function buildAcpFlightResult(params: AcpFlightResultParams): FlightLogResult;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { redactAcpMessage } from "./errors.js";
|
|
2
|
+
import { isGatewaySessionId } from "./session-map.js";
|
|
3
|
+
import { redactSecrets } from "../secret-redaction.js";
|
|
4
|
+
export function summarizeAcpPromptForFlight(prompt) {
|
|
5
|
+
const n = prompt.length;
|
|
6
|
+
return `[acp prompt: ${n} content block${n === 1 ? "" : "s"}]`;
|
|
7
|
+
}
|
|
8
|
+
export function summarizeAcpResponseForFlight(responseText) {
|
|
9
|
+
return `[acp response: ${responseText.length} char${responseText.length === 1 ? "" : "s"}]`;
|
|
10
|
+
}
|
|
11
|
+
export function redactAcpTextForFlight(text) {
|
|
12
|
+
return redactSecrets(redactAcpMessage(text));
|
|
13
|
+
}
|
|
14
|
+
export function buildAcpFlightStart(params) {
|
|
15
|
+
const sessionId = params.gatewaySessionId !== undefined && isGatewaySessionId(params.gatewaySessionId)
|
|
16
|
+
? params.gatewaySessionId
|
|
17
|
+
: undefined;
|
|
18
|
+
return {
|
|
19
|
+
correlationId: params.correlationId,
|
|
20
|
+
cli: params.provider,
|
|
21
|
+
model: params.model,
|
|
22
|
+
prompt: summarizeAcpPromptForFlight(params.prompt),
|
|
23
|
+
sessionId,
|
|
24
|
+
asyncJobId: params.asyncJobId,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export function buildAcpFlightResult(params) {
|
|
28
|
+
return {
|
|
29
|
+
response: summarizeAcpResponseForFlight(params.responseText),
|
|
30
|
+
durationMs: params.durationMs,
|
|
31
|
+
status: params.status,
|
|
32
|
+
exitCode: params.exitCode,
|
|
33
|
+
retryCount: 0,
|
|
34
|
+
circuitBreakerState: "closed",
|
|
35
|
+
optimizationApplied: false,
|
|
36
|
+
inputTokens: params.inputTokens,
|
|
37
|
+
outputTokens: params.outputTokens,
|
|
38
|
+
errorMessage: params.errorMessage !== undefined ? redactAcpTextForFlight(params.errorMessage) : undefined,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { HostCallbackContext, HostServices } from "./client.js";
|
|
2
|
+
import type { ReadTextFileRequest, ReadTextFileResponse, RequestPermissionRequest, RequestPermissionResponse, WriteTextFileRequest, WriteTextFileResponse } from "./types.js";
|
|
3
|
+
import type { Logger } from "../logger.js";
|
|
4
|
+
export type AcpPermissionDecider = (request: RequestPermissionRequest, context: HostCallbackContext) => Promise<RequestPermissionResponse>;
|
|
5
|
+
export interface GatewayHostServicesOptions {
|
|
6
|
+
readonly logger?: Logger;
|
|
7
|
+
readonly permissionDecider?: AcpPermissionDecider;
|
|
8
|
+
}
|
|
9
|
+
export declare class GatewayHostServices implements HostServices {
|
|
10
|
+
private readonly logger;
|
|
11
|
+
private readonly permissionDecider?;
|
|
12
|
+
constructor(options?: GatewayHostServicesOptions);
|
|
13
|
+
readTextFile(_request: ReadTextFileRequest, context: HostCallbackContext): Promise<ReadTextFileResponse>;
|
|
14
|
+
writeTextFile(_request: WriteTextFileRequest, context: HostCallbackContext): Promise<WriteTextFileResponse>;
|
|
15
|
+
requestPermission(request: RequestPermissionRequest, context: HostCallbackContext): Promise<RequestPermissionResponse>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { AcpPermissionDeniedError } from "./errors.js";
|
|
2
|
+
import { noopLogger } from "../logger.js";
|
|
3
|
+
export class GatewayHostServices {
|
|
4
|
+
logger;
|
|
5
|
+
permissionDecider;
|
|
6
|
+
constructor(options = {}) {
|
|
7
|
+
this.logger = options.logger ?? noopLogger;
|
|
8
|
+
this.permissionDecider = options.permissionDecider;
|
|
9
|
+
}
|
|
10
|
+
async readTextFile(_request, context) {
|
|
11
|
+
this.logger.info("acp.host.read_denied", { provider: context.provider });
|
|
12
|
+
throw new AcpPermissionDeniedError(context.provider, "filesystem read host service is disabled", { provider: context.provider, debug: { method: context.method } });
|
|
13
|
+
}
|
|
14
|
+
async writeTextFile(_request, context) {
|
|
15
|
+
this.logger.info("acp.host.write_denied", { provider: context.provider });
|
|
16
|
+
throw new AcpPermissionDeniedError(context.provider, "filesystem write host service is disabled", { provider: context.provider, debug: { method: context.method } });
|
|
17
|
+
}
|
|
18
|
+
async requestPermission(request, context) {
|
|
19
|
+
if (this.permissionDecider) {
|
|
20
|
+
return this.permissionDecider(request, context);
|
|
21
|
+
}
|
|
22
|
+
this.logger.info("acp.permission.denied", {
|
|
23
|
+
provider: context.provider,
|
|
24
|
+
reason: "no_approval_bridge",
|
|
25
|
+
optionCount: request.options.length,
|
|
26
|
+
});
|
|
27
|
+
return { outcome: { outcome: "cancelled" } };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { HostCallbackContext } from "./client.js";
|
|
2
|
+
import type { RequestPermissionRequest, RequestPermissionResponse } from "./types.js";
|
|
3
|
+
import { ApprovalManager, type ApprovalCli, type ApprovalPolicy } from "../approval-manager.js";
|
|
4
|
+
import type { Logger } from "../logger.js";
|
|
5
|
+
export type AcpPermissionCategory = "read" | "write" | "execute" | "other";
|
|
6
|
+
export interface AcpPermissionBridgeDeps {
|
|
7
|
+
readonly approvalManager: ApprovalManager;
|
|
8
|
+
readonly provider: ApprovalCli;
|
|
9
|
+
readonly allowWrite?: boolean;
|
|
10
|
+
readonly allowTerminal?: boolean;
|
|
11
|
+
readonly policy?: ApprovalPolicy;
|
|
12
|
+
readonly logger?: Logger;
|
|
13
|
+
}
|
|
14
|
+
export declare function categorizeToolCall(toolCall: Record<string, unknown>): AcpPermissionCategory;
|
|
15
|
+
export declare function createAcpPermissionDecider(deps: AcpPermissionBridgeDeps): (request: RequestPermissionRequest, context: HostCallbackContext) => Promise<RequestPermissionResponse>;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { noopLogger } from "../logger.js";
|
|
2
|
+
const WRITE_KINDS = new Set(["edit", "delete", "move"]);
|
|
3
|
+
const EXECUTE_KINDS = new Set(["execute"]);
|
|
4
|
+
const READ_KINDS = new Set(["read", "search", "think"]);
|
|
5
|
+
export function categorizeToolCall(toolCall) {
|
|
6
|
+
const kind = typeof toolCall.kind === "string" ? toolCall.kind.toLowerCase() : "";
|
|
7
|
+
if (WRITE_KINDS.has(kind))
|
|
8
|
+
return "write";
|
|
9
|
+
if (EXECUTE_KINDS.has(kind))
|
|
10
|
+
return "execute";
|
|
11
|
+
if (READ_KINDS.has(kind))
|
|
12
|
+
return "read";
|
|
13
|
+
return "other";
|
|
14
|
+
}
|
|
15
|
+
function isAllowOption(option) {
|
|
16
|
+
const kind = typeof option.kind === "string" ? option.kind.toLowerCase() : "";
|
|
17
|
+
return kind.startsWith("allow");
|
|
18
|
+
}
|
|
19
|
+
const CANCELLED = { outcome: { outcome: "cancelled" } };
|
|
20
|
+
export function createAcpPermissionDecider(deps) {
|
|
21
|
+
const logger = deps.logger ?? noopLogger;
|
|
22
|
+
return async (request, _context) => {
|
|
23
|
+
const category = categorizeToolCall(request.toolCall);
|
|
24
|
+
if (category === "write" && deps.allowWrite !== true) {
|
|
25
|
+
logger.info("acp.permission.denied", {
|
|
26
|
+
provider: deps.provider,
|
|
27
|
+
category,
|
|
28
|
+
reason: "write_disabled",
|
|
29
|
+
});
|
|
30
|
+
return CANCELLED;
|
|
31
|
+
}
|
|
32
|
+
if (category === "execute" && deps.allowTerminal !== true) {
|
|
33
|
+
logger.info("acp.permission.denied", {
|
|
34
|
+
provider: deps.provider,
|
|
35
|
+
category,
|
|
36
|
+
reason: "terminal_disabled",
|
|
37
|
+
});
|
|
38
|
+
return CANCELLED;
|
|
39
|
+
}
|
|
40
|
+
if (category === "other") {
|
|
41
|
+
logger.info("acp.permission.denied", {
|
|
42
|
+
provider: deps.provider,
|
|
43
|
+
category,
|
|
44
|
+
reason: "unknown_kind_denied",
|
|
45
|
+
});
|
|
46
|
+
return CANCELLED;
|
|
47
|
+
}
|
|
48
|
+
let approved;
|
|
49
|
+
try {
|
|
50
|
+
const record = deps.approvalManager.decide({
|
|
51
|
+
cli: deps.provider,
|
|
52
|
+
operation: `acp_permission:${category}`,
|
|
53
|
+
prompt: `ACP permission request from ${deps.provider}`,
|
|
54
|
+
bypassRequested: false,
|
|
55
|
+
fullAuto: false,
|
|
56
|
+
requestedMcpServers: [],
|
|
57
|
+
policy: deps.policy,
|
|
58
|
+
metadata: { acp: true, category, optionCount: request.options.length },
|
|
59
|
+
});
|
|
60
|
+
approved = record.status === "approved";
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
logger.error("acp.permission.decide_error", {
|
|
64
|
+
provider: deps.provider,
|
|
65
|
+
category,
|
|
66
|
+
errorClass: err instanceof Error ? err.name : "unknown",
|
|
67
|
+
});
|
|
68
|
+
return CANCELLED;
|
|
69
|
+
}
|
|
70
|
+
if (!approved) {
|
|
71
|
+
logger.info("acp.permission.denied", {
|
|
72
|
+
provider: deps.provider,
|
|
73
|
+
category,
|
|
74
|
+
reason: "approval_denied",
|
|
75
|
+
});
|
|
76
|
+
return CANCELLED;
|
|
77
|
+
}
|
|
78
|
+
const allow = request.options.find(isAllowOption);
|
|
79
|
+
if (!allow) {
|
|
80
|
+
logger.info("acp.permission.denied", {
|
|
81
|
+
provider: deps.provider,
|
|
82
|
+
category,
|
|
83
|
+
reason: "no_allow_option",
|
|
84
|
+
});
|
|
85
|
+
return CANCELLED;
|
|
86
|
+
}
|
|
87
|
+
logger.info("acp.permission.approved", { provider: deps.provider, category });
|
|
88
|
+
return { outcome: { outcome: "selected", optionId: allow.optionId } };
|
|
89
|
+
};
|
|
90
|
+
}
|
|
@@ -117,6 +117,8 @@ export class AcpProcessManager {
|
|
|
117
117
|
callbacks: options.callbacks,
|
|
118
118
|
idleTimeoutMs: options.idleTimeoutMs ?? this.config.processIdleTimeoutMs,
|
|
119
119
|
initializeTimeoutMs: this.config.initializeTimeoutMs,
|
|
120
|
+
sessionNewTimeoutMs: this.config.sessionNewTimeoutMs,
|
|
121
|
+
promptTimeoutMs: this.config.promptTimeoutMs,
|
|
120
122
|
onTerminal: m => this.live.delete(m),
|
|
121
123
|
});
|
|
122
124
|
this.live.add(managed);
|
|
@@ -192,7 +194,11 @@ class ManagedProcessImpl {
|
|
|
192
194
|
hostServices: options.hostServices,
|
|
193
195
|
callbacks: options.callbacks,
|
|
194
196
|
logger: this.logger,
|
|
195
|
-
timeouts: {
|
|
197
|
+
timeouts: {
|
|
198
|
+
initializeMs: options.initializeTimeoutMs,
|
|
199
|
+
sessionNewMs: options.sessionNewTimeoutMs,
|
|
200
|
+
promptMs: options.promptTimeoutMs,
|
|
201
|
+
},
|
|
196
202
|
});
|
|
197
203
|
options.child.on("exit", (code, signal) => this.handleExit(code, signal));
|
|
198
204
|
options.child.on("error", err => this.handleSpawnError(err));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CliType } from "../session-manager.js";
|
|
2
|
-
export type AcpProviderStatus = "native_smoke_passed" | "adapter_mediated_deferred" | "absent_watchlist";
|
|
2
|
+
export type AcpProviderStatus = "native_smoke_passed" | "native_candidate" | "adapter_mediated_deferred" | "absent_watchlist";
|
|
3
3
|
export type AcpSupportKind = "native" | "adapter_mediated" | "none";
|
|
4
4
|
export interface AcpEntrypoint {
|
|
5
5
|
readonly command: string;
|
|
@@ -4,7 +4,7 @@ const ACP_PROVIDER_REGISTRY = Object.freeze({
|
|
|
4
4
|
displayName: "Mistral Vibe",
|
|
5
5
|
status: "native_smoke_passed",
|
|
6
6
|
supportKind: "native",
|
|
7
|
-
targetVersion: "vibe 2.
|
|
7
|
+
targetVersion: "vibe 2.17.1",
|
|
8
8
|
entrypoint: Object.freeze({ command: "vibe-acp", args: Object.freeze([]) }),
|
|
9
9
|
runtimeEnabledDefault: false,
|
|
10
10
|
shipRuntimePilot: true,
|
|
@@ -17,7 +17,7 @@ const ACP_PROVIDER_REGISTRY = Object.freeze({
|
|
|
17
17
|
displayName: "xAI Grok CLI",
|
|
18
18
|
status: "native_smoke_passed",
|
|
19
19
|
supportKind: "native",
|
|
20
|
-
targetVersion: "grok 0.2.
|
|
20
|
+
targetVersion: "grok 0.2.60 (474c2bbfc)",
|
|
21
21
|
entrypoint: Object.freeze({ command: "grok", args: Object.freeze(["agent", "stdio"]) }),
|
|
22
22
|
runtimeEnabledDefault: false,
|
|
23
23
|
shipRuntimePilot: true,
|
|
@@ -30,7 +30,7 @@ const ACP_PROVIDER_REGISTRY = Object.freeze({
|
|
|
30
30
|
displayName: "OpenAI Codex CLI",
|
|
31
31
|
status: "adapter_mediated_deferred",
|
|
32
32
|
supportKind: "adapter_mediated",
|
|
33
|
-
targetVersion: "codex-cli 0.
|
|
33
|
+
targetVersion: "codex-cli 0.141.0",
|
|
34
34
|
entrypoint: null,
|
|
35
35
|
runtimeEnabledDefault: false,
|
|
36
36
|
shipRuntimePilot: false,
|
|
@@ -43,7 +43,7 @@ const ACP_PROVIDER_REGISTRY = Object.freeze({
|
|
|
43
43
|
displayName: "Anthropic Claude Code",
|
|
44
44
|
status: "adapter_mediated_deferred",
|
|
45
45
|
supportKind: "adapter_mediated",
|
|
46
|
-
targetVersion: "claude 2.1.
|
|
46
|
+
targetVersion: "claude 2.1.185",
|
|
47
47
|
entrypoint: null,
|
|
48
48
|
runtimeEnabledDefault: false,
|
|
49
49
|
shipRuntimePilot: false,
|
|
@@ -56,7 +56,7 @@ const ACP_PROVIDER_REGISTRY = Object.freeze({
|
|
|
56
56
|
displayName: "Google Antigravity",
|
|
57
57
|
status: "absent_watchlist",
|
|
58
58
|
supportKind: "none",
|
|
59
|
-
targetVersion: "agy 1.0.
|
|
59
|
+
targetVersion: "agy 1.0.10",
|
|
60
60
|
entrypoint: null,
|
|
61
61
|
runtimeEnabledDefault: false,
|
|
62
62
|
shipRuntimePilot: false,
|
|
@@ -64,6 +64,19 @@ const ACP_PROVIDER_REGISTRY = Object.freeze({
|
|
|
64
64
|
adapterCandidates: Object.freeze([]),
|
|
65
65
|
caveat: "No ACP flag or subcommand at the target version. Legacy Gemini CLI ACP evidence does not transfer to Antigravity agy. Watchlist only.",
|
|
66
66
|
}),
|
|
67
|
+
devin: Object.freeze({
|
|
68
|
+
provider: "devin",
|
|
69
|
+
displayName: "Cognition Devin CLI",
|
|
70
|
+
status: "native_smoke_passed",
|
|
71
|
+
supportKind: "native",
|
|
72
|
+
targetVersion: "devin 2026.7.23 (3bd47f77)",
|
|
73
|
+
entrypoint: Object.freeze({ command: "devin", args: Object.freeze(["acp"]) }),
|
|
74
|
+
runtimeEnabledDefault: false,
|
|
75
|
+
shipRuntimePilot: true,
|
|
76
|
+
runtimePriority: 3,
|
|
77
|
+
adapterCandidates: Object.freeze([]),
|
|
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
|
+
}),
|
|
67
80
|
});
|
|
68
81
|
export function getAcpProviderRegistry() {
|
|
69
82
|
return ACP_PROVIDER_REGISTRY;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type AcpSpawnFn, type ProcessEnv } from "./process-manager.js";
|
|
2
|
+
import type { ApprovalManager } from "../approval-manager.js";
|
|
3
|
+
import type { AcpConfig } from "../config.js";
|
|
4
|
+
import type { FlightLogResult, FlightLogStart } from "../flight-recorder.js";
|
|
5
|
+
import type { Logger } from "../logger.js";
|
|
6
|
+
import type { CliType, ISessionManager } from "../session-manager.js";
|
|
7
|
+
export interface AcpFlightSink {
|
|
8
|
+
logStart(entry: FlightLogStart): void;
|
|
9
|
+
logComplete(correlationId: string, result: FlightLogResult): void;
|
|
10
|
+
}
|
|
11
|
+
export interface AcpRuntimeDeps {
|
|
12
|
+
readonly config: AcpConfig;
|
|
13
|
+
readonly sessionManager: ISessionManager;
|
|
14
|
+
readonly approvalManager: ApprovalManager;
|
|
15
|
+
readonly flightRecorder?: AcpFlightSink;
|
|
16
|
+
readonly logger?: Logger;
|
|
17
|
+
readonly spawn?: AcpSpawnFn;
|
|
18
|
+
readonly baseEnv?: ProcessEnv;
|
|
19
|
+
readonly now?: () => number;
|
|
20
|
+
}
|
|
21
|
+
export interface AcpRunRequest {
|
|
22
|
+
readonly provider: CliType;
|
|
23
|
+
readonly prompt: string;
|
|
24
|
+
readonly model?: string;
|
|
25
|
+
readonly sessionId?: string;
|
|
26
|
+
readonly cwd?: string;
|
|
27
|
+
readonly correlationId: string;
|
|
28
|
+
}
|
|
29
|
+
export interface AcpRunResult {
|
|
30
|
+
readonly text: string;
|
|
31
|
+
readonly gatewaySessionId: string;
|
|
32
|
+
readonly protocolVersion: number | null;
|
|
33
|
+
readonly durationMs: number;
|
|
34
|
+
}
|
|
35
|
+
export declare function runAcpRequest(deps: AcpRuntimeDeps, req: AcpRunRequest): Promise<AcpRunResult>;
|