llm-cli-gateway 2.13.2 → 2.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +139 -0
- package/README.md +68 -29
- package/dist/acp/client.d.ts +29 -1
- package/dist/acp/client.js +78 -4
- package/dist/acp/errors.d.ts +9 -1
- package/dist/acp/errors.js +19 -0
- package/dist/acp/event-normalizer.d.ts +12 -0
- package/dist/acp/event-normalizer.js +16 -0
- package/dist/acp/flight-redaction.d.ts +3 -0
- package/dist/acp/flight-redaction.js +3 -0
- package/dist/acp/permission-bridge.js +11 -5
- package/dist/acp/process-manager.d.ts +2 -1
- package/dist/acp/process-manager.js +43 -4
- package/dist/acp/provider-registry.js +19 -11
- package/dist/acp/runtime.d.ts +8 -0
- package/dist/acp/runtime.js +47 -4
- package/dist/acp/types.d.ts +3083 -55
- package/dist/acp/types.js +242 -5
- package/dist/async-job-manager.d.ts +38 -1
- package/dist/async-job-manager.js +287 -20
- package/dist/codex-json-parser.d.ts +1 -0
- package/dist/codex-json-parser.js +6 -0
- package/dist/config.d.ts +19 -0
- package/dist/config.js +84 -1
- package/dist/flight-recorder.d.ts +2 -0
- package/dist/flight-recorder.js +20 -0
- package/dist/gemini-json-parser.d.ts +2 -0
- package/dist/gemini-json-parser.js +45 -8
- package/dist/grok-json-parser.d.ts +14 -0
- package/dist/grok-json-parser.js +156 -0
- package/dist/index.d.ts +47 -2
- package/dist/index.js +504 -122
- package/dist/job-store.d.ts +119 -11
- package/dist/job-store.js +372 -42
- package/dist/model-registry.d.ts +1 -0
- package/dist/model-registry.js +46 -0
- package/dist/oauth.js +2 -2
- package/dist/postgres-job-store-worker.d.ts +1 -0
- package/dist/postgres-job-store-worker.js +444 -0
- package/dist/pricing.d.ts +3 -2
- package/dist/provider-acp-capabilities.d.ts +52 -0
- package/dist/provider-acp-capabilities.js +101 -0
- package/dist/provider-admin-tools.d.ts +100 -0
- package/dist/provider-admin-tools.js +572 -0
- package/dist/provider-capability-cache.d.ts +46 -0
- package/dist/provider-capability-cache.js +248 -0
- package/dist/provider-capability-discovery.d.ts +85 -0
- package/dist/provider-capability-discovery.js +461 -0
- package/dist/provider-capability-resolver.d.ts +29 -0
- package/dist/provider-capability-resolver.js +92 -0
- package/dist/provider-definition-assertions.d.ts +9 -0
- package/dist/provider-definition-assertions.js +147 -0
- package/dist/provider-definitions.d.ts +127 -0
- package/dist/provider-definitions.js +758 -0
- package/dist/provider-help-parser.d.ts +34 -0
- package/dist/provider-help-parser.js +203 -0
- package/dist/provider-model-discovery.d.ts +30 -0
- package/dist/provider-model-discovery.js +229 -0
- package/dist/provider-output-metadata.d.ts +7 -0
- package/dist/provider-output-metadata.js +68 -0
- package/dist/provider-schema-builder.d.ts +22 -0
- package/dist/provider-schema-builder.js +55 -0
- package/dist/provider-surface-generator.d.ts +98 -0
- package/dist/provider-surface-generator.js +140 -0
- package/dist/provider-tool-capabilities.d.ts +3 -2
- package/dist/provider-tool-capabilities.js +77 -62
- package/dist/request-helpers.d.ts +37 -4
- package/dist/request-helpers.js +134 -0
- package/dist/resources.d.ts +6 -1
- package/dist/resources.js +64 -192
- package/dist/session-manager.js +18 -11
- package/dist/sqlite-driver.d.ts +1 -1
- package/dist/sqlite-driver.js +2 -1
- package/dist/stream-json-parser.d.ts +1 -0
- package/dist/stream-json-parser.js +3 -0
- package/dist/upstream-contracts.d.ts +17 -1
- package/dist/upstream-contracts.js +209 -36
- package/npm-shrinkwrap.json +2 -2
- package/package.json +8 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,145 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the llm-cli-gateway project.
|
|
4
4
|
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [2.14.0] - 2026-07-03
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **Durable instance-lease orphan recovery (#139).** The blanket
|
|
12
|
+
`markOrphanedOnStartup` sweep in the `AsyncJobManager` constructor rewrote
|
|
13
|
+
every `running` row to `orphaned`, so on a SHARED store (`backend =
|
|
14
|
+
"postgres"`) a fresh instance (especially an ephemeral stdio spawn)
|
|
15
|
+
transiently orphaned other live instances' in-flight jobs (a `running` ->
|
|
16
|
+
`orphaned` -> `completed` flap a poller can trip on). The sweep is now a
|
|
17
|
+
per-job fencing lease: each instance registers a lease and advances a
|
|
18
|
+
`jobs.lease_deadline` on every heartbeat, and the sweep orphans a
|
|
19
|
+
`queued`/`running` job only when its own `lease_deadline` has expired, never
|
|
20
|
+
because a different live instance started. Because heartbeat and sweep are
|
|
21
|
+
both `UPDATE`s on the same `jobs` rows, they serialize on the row lock
|
|
22
|
+
(Postgres READ COMMITTED) and are trivially serial under single-writer sqlite,
|
|
23
|
+
so no job whose owner heartbeated within the lease TTL is ever swept. A
|
|
24
|
+
genuinely stale-then-reviving owner self-heals to the correct terminal state
|
|
25
|
+
via the guarded `recordComplete` and a flight-recorder reconcile. The fix is
|
|
26
|
+
transport-aware (an extra `httpJobGrace` for no-pid http jobs, an advisory
|
|
27
|
+
never-vetoing `kill(pid,0)` for same-host process jobs), durable-admission is
|
|
28
|
+
fail-closed (a failed `recordStart`/`registerInstance` fails the request and
|
|
29
|
+
releases the limiter permit), and graceful shutdown drains in-flight terminal
|
|
30
|
+
writes before deregistering the lease. Additive schema (`jobs.owner_instance`,
|
|
31
|
+
`jobs.lease_deadline`, a `gateway_instances` table), auto-created on both
|
|
32
|
+
sqlite and postgres, no data migration.
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
|
|
36
|
+
- **`[persistence].ownsOrphanRecovery` is deprecated (#139).** The interim gate
|
|
37
|
+
(PR #140) that let one designated `postgres` instance own the blanket startup
|
|
38
|
+
sweep is superseded by the durable lease above, which is safe to run from
|
|
39
|
+
every instance. The flag is still parsed (so existing configs do not error)
|
|
40
|
+
and now emits a one-time deprecation warning; it no longer changes behaviour
|
|
41
|
+
and will be removed in a later release. New `[persistence]` knobs tune the
|
|
42
|
+
lease: `instanceHeartbeatMs` (15000), `instanceLeaseTtlMs` (90000),
|
|
43
|
+
`httpJobGraceMs` (300000), `orphanSweepIntervalMs` (30000), `instanceGcMs`
|
|
44
|
+
(3600000), validated so `instanceLeaseTtlMs >= 2 * instanceHeartbeatMs` and
|
|
45
|
+
`httpJobGraceMs >= instanceLeaseTtlMs`.
|
|
46
|
+
|
|
47
|
+
## [2.14.0-rc.1] - 2026-07-03: full-featured provider integration + security-review hardening
|
|
48
|
+
|
|
49
|
+
Release candidate. Every non-Cursor provider (Claude, Codex, Gemini, Grok,
|
|
50
|
+
Mistral, Devin) becomes a first-class, provider-definition-driven integration,
|
|
51
|
+
and a security review of the change set is folded in. Cursor stays working via
|
|
52
|
+
the shared generation and is maintenance-only.
|
|
53
|
+
|
|
54
|
+
### Added
|
|
55
|
+
|
|
56
|
+
- **Provider definitions as the single source of truth** (`src/provider-definitions.ts`):
|
|
57
|
+
one `ProviderDefinition` per CLI type drives request schemas, resources,
|
|
58
|
+
discovery, and capabilities, with compile-time exhaustiveness assertions and a
|
|
59
|
+
`scripts/provider-surfaces-check.mjs` ratchet wired into `npm run check`.
|
|
60
|
+
- **Runtime provider capability discovery with an on-disk cache**: an injectable
|
|
61
|
+
probe runner (no shell interpolation of caller input), a cache keyed on
|
|
62
|
+
discovery checksums with a shape-driven secret scrubber and Zod-validated
|
|
63
|
+
reads, and a TTL that bounds staleness so an upgraded or moved provider CLI is
|
|
64
|
+
re-discovered instead of served stale.
|
|
65
|
+
- **Registry-generated `models://` and `sessions://` resources** for every
|
|
66
|
+
provider (Devin and Cursor now first-class), plus **live model discovery**
|
|
67
|
+
wired into `models://<provider>` and `list_models` via a memoized resolver
|
|
68
|
+
with a fire-and-forget startup warm (reads never block on a spawn).
|
|
69
|
+
- **Complete CLI request-field coverage** across providers, guarded by
|
|
70
|
+
upstream-contract and coverage-closure tests; interactive/admin-only flags are
|
|
71
|
+
represented as typed capability facts rather than silently dropped.
|
|
72
|
+
- **Discovery-driven native ACP surface**: Grok, Mistral, and Devin route
|
|
73
|
+
natively over ACP when enabled (no masquerade), with capability-gated session
|
|
74
|
+
methods and state-mutating session ops gated behind
|
|
75
|
+
`acp.allow_mutating_session_ops`.
|
|
76
|
+
- **Provider admin tools**: `provider_admin_list` / `provider_admin_run`
|
|
77
|
+
(read-only) and `provider_admin_mutate` (gated). Availability is
|
|
78
|
+
discovery-driven; mutating ops require `[admin] allow_mutating_cli_admin_ops`
|
|
79
|
+
and, for remote callers, `LLM_GATEWAY_CLI_ADMIN=1` plus the `cli:admin` OAuth
|
|
80
|
+
scope, routed through the approval manager with a fail-closed pre-spawn audit.
|
|
81
|
+
- **Postgres job store** (`PostgresJobStore`): a durable async-job and
|
|
82
|
+
validation-run/receipt backend over the `pg` pool, driven through a dedicated
|
|
83
|
+
worker thread (`postgres-job-store-worker.ts`). Selectable via
|
|
84
|
+
`persistence.backend = "postgres"`; the ephemeral memory backend deliberately
|
|
85
|
+
does not implement the validation-run surface.
|
|
86
|
+
- **Search-engine discoverability for the docs site**: Google Search Console
|
|
87
|
+
and Bing IndexNow submission (`npm run search:*`), plus `sitemap.xml`,
|
|
88
|
+
`robots.txt`, and `llms.txt`, alongside a marketing-site refresh.
|
|
89
|
+
|
|
90
|
+
### Changed
|
|
91
|
+
|
|
92
|
+
- **Unified provider output/event normalization**: a real Grok JSON parser, and
|
|
93
|
+
`stopReason`/error signals threaded across the CLI parsers and the ACP event
|
|
94
|
+
normalizer. The provider-minted session id and terminal stop reason are
|
|
95
|
+
preserved through the async job and flight recorder so a deferred job resumes
|
|
96
|
+
with the real provider session id.
|
|
97
|
+
- **`provider_tool_capabilities`** now reports ACP `runtimeEnabled` from the
|
|
98
|
+
resolved config (previously hardcoded), with entrypoint/version/mediation
|
|
99
|
+
derived from the registry.
|
|
100
|
+
- Docs: README provider-capability table, per-provider skills refreshed to the
|
|
101
|
+
installed CLI versions, ACP documented as sync-only for now, and the
|
|
102
|
+
`provider-acp://` resource registered on the MCP server.
|
|
103
|
+
|
|
104
|
+
### Fixed
|
|
105
|
+
|
|
106
|
+
- Codex and Gemini clean exits that actually carried a failed terminal event
|
|
107
|
+
now surface a warning instead of being reported as a silent empty success;
|
|
108
|
+
the ACP transport likewise flags a refusal/cancelled/truncated turn.
|
|
109
|
+
- Codex surfaces the real `turn.failed` reason on a non-zero exit instead of a
|
|
110
|
+
partial agent message.
|
|
111
|
+
- json-mode output parsers tolerate a stray banner line around the JSON object
|
|
112
|
+
(telemetry is no longer all-or-nothing), and the Gemini stream replaces rather
|
|
113
|
+
than doubles a consolidated non-delta final message.
|
|
114
|
+
- Implausible `agy models` label lines are filtered instead of surfacing as
|
|
115
|
+
bogus models in `models://gemini`.
|
|
116
|
+
|
|
117
|
+
### Security
|
|
118
|
+
|
|
119
|
+
- **Remote host-path confinement.** Remote HTTP/OAuth callers can no longer hand
|
|
120
|
+
a provider CLI an arbitrary host path to read, write, or load code from. The
|
|
121
|
+
advanced path/plugin fields are rejected for remote callers (local stdio is
|
|
122
|
+
unaffected): `systemPromptFile`/`appendSystemPromptFile`/`settings`/
|
|
123
|
+
`pluginDir`/`pluginUrl`/`debugFile` (Claude), string `outputSchema`/`images`/
|
|
124
|
+
`outputLastMessage` (Codex), `promptFile`/`config`/`agentConfig`/string
|
|
125
|
+
`exportSession` (Devin), and `promptFile`/`leaderSocket`/`rules`/`agent`
|
|
126
|
+
(Grok). `workingDir`/`addDir`/`includeDirs` remain workspace-confined.
|
|
127
|
+
- **Read-only provider-admin ops** now require the same remote `cli:admin` gate
|
|
128
|
+
as the mutating path, so a remote caller cannot enumerate or trigger
|
|
129
|
+
host-global provider-CLI spawns unprivileged.
|
|
130
|
+
- **ACP permission bridge** never turns a single approval into a standing
|
|
131
|
+
`allow_always` grant (it selects a one-time allow, else denies).
|
|
132
|
+
- **ACP subprocess reaping**: a graceful termination signal escalates to
|
|
133
|
+
SIGKILL after a grace window so a provider CLI that ignores SIGTERM is not
|
|
134
|
+
leaked as a zombie.
|
|
135
|
+
- Job results scrub the provider session id from returned streams for remote
|
|
136
|
+
callers; ACP error redaction covers Google/GitHub/Slack token shapes;
|
|
137
|
+
`session_info_update` is handled as a forward-compatible unknown ACP variant.
|
|
138
|
+
- CI/release: the Cloudflare Pages token is fetched from Azure Key Vault via
|
|
139
|
+
GitHub OIDC federation (no stored secret); the release tooling supports
|
|
140
|
+
prerelease cuts (a prerelease publishes under a side npm dist-tag, never
|
|
141
|
+
`latest`); typos and gitleaks allowlists cover a deliberate identifier and
|
|
142
|
+
secret-fixture test files.
|
|
143
|
+
|
|
5
144
|
## [2.13.2] - 2026-07-01: remote HTTP + OAuth connector UX and hardening
|
|
6
145
|
|
|
7
146
|
### Added
|
package/README.md
CHANGED
|
@@ -9,9 +9,11 @@
|
|
|
9
9
|
> _"Without consultation, plans are frustrated, but with many counselors they succeed."_
|
|
10
10
|
> — Proverbs 15:22 (LSB)
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
**Secure local control plane for AI coding agents.**
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
`llm-cli-gateway` lets supported MCP clients operate Claude Code, Codex, Gemini/Antigravity, Grok Build, Mistral Vibe, Cognition Devin, Cursor Agent, and configured HTTP API providers through one user-owned gateway while preserving native CLI sessions, local credentials, durable async jobs, validation receipts, and review workflows.
|
|
15
|
+
|
|
16
|
+
**Why developers try it:** use the client you are already in to delegate work to local coding agents, scope remote execution to registered workspaces, gate risky actions, survive disconnects, and collect auditable review evidence without turning those agents into a generic chat proxy.
|
|
15
17
|
|
|
16
18
|
**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
19
|
|
|
@@ -36,9 +38,9 @@ Or use directly with `npx` from an MCP client:
|
|
|
36
38
|
|
|
37
39
|
## What It Provides Today
|
|
38
40
|
|
|
39
|
-
`llm-cli-gateway` is a single-user MCP
|
|
41
|
+
`llm-cli-gateway` is a single-user MCP control plane for operating AI coding agents from supported local or remote clients. It is more than a thin CLI wrapper:
|
|
40
42
|
|
|
41
|
-
- Runs registered provider CLIs through consistent sync and async MCP tools.
|
|
43
|
+
- Runs registered provider CLIs and configured HTTP API providers through consistent sync and async MCP tools.
|
|
42
44
|
- Persists long-running jobs, supports restart-safe result collection, deduplication, cancellation, and sync-to-async deferral.
|
|
43
45
|
- Tracks sessions, real CLI resume paths, structured response metadata, and cache telemetry.
|
|
44
46
|
- Supports cache-aware `promptParts`, including explicit Claude `cache_control` when opted in.
|
|
@@ -51,7 +53,7 @@ Or use directly with `npx` from an MCP client:
|
|
|
51
53
|
|
|
52
54
|
The repo ships agent-ready workflow skills under [`.agents/skills`](.agents/skills) for async orchestration, session continuity, multi-LLM review, implement-review-fix loops, and secure approval-gated dispatch. Six caller-facing skills are bundled in the published npm package: `async-job-orchestration`, `multi-llm-review`, `session-workflow`, `secure-orchestration`, `implement-review-fix`, and `public-demo-session`. Machine-readable DAG-TOML plans live under [`docs/plans`](docs/plans) and [`setup/install-plan.dag.toml`](setup/install-plan.dag.toml) for workflows that need deterministic sequencing and verification gates.
|
|
53
55
|
|
|
54
|
-
The next documentation focus is provider-specific skill and DAG-TOML pairs for each outbound CLI: Claude, Codex, Gemini, Grok, and
|
|
56
|
+
The next documentation focus is provider-specific skill and DAG-TOML pairs for each outbound CLI and API-provider family: Claude, Codex, Gemini/Antigravity, Grok, Mistral Vibe, Devin, Cursor Agent, OpenAI-compatible endpoints, Anthropic Messages, and xAI Responses. The implementation plan is tracked in [`docs/plans/provider-workflow-assets.dag.toml`](docs/plans/provider-workflow-assets.dag.toml), with each provider asset expected to cover install/login checks or token-env checks, session behavior, approval modes, cache/telemetry surfaces, failure modes, and a smoke-test gate.
|
|
55
57
|
|
|
56
58
|
## Trust & Supply Chain
|
|
57
59
|
|
|
@@ -61,13 +63,13 @@ The next documentation focus is provider-specific skill and DAG-TOML pairs for e
|
|
|
61
63
|
- CI runs build, lint, format, tests, package checks, and npm audit.
|
|
62
64
|
- Security CI runs actionlint, zizmor, shellcheck, typos, osv-scanner, gitleaks, and lychee.
|
|
63
65
|
- GitHub release installer artifacts are checksummed and signed with Sigstore keyless signing.
|
|
64
|
-
- npm releases use
|
|
66
|
+
- npm releases use a generated prod-only shrinkwrap and release security audit; the publish token is fetched at runtime from Azure Key Vault through GitHub OIDC to Entra.
|
|
65
67
|
- The npm package intentionally ships a generated, prod-only `npm-shrinkwrap.json` so registry installs resolve the audited release tree. Release gates regenerate it from `package-lock.json`, compare for parity, and run a registry-fidelity consumer install before publishing.
|
|
66
68
|
- Socket behavioural alerts are documented in [`socket.yml`](./socket.yml) and under "Security Considerations" below. `shellAccess` and `shrinkwrap` are reviewed package capabilities/configuration for this CLI appliance, not hidden install behaviour.
|
|
67
69
|
|
|
68
70
|
## Personal MCP Appliance
|
|
69
71
|
|
|
70
|
-
The personal-appliance contract keeps that surface intentionally narrow: one trusted user runs the gateway on a machine or volume they own, connects one MCP endpoint, and
|
|
72
|
+
The personal-appliance contract keeps that surface intentionally narrow: one trusted user runs the gateway on a machine or volume they own, connects one MCP endpoint, and lets supported clients operate local coding agents through workspace-scoped, approval-gated, auditable requests.
|
|
71
73
|
|
|
72
74
|
The product contract is documented in [docs/personal-mcp/PRODUCT_CONTRACT.md](docs/personal-mcp/PRODUCT_CONTRACT.md). It defines the single-user scope, security posture, target support matrix, and provider-support verification gates. Public setup guides must not claim ChatGPT, Claude web, Claude Desktop, Codex, Gemini CLI, Gemini web, or Grok inbound support until the corresponding provider/client path has been verified.
|
|
73
75
|
|
|
@@ -190,15 +192,15 @@ Every `*_request` and `*_request_async` tool except `devin_request` / `devin_req
|
|
|
190
192
|
|
|
191
193
|
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
194
|
|
|
193
|
-
| CLI | Prefix discipline | Explicit lever(s)
|
|
194
|
-
| ------- | ----------------- |
|
|
195
|
+
| CLI | Prefix discipline | Explicit lever(s) |
|
|
196
|
+
| ------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
|
|
195
197
|
| 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
|
-
| devin | no | plain `prompt` only
|
|
201
|
-
| cursor | no | plain `prompt` only
|
|
198
|
+
| codex | yes | none (OpenAI implicit) |
|
|
199
|
+
| gemini | yes | none (implicit server-side) |
|
|
200
|
+
| grok | yes | `compactionMode` / `compactionDetail` (context compaction: `summary | transcript | segments`; `segments` writes per-segment markdown) |
|
|
201
|
+
| mistral | yes | none (implicit) |
|
|
202
|
+
| devin | no | plain `prompt` only |
|
|
203
|
+
| cursor | no | plain `prompt` only |
|
|
202
204
|
|
|
203
205
|
**Claude example (explicit cacheControl)**
|
|
204
206
|
|
|
@@ -208,10 +210,10 @@ claude_request({
|
|
|
208
210
|
system: "You are a helpful code reviewer.",
|
|
209
211
|
context: "<long stable file dump>",
|
|
210
212
|
task: "Review the diff.",
|
|
211
|
-
cacheControl: { system: true, context: true }
|
|
213
|
+
cacheControl: { system: true, context: true }, // task is never marked
|
|
212
214
|
},
|
|
213
|
-
outputFormat: "stream-json"
|
|
214
|
-
})
|
|
215
|
+
outputFormat: "stream-json",
|
|
216
|
+
});
|
|
215
217
|
```
|
|
216
218
|
|
|
217
219
|
Gateway emits the `stream-json` stdin path with `cache_control: {type:"ephemeral", ttl:"1h"}` on marked blocks only.
|
|
@@ -222,8 +224,8 @@ Gateway emits the `stream-json` stdin path with `cache_control: {type:"ephemeral
|
|
|
222
224
|
grok_request({
|
|
223
225
|
promptParts: { system: "...", context: "...", task: "..." },
|
|
224
226
|
compactionMode: "segments",
|
|
225
|
-
compactionDetail: "balanced"
|
|
226
|
-
})
|
|
227
|
+
compactionDetail: "balanced",
|
|
228
|
+
});
|
|
227
229
|
```
|
|
228
230
|
|
|
229
231
|
Emits `--compaction-mode segments --compaction-detail balanced`.
|
|
@@ -249,6 +251,26 @@ Opt-in flags (all default off) live under `[cache_awareness]` in `~/.llm-cli-gat
|
|
|
249
251
|
- **Type Safety**: Strict TypeScript with comprehensive error handling
|
|
250
252
|
- **Supply-chain hardening**: a dedicated `.github/workflows/security.yml` runs actionlint, zizmor, shellcheck, typos, osv-scanner, gitleaks, and lychee on every push and PR (see `SECURITY.md` for the threat model)
|
|
251
253
|
|
|
254
|
+
## Provider capability surface
|
|
255
|
+
|
|
256
|
+
Every provider is reachable through the same request, session, job, and validation machinery, but the underlying CLIs differ in what they natively expose. The table records what actually shipped per provider; discover the live surface at runtime with `provider_tool_capabilities`, `list_models`, and the `provider-acp://<provider>` / `provider-tools://<provider>` resources.
|
|
257
|
+
|
|
258
|
+
| Provider | CLI request tools | Native ACP | Live model discovery | Admin surface |
|
|
259
|
+
| --- | --- | --- | --- | --- |
|
|
260
|
+
| Claude Code (`claude`) | `claude_request` / `_async` | None (CLI-first; no ACP entrypoint at claude 2.1.198) | model aliases, reasoning-effort levels, fallback model | read-only via `provider_admin_list` / `provider_admin_run` |
|
|
261
|
+
| OpenAI Codex (`codex`) | `codex_request` / `_async`, `codex_fork_session` | None (codex-cli 0.142.4 advertises mcp-server / app-server transports, not native ACP) | `codex debug models` | read-only via `provider_admin_list` / `provider_admin_run` |
|
|
262
|
+
| Gemini / Antigravity (`gemini`, `agy`) | `gemini_request` / `_async` | None (agy 1.0.14 exposes no ACP entrypoint; legacy Gemini CLI ACP evidence does not transfer) | `agy models` | read-only via `provider_admin_list` / `provider_admin_run` |
|
|
263
|
+
| xAI Grok (`grok`) | `grok_request` (sync `transport: "acp"`) / `_async` | Native via `grok agent stdio` | `grok models` + `~/.grok/config.toml` | read-only via `provider_admin_list` / `provider_admin_run` |
|
|
264
|
+
| Mistral Vibe (`mistral`) | `mistral_request` (sync `transport: "acp"`) / `_async` | Native via `vibe-acp` | Vibe config plus the `VIBE_ACTIVE_MODEL` active model and agent profiles | read-only via `provider_admin_list` / `provider_admin_run` |
|
|
265
|
+
| Cognition Devin (`devin`) | `devin_request` (sync `transport: "acp"`, `agentType: summarizer\|review`) / `_async` | Native via `devin acp` | `--model` / `DEVIN_MODEL` | read-only via `provider_admin_list` / `provider_admin_run` |
|
|
266
|
+
| Cursor Agent (`cursor`) | `cursor_request` (sync `transport: "acp"`) / `_async` | Native via `cursor-agent acp` (companion-owned) | model aliases | read-only via `provider_admin_list` / `provider_admin_run` |
|
|
267
|
+
|
|
268
|
+
- **Native ACP** is reported honestly. `grok`, `mistral`, `devin`, and `cursor` expose a native ACP entrypoint, so `provider-acp://<provider>` carries the negotiated `initialize` capability set and the derived session-method availability, and the sync `*_request` accepts `transport: "acp"` (fails closed unless `[acp]` and the provider's `runtime_enabled` gate are set). ACP routing is sync-only: the `*_request_async` variants always run the CLI transport and do not accept `transport: "acp"` (nor Devin's `agentType`); async ACP parity is a later phase. `claude`, `codex`, and `gemini` have no native ACP entrypoint at their target CLI versions; their `provider-acp://` records report `native: false` with no methods and no adapter-as-native masquerade, and they expose no `transport: "acp"` selector.
|
|
269
|
+
- **Resources** are generated from the provider registry for every CLI provider: `models://<provider>`, `sessions://<provider>`, `provider-acp://<provider>`, `provider-tools://<provider>`, and `provider-subcommands://<provider>`.
|
|
270
|
+
- **Model discovery** is live and account-aware: the discovery listed above reaches `models://<provider>` and `list_models`, degrading to static registry facts when a live probe is unavailable (a resource read never spawns a CLI).
|
|
271
|
+
- **Admin surfaces** are discovery-driven and output-redacted. `provider_admin_list` and `provider_admin_run` are read-only for every provider. State-mutating admin operations are exposed only through `provider_admin_mutate`, gated behind `[admin] allow_mutating_cli_admin_ops`, the remote `cli:admin` scope, an approval gate, and an audit record. Mutating ACP session operations are likewise gated behind `[acp] allow_mutating_session_ops`.
|
|
272
|
+
- **Validation** commands work across every provider: `validate_with_models`, `second_opinion`, `compare_answers`, `red_team_review`, `consensus_check`, `ask_model`, and `synthesize_validation`, with durable signed receipts via `validation_receipt` and the `validation-receipt://{validationId}` resource.
|
|
273
|
+
|
|
252
274
|
## Prerequisites
|
|
253
275
|
|
|
254
276
|
**Node.js >= 24.4.0** is required (`engines.node` in `package.json`). The gateway uses Node's built-in `node:sqlite` module for persistence — there is no native binding to compile and no install scripts run. The 24.4 floor is where `allowBareNamedParameters` defaults to `true`, which the persistence layer relies on.
|
|
@@ -389,7 +411,7 @@ The personal-appliance surface exposes simplified validation tools for non-devel
|
|
|
389
411
|
- `synthesize_validation`: run an explicit judge model after provider results have been collected.
|
|
390
412
|
- `list_available_models`: list the models each provider CLI exposes through the simplified surface.
|
|
391
413
|
- `job_status` and `job_result`: poll and collect validation job outputs.
|
|
392
|
-
- `validation_receipt`: retrieve the immutable receipt of a terminal cross-LLM validation run by `validationId` (returns `minted | pending | expired_unminted | not_found`, own-or-not-found). `format: "markdown"` renders a human-readable report; `includeRawResponses` inlines provider answer text. Registered only
|
|
414
|
+
- `validation_receipt`: retrieve the immutable receipt of a terminal cross-LLM validation run by `validationId` (returns `minted | pending | expired_unminted | not_found`, own-or-not-found). `format: "markdown"` renders a human-readable report; `includeRawResponses` inlines provider answer text. Registered only when the attached job store provides the durable validation-run store capability (`sqlite` and `postgres`).
|
|
393
415
|
|
|
394
416
|
The same receipt is also exposed as the `validation-receipt://{validationId}` MCP resource (same durable gate and own-or-not-found owner scoping).
|
|
395
417
|
|
|
@@ -574,6 +596,7 @@ Execute a Grok CLI (xAI) request with session support.
|
|
|
574
596
|
|
|
575
597
|
- `prompt` (string, optional*): The prompt to send (1-100,000 chars). *Exactly one of `prompt` or `promptParts` is required (mutually exclusive)
|
|
576
598
|
- `model` (string, optional): Model name or alias (e.g. `grok-build`, `latest`)
|
|
599
|
+
- `transport` (string, optional): `"cli"` (default) runs the Grok CLI; `"acp"` routes through Grok's native `grok agent stdio` transport when `[acp].enabled` and the provider's `runtime_enabled` are set (fails closed otherwise). Sync-only: `grok_request_async` always runs the CLI transport and does not accept `transport`
|
|
577
600
|
- `outputFormat` (string, optional): `"plain"` (default), `"json"`, or `"streaming-json"`
|
|
578
601
|
- `sessionId` (string, optional): Session ID to resume (`--resume <id>`)
|
|
579
602
|
- `resumeLatest` (boolean, optional): Resume the most recent session in the current cwd (`--continue`)
|
|
@@ -636,7 +659,7 @@ Every async job is persisted to a job store as it transitions through running
|
|
|
636
659
|
|
|
637
660
|
- **Re-issuing a request is safe.** Identical `*_request` / `*_request_async` calls within the dedup window (default 1 hour) short-circuit onto the existing running or completed job — the caller gets back the same job ID instead of starting a duplicate run. This directly fixes the "agent times out polling, re-issues, and the whole job starts over" failure mode.
|
|
638
661
|
- **`llm_job_status` and `llm_job_result` work across gateway restarts.** Job rows live for 30 days by default; callers can collect results long after the in-memory cache has evicted them.
|
|
639
|
-
- **
|
|
662
|
+
- **A job is marked `orphaned` only when its owning gateway instance is provably gone**, never because another instance restarted. Each instance holds a periodic heartbeat lease and stamps every job it owns; the recovery sweep orphans a `queued`/`running` job only when that job's own lease has expired. On a shared store (`backend = "postgres"`) this means a fresh instance never orphans another live instance's in-flight jobs. The captured partial output of a genuinely orphaned job remains readable, and a stale-then-reviving owner that later finishes self-heals to the correct terminal state (issue #139).
|
|
640
663
|
- **Pass `forceRefresh: true`** on any request tool to bypass dedup and force a fresh CLI run.
|
|
641
664
|
|
|
642
665
|
##### Persistence configuration
|
|
@@ -647,17 +670,29 @@ The job-store backend is configured by `~/.llm-cli-gateway/config.toml` (overrid
|
|
|
647
670
|
[persistence]
|
|
648
671
|
backend = "sqlite" # "sqlite" | "memory" | "postgres" | "none"
|
|
649
672
|
path = "~/.llm-cli-gateway/logs.db" # for sqlite
|
|
650
|
-
# dsn = "postgresql://user:pw@host/db" # for postgres
|
|
673
|
+
# dsn = "postgresql://user:pw@host/db" # for postgres
|
|
651
674
|
retentionDays = 30
|
|
652
675
|
dedupWindowMs = 3600000
|
|
653
676
|
acknowledgeEphemeral = false # required to enable async tools with memory backend
|
|
677
|
+
|
|
678
|
+
# Issue #139 durable orphan-recovery lease (defaults shown). Each instance
|
|
679
|
+
# advances a per-job lease on every heartbeat; the sweep orphans a job only
|
|
680
|
+
# after its own lease expires, so a fresh instance never orphans another live
|
|
681
|
+
# instance's jobs on a shared store. Validated: leaseTtl >= 2*heartbeat and
|
|
682
|
+
# httpJobGrace >= leaseTtl.
|
|
683
|
+
instanceHeartbeatMs = 15000 # heartbeat cadence
|
|
684
|
+
instanceLeaseTtlMs = 90000 # per-job lease TTL (6x heartbeat)
|
|
685
|
+
httpJobGraceMs = 300000 # extra grace for no-pid http jobs (5 min)
|
|
686
|
+
orphanSweepIntervalMs = 30000 # reaper cadence
|
|
687
|
+
instanceGcMs = 3600000 # gateway_instances GC horizon
|
|
688
|
+
# ownsOrphanRecovery = false # DEPRECATED (#139): superseded by the lease; parsed + warned, no longer used
|
|
654
689
|
```
|
|
655
690
|
|
|
656
691
|
Backends:
|
|
657
692
|
|
|
658
693
|
- **`sqlite`** (default) — durable, file-backed. Safe for single-instance deployments.
|
|
694
|
+
- **`postgres`** — durable PostgreSQL-backed async job, dedup, orphan recovery, HTTP job, and validation receipt storage. Use this for multi-instance or service deployments. Requires the optional peer dependency `pg` to be installed alongside the gateway.
|
|
659
695
|
- **`memory`** — in-process Map. Lost on gateway exit. Requires `acknowledgeEphemeral = true` to be loaded. Suitable for tests and ephemeral CI gateways.
|
|
660
|
-
- **`postgres`** — interface only, implementation not yet shipped. Selecting this backend throws at startup.
|
|
661
696
|
- **`none`** — no store. **`*_request_async`, `llm_job_status`, `llm_job_result`, and `llm_job_cancel` are NOT registered on the gateway.** This is a structural invariant: agents that try to call async tools against a gateway with `backend = "none"` get a clean "tool not found" at connect time instead of silent in-memory loss after the 1-hour TTL. Use `llm_process_health` to inspect the resolved persistence state programmatically.
|
|
662
697
|
|
|
663
698
|
Legacy environment variables (deprecated; emit a warning at startup):
|
|
@@ -922,6 +957,7 @@ consumes = ["OUT:mcp-reconnected"]
|
|
|
922
957
|
Run a Mistral Vibe agentic coding request. Like `grok_request` in shape, but with Vibe's specific surface:
|
|
923
958
|
|
|
924
959
|
- `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.
|
|
960
|
+
- `transport` (string, optional): `"cli"` (default) runs the Vibe CLI; `"acp"` routes through Vibe's native `vibe-acp` transport when `[acp].enabled` and the provider's `runtime_enabled` are set (fails closed otherwise). Sync-only: `mistral_request_async` always runs the CLI transport and does not accept `transport`
|
|
925
961
|
- `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.
|
|
926
962
|
- `allowedTools` (string[], optional): One `--enabled-tools <tool>` flag per entry (allow-list only).
|
|
927
963
|
- `disallowedTools` (string[], optional): Accepted for parity with the other providers; ignored at the CLI boundary with a logged warning.
|
|
@@ -951,7 +987,8 @@ Run a Cognition Devin CLI request synchronously (headless print mode, `devin -p`
|
|
|
951
987
|
|
|
952
988
|
- `prompt` (string, optional*): Prompt text for Devin CLI (1-100,000 chars). Required in practice; `promptFile` is additive
|
|
953
989
|
- `model` (string, optional): Model name or alias (e.g. `opus`, `latest`)
|
|
954
|
-
- `transport` (string, optional): `"cli"` (default) runs the Devin CLI; `"acp"` routes through `devin acp` when `[acp].enabled` and the provider's `runtime_enabled` are set (fails closed otherwise)
|
|
990
|
+
- `transport` (string, optional): `"cli"` (default) runs the Devin CLI; `"acp"` routes through Devin's native `devin acp` transport when `[acp].enabled` and the provider's `runtime_enabled` are set (fails closed otherwise). Sync-only: `devin_request_async` always runs the CLI transport and accepts neither `transport` nor `agentType`
|
|
991
|
+
- `agentType` (string, optional): ACP agent variant for `transport: "acp"` (`devin acp --agent-type`): `"summarizer"` (no tools, text summary) or `"review"` (read-only plus shell code-review); ignored for the CLI transport
|
|
955
992
|
- `permissionMode` (string, optional): Devin CLI permission mode (`--permission-mode`): `auto` (auto-approves read-only tools), `smart` (also auto-runs actions a fast model judges safe), `dangerous` (auto-approves all). Omit to use Devin's headless default
|
|
956
993
|
- `promptFile` (string, optional): Load the initial prompt from a file (`--prompt-file`)
|
|
957
994
|
- `sessionId` (string, optional): Devin session ID to resume (`--resume <id>`). The `gw-*` id minted for a brand-new session is not resumable via `sessionId`; continue with `resumeLatest: true`
|
|
@@ -972,7 +1009,7 @@ Run a Cursor Agent CLI request synchronously. Defaults to headless print mode (`
|
|
|
972
1009
|
- `model` (string, optional): Model name or alias (for example `gpt-5`, `sonnet-4-thinking`, or `latest`)
|
|
973
1010
|
- `mode` (string, optional): Cursor mode, `"plan"` or `"ask"` (`--mode`)
|
|
974
1011
|
- `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
|
|
1012
|
+
- `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. Sync-only: `cursor_request_async` always runs the CLI transport and does not accept `transport`
|
|
976
1013
|
- `force` (boolean, optional): Emit `--force` for non-interactive operation
|
|
977
1014
|
- `autoReview` (boolean, optional): Emit `--auto-review`
|
|
978
1015
|
- `sandbox` (string, optional): `"enabled"` or `"disabled"` (`--sandbox`)
|
|
@@ -1317,7 +1354,7 @@ The API key value is never emitted on any of these surfaces (only the env var na
|
|
|
1317
1354
|
|
|
1318
1355
|
### Security note
|
|
1319
1356
|
|
|
1320
|
-
The resolved API key is excluded from `payloadJson`, the dedup key, logs, and the flight recorder. However, the **request prompt is persisted in plaintext** in the async job store (
|
|
1357
|
+
The resolved API key is excluded from `payloadJson`, the dedup key, logs, and the flight recorder. However, the **request prompt is persisted in plaintext** in the async job store (SQLite at `[persistence].path`, default `~/.llm-cli-gateway/logs.db`, or Postgres rows under `backend = "postgres"`) and is not covered by secret redaction. This mirrors the CLI tools, whose prompt is persisted in `argsJson` whenever it is passed as a command argument (a few CLI paths instead stream the prompt over stdin and so do not persist it). Treat the job store as sensitive at rest. See [Security Considerations](#security-considerations).
|
|
1321
1358
|
|
|
1322
1359
|
## Session Management
|
|
1323
1360
|
|
|
@@ -1384,9 +1421,11 @@ await callTool("session_delete", {
|
|
|
1384
1421
|
- **Gemini (Antigravity)** → `default` / prompted, i.e. **no** `--dangerously-skip-permissions` (the `agy` CLI has no accept-edits middle rung, so the safe default is prompted execution; without the opt-in, Gemini cannot auto-approve mutating tools under `mcp_managed`)
|
|
1385
1422
|
|
|
1386
1423
|
Set to `1`/`true` to let the operator opt back in: this permits bypass requests through the approval gate **and** restores each provider's full auto-approve mode under `mcp_managed` (Claude `bypassPermissions`, Grok `--always-approve`, Mistral `--agent auto-approve`, Gemini `--dangerously-skip-permissions`). Sandboxed auto modes (e.g. codex `--sandbox workspace-write`) are unaffected.
|
|
1424
|
+
|
|
1387
1425
|
```bash
|
|
1388
1426
|
LLM_GATEWAY_APPROVAL_ALLOW_BYPASS=1 node dist/index.js
|
|
1389
1427
|
```
|
|
1428
|
+
|
|
1390
1429
|
- `LLM_GATEWAY_TRUSTED_PRINCIPAL_HEADER`: Name of an HTTP header carrying the authenticated user identity asserted by a **trusted front door** (any identity-aware reverse proxy / IdP). When set, the gateway adopts that header value as the request's ownership principal — but **only** for requests authenticated with the gateway's own static bearer token (i.e. the trusted upstream proxy), never from an arbitrary remote client. Off by default; IdP-agnostic. Lets a proxy-fronted multi-user deployment carry per-user identity into the gateway.
|
|
1391
1430
|
```bash
|
|
1392
1431
|
LLM_GATEWAY_TRUSTED_PRINCIPAL_HEADER=x-gateway-principal node dist/index.js
|
|
@@ -1575,11 +1614,11 @@ The gateway supports concurrent requests across different CLIs. Each request spa
|
|
|
1575
1614
|
|
|
1576
1615
|
- **Input Validation**: All prompts are validated (min 1 char, max 100k chars)
|
|
1577
1616
|
- **API-provider keys**: For `[providers.<name>]` HTTP providers, the gateway reads the key from the named environment variable at request time only. The resolved key is excluded from the persisted `payloadJson`, the dedup key, logs, and the flight recorder, and is never surfaced on the discovery/diagnostic surfaces (which report only the env var name and a presence boolean). `base_url` userinfo is redacted on the diagnostic surfaces. See [API providers (HTTP)](#api-providers-http).
|
|
1578
|
-
- **Prompt persistence at rest**: Async job rows store the request **prompt in plaintext** (HTTP `payloadJson`, and CLI `argsJson` whenever the prompt is passed as a command argument rather than streamed over stdin); this is not covered by secret redaction. The SQLite job-store file (default `~/.llm-cli-gateway/logs.db`, configurable via `[persistence].path`) is `chmod`ed to `0o600` on non-Windows hosts;
|
|
1617
|
+
- **Prompt persistence at rest**: Async job rows store the request **prompt in plaintext** (HTTP `payloadJson`, and CLI `argsJson` whenever the prompt is passed as a command argument rather than streamed over stdin); this is not covered by secret redaction. The SQLite job-store file (default `~/.llm-cli-gateway/logs.db`, configurable via `[persistence].path`) is `chmod`ed to `0o600` on non-Windows hosts; the Postgres backend stores the same fields in database rows. Treat either backend as sensitive and scope/rotate it like any prompt log. Set `[persistence].backend = "none"` to disable the async job store entirely (the `*_request_async` / `llm_job_*` tools are then not registered).
|
|
1579
1618
|
- **Command Execution**: Uses `spawn` with separate arguments (not shell execution)
|
|
1580
1619
|
- **No Eval**: No dynamic code evaluation in our source (see "Socket alerts" below for the transitive `ajv` codegen case)
|
|
1581
1620
|
- **Sandboxing**: Consider running in containers for production use
|
|
1582
|
-
- **
|
|
1621
|
+
- **npm publish control**: npm releases are gated by the generated prod-only shrinkwrap, release security audit, packed-consumer checks, and a publish token fetched at runtime from Azure Key Vault through GitHub OIDC to Entra
|
|
1583
1622
|
- **Release signing**: GitHub release installer artifacts are signed with Sigstore keyless signing; verify `SHA256SUMS.sigstore.json` before trusting the checksum file
|
|
1584
1623
|
|
|
1585
1624
|
### Socket alerts — context for reviewers
|
package/dist/acp/client.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AcpError } from "./errors.js";
|
|
2
2
|
import type { JsonRpcStdioTransport, JsonRpcId } from "./json-rpc-stdio.js";
|
|
3
|
-
import { type ContentBlock, type InitializeResponse, type ReadTextFileRequest, type ReadTextFileResponse, type RequestPermissionRequest, type RequestPermissionResponse, type SessionLoadResponse, type SessionNewResponse, type SessionPromptResponse, type SessionUpdateNotification, type WriteTextFileRequest, type WriteTextFileResponse } from "./types.js";
|
|
3
|
+
import { type CloseSessionResponse, type ContentBlock, type DeleteSessionResponse, type InitializeResponse, type ListSessionsResponse, type ReadTextFileRequest, type ReadTextFileResponse, type RequestPermissionRequest, type RequestPermissionResponse, type SessionLoadResponse, type SessionNewResponse, type SessionPromptResponse, type SessionResumeResponse, type SessionUpdateNotification, type SetSessionConfigOptionResponse, type SetSessionModeResponse, type WriteTextFileRequest, type WriteTextFileResponse } from "./types.js";
|
|
4
4
|
import type { Logger } from "../logger.js";
|
|
5
5
|
import type { CliType } from "../session-manager.js";
|
|
6
6
|
export declare const DEFAULT_ACP_PROTOCOL_VERSION = 1;
|
|
@@ -25,6 +25,7 @@ export interface AcpClientOptions {
|
|
|
25
25
|
readonly logger?: Logger;
|
|
26
26
|
readonly protocolVersion?: number;
|
|
27
27
|
readonly timeouts?: AcpClientTimeouts;
|
|
28
|
+
readonly allowMutatingSessionOps?: boolean;
|
|
28
29
|
}
|
|
29
30
|
export interface AcpClientTimeouts {
|
|
30
31
|
readonly initializeMs?: number;
|
|
@@ -48,6 +49,18 @@ export interface PromptParams {
|
|
|
48
49
|
readonly sessionId: string;
|
|
49
50
|
readonly prompt: ReadonlyArray<ContentBlock>;
|
|
50
51
|
}
|
|
52
|
+
export interface ResumeSessionParams extends NewSessionParams {
|
|
53
|
+
readonly sessionId: string;
|
|
54
|
+
}
|
|
55
|
+
export interface SetSessionModeParams {
|
|
56
|
+
readonly sessionId: string;
|
|
57
|
+
readonly modeId: string;
|
|
58
|
+
}
|
|
59
|
+
export interface SetSessionConfigOptionParams {
|
|
60
|
+
readonly sessionId: string;
|
|
61
|
+
readonly configId: string;
|
|
62
|
+
readonly value: string;
|
|
63
|
+
}
|
|
51
64
|
export declare class AcpClient {
|
|
52
65
|
private readonly transport;
|
|
53
66
|
private readonly provider;
|
|
@@ -56,16 +69,31 @@ export declare class AcpClient {
|
|
|
56
69
|
private readonly logger;
|
|
57
70
|
private readonly protocolVersion;
|
|
58
71
|
private readonly timeouts;
|
|
72
|
+
private readonly allowMutatingSessionOps;
|
|
59
73
|
private initialized;
|
|
60
74
|
private initializeResult;
|
|
75
|
+
private methodAvailability;
|
|
76
|
+
private static readonly MUTATING_METHODS;
|
|
61
77
|
constructor(options: AcpClientOptions);
|
|
62
78
|
get isInitialized(): boolean;
|
|
63
79
|
get agentInfo(): InitializeResponse | null;
|
|
80
|
+
get availableMethods(): ReadonlySet<string>;
|
|
81
|
+
supportsMethod(method: string): boolean;
|
|
64
82
|
initialize(options?: InitializeOptions): Promise<InitializeResponse>;
|
|
65
83
|
newSession(params: NewSessionParams): Promise<SessionNewResponse>;
|
|
66
84
|
loadSession(params: LoadSessionParams): Promise<SessionLoadResponse>;
|
|
67
85
|
prompt(params: PromptParams): Promise<SessionPromptResponse>;
|
|
68
86
|
cancel(sessionId: string): void;
|
|
87
|
+
resumeSession(params: ResumeSessionParams): Promise<SessionResumeResponse>;
|
|
88
|
+
listSessions(params?: {
|
|
89
|
+
cursor?: string;
|
|
90
|
+
}): Promise<ListSessionsResponse>;
|
|
91
|
+
closeSession(sessionId: string): Promise<CloseSessionResponse>;
|
|
92
|
+
deleteSession(sessionId: string): Promise<DeleteSessionResponse>;
|
|
93
|
+
setSessionMode(params: SetSessionModeParams): Promise<SetSessionModeResponse>;
|
|
94
|
+
setSessionConfigOption(params: SetSessionConfigOptionParams): Promise<SetSessionConfigOptionResponse>;
|
|
95
|
+
private augmentSessionMethods;
|
|
96
|
+
private assertMethodAvailable;
|
|
69
97
|
private send;
|
|
70
98
|
private normalizeError;
|
|
71
99
|
private assertInitialized;
|
package/dist/acp/client.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { AcpProtocolError, isAcpError } from "./errors.js";
|
|
2
|
-
import { parseInitializeResponse, parseReadTextFileRequest, parseRequestPermissionRequest, parseSessionLoadResponse, parseSessionNewResponse, parseSessionPromptResponse, parseSessionUpdateNotification, parseWriteTextFileRequest, } from "./types.js";
|
|
1
|
+
import { AcpMethodUnsupportedError, AcpMutatingDisabledError, AcpProtocolError, isAcpError, } from "./errors.js";
|
|
2
|
+
import { deriveAcpMethodAvailability, parseCloseSessionResponse, parseDeleteSessionResponse, parseInitializeResponse, parseListSessionsResponse, parseReadTextFileRequest, parseRequestPermissionRequest, parseSessionLoadResponse, parseSessionNewResponse, parseSessionPromptResponse, parseSessionResumeResponse, parseSessionUpdateNotification, parseSetSessionConfigOptionResponse, parseSetSessionModeResponse, parseWriteTextFileRequest, sessionResponseMethods, } from "./types.js";
|
|
3
3
|
import { noopLogger } from "../logger.js";
|
|
4
4
|
export const DEFAULT_ACP_PROTOCOL_VERSION = 1;
|
|
5
5
|
export class AcpClient {
|
|
@@ -10,8 +10,15 @@ export class AcpClient {
|
|
|
10
10
|
logger;
|
|
11
11
|
protocolVersion;
|
|
12
12
|
timeouts;
|
|
13
|
+
allowMutatingSessionOps;
|
|
13
14
|
initialized = false;
|
|
14
15
|
initializeResult = null;
|
|
16
|
+
methodAvailability = new Set();
|
|
17
|
+
static MUTATING_METHODS = new Set([
|
|
18
|
+
"session/delete",
|
|
19
|
+
"session/set_mode",
|
|
20
|
+
"session/set_config_option",
|
|
21
|
+
]);
|
|
15
22
|
constructor(options) {
|
|
16
23
|
this.transport = options.transport;
|
|
17
24
|
this.provider = options.provider;
|
|
@@ -20,6 +27,7 @@ export class AcpClient {
|
|
|
20
27
|
this.logger = options.logger ?? noopLogger;
|
|
21
28
|
this.protocolVersion = options.protocolVersion ?? DEFAULT_ACP_PROTOCOL_VERSION;
|
|
22
29
|
this.timeouts = options.timeouts ?? {};
|
|
30
|
+
this.allowMutatingSessionOps = options.allowMutatingSessionOps ?? false;
|
|
23
31
|
}
|
|
24
32
|
get isInitialized() {
|
|
25
33
|
return this.initialized;
|
|
@@ -27,6 +35,12 @@ export class AcpClient {
|
|
|
27
35
|
get agentInfo() {
|
|
28
36
|
return this.initializeResult;
|
|
29
37
|
}
|
|
38
|
+
get availableMethods() {
|
|
39
|
+
return new Set(this.methodAvailability);
|
|
40
|
+
}
|
|
41
|
+
supportsMethod(method) {
|
|
42
|
+
return this.methodAvailability.has(method);
|
|
43
|
+
}
|
|
30
44
|
async initialize(options = {}) {
|
|
31
45
|
if (this.initialized && this.initializeResult) {
|
|
32
46
|
return this.initializeResult;
|
|
@@ -45,12 +59,15 @@ export class AcpClient {
|
|
|
45
59
|
const result = parseInitializeResponse(raw, this.provider);
|
|
46
60
|
this.initialized = true;
|
|
47
61
|
this.initializeResult = result;
|
|
62
|
+
this.methodAvailability = new Set(deriveAcpMethodAvailability(result));
|
|
48
63
|
return result;
|
|
49
64
|
}
|
|
50
65
|
async newSession(params) {
|
|
51
66
|
this.assertInitialized("session/new");
|
|
52
67
|
const raw = await this.send("session/new", { cwd: params.cwd, mcpServers: params.mcpServers ?? [] }, this.timeouts.sessionNewMs);
|
|
53
|
-
|
|
68
|
+
const response = parseSessionNewResponse(raw, this.provider);
|
|
69
|
+
this.augmentSessionMethods(response);
|
|
70
|
+
return response;
|
|
54
71
|
}
|
|
55
72
|
async loadSession(params) {
|
|
56
73
|
this.assertInitialized("session/load");
|
|
@@ -59,7 +76,9 @@ export class AcpClient {
|
|
|
59
76
|
cwd: params.cwd,
|
|
60
77
|
mcpServers: params.mcpServers ?? [],
|
|
61
78
|
}, this.timeouts.sessionLoadMs);
|
|
62
|
-
|
|
79
|
+
const response = parseSessionLoadResponse(raw, this.provider);
|
|
80
|
+
this.augmentSessionMethods(response);
|
|
81
|
+
return response;
|
|
63
82
|
}
|
|
64
83
|
async prompt(params) {
|
|
65
84
|
this.assertInitialized("session/prompt");
|
|
@@ -69,6 +88,61 @@ export class AcpClient {
|
|
|
69
88
|
cancel(sessionId) {
|
|
70
89
|
this.transport.notify("session/cancel", { sessionId });
|
|
71
90
|
}
|
|
91
|
+
async resumeSession(params) {
|
|
92
|
+
this.assertMethodAvailable("session/resume");
|
|
93
|
+
const raw = await this.send("session/resume", { sessionId: params.sessionId, cwd: params.cwd, mcpServers: params.mcpServers ?? [] }, this.timeouts.sessionLoadMs);
|
|
94
|
+
const response = parseSessionResumeResponse(raw, this.provider);
|
|
95
|
+
this.augmentSessionMethods(response);
|
|
96
|
+
return response;
|
|
97
|
+
}
|
|
98
|
+
async listSessions(params = {}) {
|
|
99
|
+
this.assertMethodAvailable("session/list");
|
|
100
|
+
const raw = await this.send("session/list", { cursor: params.cursor });
|
|
101
|
+
return parseListSessionsResponse(raw, this.provider);
|
|
102
|
+
}
|
|
103
|
+
async closeSession(sessionId) {
|
|
104
|
+
this.assertMethodAvailable("session/close");
|
|
105
|
+
const raw = await this.send("session/close", { sessionId });
|
|
106
|
+
return parseCloseSessionResponse(raw, this.provider);
|
|
107
|
+
}
|
|
108
|
+
async deleteSession(sessionId) {
|
|
109
|
+
this.assertMethodAvailable("session/delete");
|
|
110
|
+
const raw = await this.send("session/delete", { sessionId });
|
|
111
|
+
return parseDeleteSessionResponse(raw, this.provider);
|
|
112
|
+
}
|
|
113
|
+
async setSessionMode(params) {
|
|
114
|
+
this.assertMethodAvailable("session/set_mode");
|
|
115
|
+
const raw = await this.send("session/set_mode", {
|
|
116
|
+
sessionId: params.sessionId,
|
|
117
|
+
modeId: params.modeId,
|
|
118
|
+
});
|
|
119
|
+
return parseSetSessionModeResponse(raw, this.provider);
|
|
120
|
+
}
|
|
121
|
+
async setSessionConfigOption(params) {
|
|
122
|
+
this.assertMethodAvailable("session/set_config_option");
|
|
123
|
+
const raw = await this.send("session/set_config_option", {
|
|
124
|
+
sessionId: params.sessionId,
|
|
125
|
+
configId: params.configId,
|
|
126
|
+
value: params.value,
|
|
127
|
+
});
|
|
128
|
+
return parseSetSessionConfigOptionResponse(raw, this.provider);
|
|
129
|
+
}
|
|
130
|
+
augmentSessionMethods(response) {
|
|
131
|
+
for (const method of sessionResponseMethods(response)) {
|
|
132
|
+
this.methodAvailability.add(method);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
assertMethodAvailable(method) {
|
|
136
|
+
this.assertInitialized(method);
|
|
137
|
+
if (!this.methodAvailability.has(method)) {
|
|
138
|
+
throw new AcpMethodUnsupportedError(this.provider, method, {
|
|
139
|
+
reason: "capability_not_advertised",
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
if (AcpClient.MUTATING_METHODS.has(method) && !this.allowMutatingSessionOps) {
|
|
143
|
+
throw new AcpMutatingDisabledError(this.provider, method, { reason: "config_gate_off" });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
72
146
|
async send(method, params, timeoutMs) {
|
|
73
147
|
try {
|
|
74
148
|
return await this.transport.request(method, params, timeoutMs);
|
package/dist/acp/errors.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CliType } from "../session-manager.js";
|
|
2
|
-
export type AcpErrorKind = "acp_disabled" | "provider_acp_disabled" | "provider_acp_unsupported" | "provider_runtime_disabled" | "provider_unavailable" | "protocol" | "timeout" | "permission_denied" | "process_exit";
|
|
2
|
+
export type AcpErrorKind = "acp_disabled" | "provider_acp_disabled" | "provider_acp_unsupported" | "provider_runtime_disabled" | "provider_unavailable" | "method_unsupported" | "mutating_disabled" | "protocol" | "timeout" | "permission_denied" | "process_exit";
|
|
3
3
|
export declare function redactAcpMessage(input: string): string;
|
|
4
4
|
export declare function redactAcpDebug(value: unknown): unknown;
|
|
5
5
|
export declare function redactAcpCause(cause: unknown): unknown;
|
|
@@ -32,6 +32,14 @@ export declare class ProviderRuntimeDisabledError extends AcpError {
|
|
|
32
32
|
export declare class ProviderUnavailableError extends AcpError {
|
|
33
33
|
constructor(provider: CliType, reason: string, debug?: AcpErrorDebug);
|
|
34
34
|
}
|
|
35
|
+
export declare class AcpMethodUnsupportedError extends AcpError {
|
|
36
|
+
readonly method: string;
|
|
37
|
+
constructor(provider: CliType, method: string, debug?: AcpErrorDebug);
|
|
38
|
+
}
|
|
39
|
+
export declare class AcpMutatingDisabledError extends AcpError {
|
|
40
|
+
readonly method: string;
|
|
41
|
+
constructor(provider: CliType, method: string, debug?: AcpErrorDebug);
|
|
42
|
+
}
|
|
35
43
|
export declare class AcpProtocolError extends AcpError {
|
|
36
44
|
readonly code?: number;
|
|
37
45
|
constructor(userMessage: string, options?: {
|