llm-cli-gateway 2.7.0 → 2.9.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.
Files changed (54) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/README.md +28 -1
  3. package/dist/acp/client.d.ts +78 -0
  4. package/dist/acp/client.js +201 -0
  5. package/dist/acp/errors.d.ts +63 -0
  6. package/dist/acp/errors.js +139 -0
  7. package/dist/acp/json-rpc-stdio.d.ts +71 -0
  8. package/dist/acp/json-rpc-stdio.js +375 -0
  9. package/dist/acp/process-manager.d.ts +66 -0
  10. package/dist/acp/process-manager.js +364 -0
  11. package/dist/acp/provider-registry.d.ts +24 -0
  12. package/dist/acp/provider-registry.js +82 -0
  13. package/dist/acp/types.d.ts +557 -0
  14. package/dist/acp/types.js +335 -0
  15. package/dist/approval-manager.d.ts +1 -0
  16. package/dist/approval-manager.js +14 -1
  17. package/dist/async-job-manager.d.ts +3 -0
  18. package/dist/async-job-manager.js +56 -16
  19. package/dist/auth.d.ts +4 -0
  20. package/dist/auth.js +16 -0
  21. package/dist/cache-stats.d.ts +1 -0
  22. package/dist/cache-stats.js +19 -11
  23. package/dist/cli-updater.js +5 -2
  24. package/dist/codex-json-parser.d.ts +3 -0
  25. package/dist/codex-json-parser.js +17 -0
  26. package/dist/config.d.ts +30 -0
  27. package/dist/config.js +140 -0
  28. package/dist/flight-recorder.d.ts +7 -1
  29. package/dist/flight-recorder.js +33 -6
  30. package/dist/http-transport.js +21 -18
  31. package/dist/index.js +104 -34
  32. package/dist/job-store.d.ts +4 -0
  33. package/dist/job-store.js +16 -4
  34. package/dist/oauth.d.ts +2 -0
  35. package/dist/oauth.js +90 -8
  36. package/dist/pricing.d.ts +1 -1
  37. package/dist/pricing.js +67 -2
  38. package/dist/provider-tool-capabilities.d.ts +38 -0
  39. package/dist/provider-tool-capabilities.js +142 -0
  40. package/dist/request-context.d.ts +4 -0
  41. package/dist/request-context.js +16 -0
  42. package/dist/request-helpers.d.ts +4 -4
  43. package/dist/request-limits.d.ts +8 -0
  44. package/dist/request-limits.js +49 -0
  45. package/dist/secret-redaction.d.ts +3 -0
  46. package/dist/secret-redaction.js +53 -0
  47. package/dist/session-manager-pg.js +8 -5
  48. package/dist/session-manager.d.ts +1 -0
  49. package/dist/session-manager.js +2 -0
  50. package/dist/upstream-contracts.d.ts +27 -0
  51. package/dist/upstream-contracts.js +131 -0
  52. package/migrations/004_session_owner_principal.sql +10 -0
  53. package/npm-shrinkwrap.json +2 -2
  54. package/package.json +1 -1
@@ -46,6 +46,23 @@ export interface ProviderFeatureCapability {
46
46
  details?: string;
47
47
  values?: string[];
48
48
  }
49
+ export type ProviderAcpStatus = "native_smoke_passed" | "native_candidate" | "adapter_mediated_deferred" | "absent_watchlist" | "not_applicable";
50
+ export type ProviderAcpMediation = "native" | "adapter_mediated" | "none";
51
+ export type ProviderAcpSmokeStatus = "passed" | "not_run" | "unsupported";
52
+ export interface ProviderAcpCapability {
53
+ status: ProviderAcpStatus;
54
+ mediation: ProviderAcpMediation;
55
+ targetVersion: string;
56
+ entrypoint: {
57
+ command: string;
58
+ args: string[];
59
+ } | null;
60
+ runtimeEnabled: boolean;
61
+ smokeSupported: boolean;
62
+ smokeStatus: ProviderAcpSmokeStatus;
63
+ caveats: string[];
64
+ docs: string;
65
+ }
49
66
  export type ProviderFeatureMap = Record<string, ProviderFeatureCapability>;
50
67
  export interface ProviderCapabilityControls {
51
68
  allowlist: ProviderToolControl;
@@ -54,6 +71,25 @@ export interface ProviderCapabilityControls {
54
71
  nativeSkills: ProviderToolControl;
55
72
  [name: string]: ProviderToolControl;
56
73
  }
74
+ export type AcpFrozenClassification = "native_candidate" | "adapter_mediated_deferred" | "absent_watchlist";
75
+ export interface AcpProviderContract {
76
+ classification: AcpFrozenClassification;
77
+ summary: string;
78
+ }
79
+ export interface AcpContractMetadata {
80
+ protocol: "Agent Client Protocol";
81
+ outOfScope: "Agent Communication Protocol";
82
+ mcpFrontendRemains: true;
83
+ acpIsInternalProviderTransport: true;
84
+ defaultTransport: "cli";
85
+ hostServicesDenyByDefault: true;
86
+ noRawAcpJsonRpcTool: true;
87
+ adapterSupportIsNotNative: true;
88
+ contractDoc: "docs/acp-contract.md";
89
+ nonGoals: readonly string[];
90
+ providers: Readonly<Record<ProviderCapabilityId, AcpProviderContract>>;
91
+ }
92
+ export declare const ACP_CONTRACT: AcpContractMetadata;
57
93
  export interface ProviderToolCapabilities {
58
94
  schemaVersion: "provider-tool-capabilities.v2";
59
95
  generatedAt: string;
@@ -62,6 +98,8 @@ export interface ProviderToolCapabilities {
62
98
  gatewayRequestTools: string[];
63
99
  modelInfo: CliInfo | GrokApiModelInfo;
64
100
  summary: string;
101
+ acpContract: AcpProviderContract;
102
+ acp: ProviderAcpCapability;
65
103
  controls: ProviderCapabilityControls;
66
104
  features: ProviderFeatureMap;
67
105
  discoveredSkills: ProviderSkillCapability[];
@@ -11,6 +11,146 @@ const MAX_SKILL_BYTES = 64 * 1024;
11
11
  const MAX_CONFIG_BYTES = 128 * 1024;
12
12
  const MAX_PROVIDER_TOOLS_PER_SKILL = 50;
13
13
  const CAPABILITY_CACHE_TTL_MS = 60 * 1000;
14
+ export const ACP_CONTRACT = {
15
+ protocol: "Agent Client Protocol",
16
+ outOfScope: "Agent Communication Protocol",
17
+ mcpFrontendRemains: true,
18
+ acpIsInternalProviderTransport: true,
19
+ defaultTransport: "cli",
20
+ hostServicesDenyByDefault: true,
21
+ noRawAcpJsonRpcTool: true,
22
+ adapterSupportIsNotNative: true,
23
+ contractDoc: "docs/acp-contract.md",
24
+ nonGoals: [
25
+ "Replace the MCP server.",
26
+ "Ship an outbound ACP server or frontend in this slice.",
27
+ "Wrap every provider immediately.",
28
+ "Run adapter-mediated providers by default.",
29
+ "Grant write or terminal HostServices by default.",
30
+ "Expose raw ACP JSON-RPC to agents.",
31
+ "Implement any agent-to-agent Agent Communication Protocol layer.",
32
+ ],
33
+ providers: {
34
+ mistral: {
35
+ classification: "native_candidate",
36
+ summary: "Mistral Vibe exposes native ACP via vibe-acp; first runtime pilot candidate.",
37
+ },
38
+ grok: {
39
+ classification: "native_candidate",
40
+ summary: "xAI Grok CLI exposes native ACP via grok agent stdio; second runtime pilot candidate.",
41
+ },
42
+ codex: {
43
+ classification: "adapter_mediated_deferred",
44
+ summary: "OpenAI Codex CLI has no native ACP entrypoint at the target version; adapter-mediated and deferred.",
45
+ },
46
+ claude: {
47
+ classification: "adapter_mediated_deferred",
48
+ summary: "Anthropic Claude Code has no native ACP entrypoint at the target version; adapter-mediated and deferred.",
49
+ },
50
+ gemini: {
51
+ classification: "absent_watchlist",
52
+ summary: "Google Antigravity agy 1.0.7 has no ACP surface; watchlist item only.",
53
+ },
54
+ grok_api: {
55
+ classification: "absent_watchlist",
56
+ summary: "Grok API is an HTTP provider with no ACP process transport; watchlist item only.",
57
+ },
58
+ },
59
+ };
60
+ const ACP_DOCS_REFERENCE = "docs/plans/first-class-acp-gateway-extension.dag.toml";
61
+ const ACP_CAPABILITIES = {
62
+ mistral: {
63
+ status: "native_smoke_passed",
64
+ mediation: "native",
65
+ targetVersion: "vibe 2.14.1",
66
+ entrypoint: { command: "vibe-acp", args: [] },
67
+ runtimeEnabled: false,
68
+ smokeSupported: true,
69
+ smokeStatus: "passed",
70
+ caveats: [
71
+ "Native ACP via the provider-scoped vibe-acp executable; first runtime pilot.",
72
+ "Runtime routing stays disabled until ACP is enabled in gateway config.",
73
+ ],
74
+ docs: ACP_DOCS_REFERENCE,
75
+ },
76
+ grok: {
77
+ status: "native_smoke_passed",
78
+ mediation: "native",
79
+ targetVersion: "grok 0.2.50 (cadf94855)",
80
+ entrypoint: { command: "grok", args: ["agent", "stdio"] },
81
+ runtimeEnabled: false,
82
+ smokeSupported: true,
83
+ smokeStatus: "passed",
84
+ caveats: [
85
+ "Native ACP via grok agent stdio; second runtime pilot.",
86
+ "Credential lookup is owned by the installed CLI; empty-env smoke is not expected to pass.",
87
+ "Runtime routing stays disabled until ACP is enabled in gateway config.",
88
+ ],
89
+ docs: ACP_DOCS_REFERENCE,
90
+ },
91
+ codex: {
92
+ status: "adapter_mediated_deferred",
93
+ mediation: "adapter_mediated",
94
+ targetVersion: "codex-cli 0.139.0",
95
+ entrypoint: null,
96
+ runtimeEnabled: false,
97
+ smokeSupported: false,
98
+ smokeStatus: "unsupported",
99
+ caveats: [
100
+ "No native ACP entrypoint at the target version; ACP would be adapter-mediated.",
101
+ "Adapter support requires a separate threat model and is never labelled native gateway ACP support.",
102
+ ],
103
+ docs: ACP_DOCS_REFERENCE,
104
+ },
105
+ claude: {
106
+ status: "adapter_mediated_deferred",
107
+ mediation: "adapter_mediated",
108
+ targetVersion: "claude 2.1.175",
109
+ entrypoint: null,
110
+ runtimeEnabled: false,
111
+ smokeSupported: false,
112
+ smokeStatus: "unsupported",
113
+ caveats: [
114
+ "No native Claude Code CLI ACP entrypoint at the target version; ACP would be adapter-mediated.",
115
+ "Adapter ownership, permission bridging, and install story must be specified before runtime support.",
116
+ ],
117
+ docs: ACP_DOCS_REFERENCE,
118
+ },
119
+ gemini: {
120
+ status: "absent_watchlist",
121
+ mediation: "none",
122
+ targetVersion: "agy 1.0.7",
123
+ entrypoint: null,
124
+ runtimeEnabled: false,
125
+ smokeSupported: false,
126
+ smokeStatus: "unsupported",
127
+ caveats: [
128
+ "Antigravity agy 1.0.7 has no ACP flag or subcommand.",
129
+ "Legacy Gemini CLI ACP evidence does not transfer to agy; kept on the upstream drift watchlist.",
130
+ ],
131
+ docs: ACP_DOCS_REFERENCE,
132
+ },
133
+ grok_api: {
134
+ status: "not_applicable",
135
+ mediation: "none",
136
+ targetVersion: "xAI Responses API",
137
+ entrypoint: null,
138
+ runtimeEnabled: false,
139
+ smokeSupported: false,
140
+ smokeStatus: "unsupported",
141
+ caveats: ["ACP is a CLI-stdio transport; the HTTP API provider has no ACP surface."],
142
+ docs: ACP_DOCS_REFERENCE,
143
+ },
144
+ };
145
+ function cloneAcpCapability(acp) {
146
+ return {
147
+ ...acp,
148
+ entrypoint: acp.entrypoint
149
+ ? { command: acp.entrypoint.command, args: [...acp.entrypoint.args] }
150
+ : null,
151
+ caveats: [...acp.caveats],
152
+ };
153
+ }
14
154
  const PROVIDER_CAPABILITY_IDS = [...CLI_TYPES, "grok_api"];
15
155
  const KNOWN_PROVIDER_TOOLS = {
16
156
  grok: [
@@ -640,6 +780,8 @@ function buildOneProviderToolCapabilities(cli, query) {
640
780
  gatewayRequestTool: gatewayRequestTools[0] ?? definition.gatewayRequestTools[0],
641
781
  modelInfo: getModelInfo(cli, query.refresh),
642
782
  summary: definition.summary,
783
+ acpContract: { ...ACP_CONTRACT.providers[cli] },
784
+ acp: cloneAcpCapability(ACP_CAPABILITIES[cli]),
643
785
  controls: cloneControls(definition.controls),
644
786
  features,
645
787
  discoveredSkills,
@@ -1,7 +1,11 @@
1
1
  export interface GatewayRequestContext {
2
+ transport?: "stdio" | "http";
2
3
  authKind?: "disabled" | "gateway_bearer" | "oauth";
3
4
  authScopes: string[];
4
5
  authClientId?: string;
6
+ authPrincipal?: string;
5
7
  }
8
+ export declare function resolveOwnerPrincipal(ctx: GatewayRequestContext | undefined): string;
9
+ export declare function principalCanAccess(rowOwner: string | null | undefined, caller: string): boolean;
6
10
  export declare function runWithRequestContext<T>(context: GatewayRequestContext, callback: () => T | Promise<T>): T | Promise<T>;
7
11
  export declare function getRequestContext(): GatewayRequestContext | undefined;
@@ -1,5 +1,21 @@
1
1
  import { AsyncLocalStorage } from "node:async_hooks";
2
2
  const requestContext = new AsyncLocalStorage();
3
+ export function resolveOwnerPrincipal(ctx) {
4
+ if (!ctx)
5
+ return "local";
6
+ if (ctx.authPrincipal)
7
+ return ctx.authPrincipal;
8
+ if (ctx.authKind === "gateway_bearer")
9
+ return "gateway-bearer";
10
+ return "local";
11
+ }
12
+ export function principalCanAccess(rowOwner, caller) {
13
+ if (rowOwner === caller)
14
+ return true;
15
+ if ((rowOwner === null || rowOwner === undefined) && caller === "local")
16
+ return true;
17
+ return false;
18
+ }
3
19
  export function runWithRequestContext(context, callback) {
4
20
  return requestContext.run(context, callback);
5
21
  }
@@ -98,8 +98,8 @@ export declare const CLAUDE_HIGH_IMPACT_PARAMS_SCHEMA: z.ZodEffects<z.ZodObject<
98
98
  effort: z.ZodOptional<z.ZodEnum<["low", "medium", "high", "xhigh", "max"]>>;
99
99
  excludeDynamicSystemPromptSections: z.ZodOptional<z.ZodBoolean>;
100
100
  }, "strip", z.ZodTypeAny, {
101
- agents?: Record<string, Record<string, unknown>> | undefined;
102
101
  agent?: string | undefined;
102
+ agents?: Record<string, Record<string, unknown>> | undefined;
103
103
  forkSession?: boolean | undefined;
104
104
  systemPrompt?: string | undefined;
105
105
  appendSystemPrompt?: string | undefined;
@@ -108,8 +108,8 @@ export declare const CLAUDE_HIGH_IMPACT_PARAMS_SCHEMA: z.ZodEffects<z.ZodObject<
108
108
  effort?: "medium" | "low" | "high" | "xhigh" | "max" | undefined;
109
109
  excludeDynamicSystemPromptSections?: boolean | undefined;
110
110
  }, {
111
- agents?: Record<string, Record<string, unknown>> | undefined;
112
111
  agent?: string | undefined;
112
+ agents?: Record<string, Record<string, unknown>> | undefined;
113
113
  forkSession?: boolean | undefined;
114
114
  systemPrompt?: string | undefined;
115
115
  appendSystemPrompt?: string | undefined;
@@ -118,8 +118,8 @@ export declare const CLAUDE_HIGH_IMPACT_PARAMS_SCHEMA: z.ZodEffects<z.ZodObject<
118
118
  effort?: "medium" | "low" | "high" | "xhigh" | "max" | undefined;
119
119
  excludeDynamicSystemPromptSections?: boolean | undefined;
120
120
  }>, {
121
- agents?: Record<string, Record<string, unknown>> | undefined;
122
121
  agent?: string | undefined;
122
+ agents?: Record<string, Record<string, unknown>> | undefined;
123
123
  forkSession?: boolean | undefined;
124
124
  systemPrompt?: string | undefined;
125
125
  appendSystemPrompt?: string | undefined;
@@ -128,8 +128,8 @@ export declare const CLAUDE_HIGH_IMPACT_PARAMS_SCHEMA: z.ZodEffects<z.ZodObject<
128
128
  effort?: "medium" | "low" | "high" | "xhigh" | "max" | undefined;
129
129
  excludeDynamicSystemPromptSections?: boolean | undefined;
130
130
  }, {
131
- agents?: Record<string, Record<string, unknown>> | undefined;
132
131
  agent?: string | undefined;
132
+ agents?: Record<string, Record<string, unknown>> | undefined;
133
133
  forkSession?: boolean | undefined;
134
134
  systemPrompt?: string | undefined;
135
135
  appendSystemPrompt?: string | undefined;
@@ -0,0 +1,8 @@
1
+ import type { IncomingMessage } from "node:http";
2
+ export declare class PayloadTooLargeError extends Error {
3
+ readonly statusCode = 413;
4
+ constructor(maxBytes: number);
5
+ }
6
+ export declare function maxHttpBodyBytes(env?: NodeJS.ProcessEnv): number;
7
+ export declare function maxOAuthBodyBytes(env?: NodeJS.ProcessEnv): number;
8
+ export declare function readCappedRawBody(req: IncomingMessage, maxBytes: number): Promise<string>;
@@ -0,0 +1,49 @@
1
+ export class PayloadTooLargeError extends Error {
2
+ statusCode = 413;
3
+ constructor(maxBytes) {
4
+ super(`Request body exceeds maximum size (${maxBytes} bytes)`);
5
+ this.name = "PayloadTooLargeError";
6
+ }
7
+ }
8
+ const DEFAULT_MAX_HTTP_BODY_BYTES = 8 * 1024 * 1024;
9
+ const DEFAULT_MAX_OAUTH_BODY_BYTES = 64 * 1024;
10
+ function positiveIntFromEnv(value, fallback) {
11
+ const parsed = Number(value);
12
+ return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : fallback;
13
+ }
14
+ export function maxHttpBodyBytes(env = process.env) {
15
+ return positiveIntFromEnv(env.LLM_GATEWAY_MAX_HTTP_BODY_BYTES, DEFAULT_MAX_HTTP_BODY_BYTES);
16
+ }
17
+ export function maxOAuthBodyBytes(env = process.env) {
18
+ return positiveIntFromEnv(env.LLM_GATEWAY_MAX_OAUTH_BODY_BYTES, DEFAULT_MAX_OAUTH_BODY_BYTES);
19
+ }
20
+ export function readCappedRawBody(req, maxBytes) {
21
+ return new Promise((resolve, reject) => {
22
+ const chunks = [];
23
+ let total = 0;
24
+ let aborted = false;
25
+ req.on("data", (chunk) => {
26
+ if (aborted)
27
+ return;
28
+ const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
29
+ total += buf.length;
30
+ if (total > maxBytes) {
31
+ aborted = true;
32
+ chunks.length = 0;
33
+ reject(new PayloadTooLargeError(maxBytes));
34
+ req.resume();
35
+ return;
36
+ }
37
+ chunks.push(buf);
38
+ });
39
+ req.on("error", err => {
40
+ if (!aborted)
41
+ reject(err);
42
+ });
43
+ req.on("end", () => {
44
+ if (aborted)
45
+ return;
46
+ resolve(chunks.length === 0 ? "" : Buffer.concat(chunks).toString("utf8"));
47
+ });
48
+ });
49
+ }
@@ -0,0 +1,3 @@
1
+ export declare function isRedactionEnabled(env?: NodeJS.ProcessEnv): boolean;
2
+ export declare function redactSecrets(text: string): string;
3
+ export declare function redactIfEnabled(value: string | null | undefined, enabled: boolean): typeof value;
@@ -0,0 +1,53 @@
1
+ const REDACTED = "[REDACTED]";
2
+ const RULES = [
3
+ {
4
+ label: "private-key",
5
+ pattern: /-----BEGIN (?:RSA |EC |DSA |OPENSSH |PGP |ENCRYPTED )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |DSA |OPENSSH |PGP |ENCRYPTED )?PRIVATE KEY-----/g,
6
+ },
7
+ { label: "anthropic-key", pattern: /\bsk-ant-[A-Za-z0-9_-]{16,}/g },
8
+ { label: "openai-key", pattern: /\bsk-(?:proj-)?[A-Za-z0-9_-]{16,}/g },
9
+ { label: "xai-key", pattern: /\bxai-[A-Za-z0-9]{16,}/g },
10
+ { label: "google-key", pattern: /\bAIza[0-9A-Za-z_-]{35}\b/g },
11
+ { label: "aws-access-key-id", pattern: /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g },
12
+ { label: "github-token", pattern: /\bgh[pousr]_[A-Za-z0-9]{36,}\b/g },
13
+ { label: "slack-token", pattern: /\bxox[baprs]-[A-Za-z0-9-]{10,}/g },
14
+ {
15
+ label: "jwt",
16
+ pattern: /\beyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g,
17
+ },
18
+ {
19
+ label: "bearer",
20
+ pattern: /\bBearer\s+[A-Za-z0-9._~+/=-]{12,}/g,
21
+ replace: () => `Bearer ${REDACTED}`,
22
+ },
23
+ {
24
+ label: "url-credential",
25
+ pattern: /([a-z][a-z0-9+.-]*:\/\/[^\s/:@]+):[^\s/@]+@/gi,
26
+ replace: (_m, prefix) => `${prefix}:${REDACTED}@`,
27
+ },
28
+ {
29
+ label: "secret-assignment",
30
+ pattern: /\b(password|passwd|pwd|secret[_-]?key|client[_-]?secret|api[_-]?key|access[_-]?key|auth[_-]?token)\b(\s*[:=]\s*)(?:"[^"\n]{6,}"|'[^'\n]{6,}'|[^\s"'\n,;]{6,})/gi,
31
+ replace: (_m, key, sep) => `${key}${sep}${REDACTED}`,
32
+ },
33
+ ];
34
+ export function isRedactionEnabled(env = process.env) {
35
+ const raw = (env.LLM_GATEWAY_REDACT_LOGGED_SECRETS ?? "").trim().toLowerCase();
36
+ return raw !== "0" && raw !== "false" && raw !== "off" && raw !== "no";
37
+ }
38
+ export function redactSecrets(text) {
39
+ if (!text)
40
+ return text;
41
+ let out = text;
42
+ for (const rule of RULES) {
43
+ out = rule.replace
44
+ ? out.replace(rule.pattern, rule.replace)
45
+ : out.replace(rule.pattern, REDACTED);
46
+ }
47
+ return out;
48
+ }
49
+ export function redactIfEnabled(value, enabled) {
50
+ if (!enabled || !value)
51
+ return value;
52
+ return redactSecrets(value);
53
+ }
@@ -1,4 +1,5 @@
1
1
  import { randomUUID } from "crypto";
2
+ import { getRequestContext, resolveOwnerPrincipal } from "./request-context.js";
2
3
  const DEFAULT_SESSION_DESCRIPTIONS = {
3
4
  claude: "Claude Session",
4
5
  codex: "Codex Session",
@@ -16,11 +17,12 @@ export class PostgreSQLSessionManager {
16
17
  const id = sessionId || randomUUID();
17
18
  const sessionDescription = description ?? DEFAULT_SESSION_DESCRIPTIONS[cli];
18
19
  const now = new Date().toISOString();
20
+ const ownerPrincipal = resolveOwnerPrincipal(getRequestContext());
19
21
  const client = await this.pool.connect();
20
22
  try {
21
23
  await client.query("BEGIN");
22
- await client.query(`INSERT INTO sessions (id, cli, description, created_at, last_used_at)
23
- VALUES ($1, $2, $3, $4, $5)`, [id, cli, sessionDescription, now, now]);
24
+ await client.query(`INSERT INTO sessions (id, cli, description, created_at, last_used_at, owner_principal)
25
+ VALUES ($1, $2, $3, $4, $5, $6)`, [id, cli, sessionDescription, now, now, ownerPrincipal]);
24
26
  await client.query(`INSERT INTO active_sessions (cli, session_id, updated_at)
25
27
  VALUES ($1, $2, $3)
26
28
  ON CONFLICT (cli) DO NOTHING`, [cli, id, now]);
@@ -31,6 +33,7 @@ export class PostgreSQLSessionManager {
31
33
  createdAt: now,
32
34
  lastUsedAt: now,
33
35
  description: sessionDescription,
36
+ ownerPrincipal,
34
37
  };
35
38
  }
36
39
  catch (error) {
@@ -42,18 +45,18 @@ export class PostgreSQLSessionManager {
42
45
  }
43
46
  }
44
47
  async getSession(sessionId) {
45
- const result = await this.pool.query(`SELECT id, cli, description, metadata, created_at AS "createdAt", last_used_at AS "lastUsedAt"
48
+ const result = await this.pool.query(`SELECT id, cli, description, metadata, created_at AS "createdAt", last_used_at AS "lastUsedAt", owner_principal AS "ownerPrincipal"
46
49
  FROM sessions
47
50
  WHERE id = $1`, [sessionId]);
48
51
  return result.rows[0] ?? null;
49
52
  }
50
53
  async listSessions(cli) {
51
54
  const query = cli
52
- ? `SELECT id, cli, description, metadata, created_at AS "createdAt", last_used_at AS "lastUsedAt"
55
+ ? `SELECT id, cli, description, metadata, created_at AS "createdAt", last_used_at AS "lastUsedAt", owner_principal AS "ownerPrincipal"
53
56
  FROM sessions
54
57
  WHERE cli = $1
55
58
  ORDER BY last_used_at DESC`
56
- : `SELECT id, cli, description, metadata, created_at AS "createdAt", last_used_at AS "lastUsedAt"
59
+ : `SELECT id, cli, description, metadata, created_at AS "createdAt", last_used_at AS "lastUsedAt", owner_principal AS "ownerPrincipal"
57
60
  FROM sessions
58
61
  ORDER BY last_used_at DESC`;
59
62
  const result = cli
@@ -14,6 +14,7 @@ export interface Session {
14
14
  lastUsedAt: string;
15
15
  description?: string;
16
16
  metadata?: Record<string, any>;
17
+ ownerPrincipal?: string | null;
17
18
  }
18
19
  export interface SessionStorage {
19
20
  sessions: Record<string, Session>;
@@ -4,6 +4,7 @@ import { join, dirname } from "path";
4
4
  import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync, openSync, fsyncSync, closeSync, chmodSync, } from "fs";
5
5
  import { DEFAULT_SESSION_TTL_SECONDS } from "./config.js";
6
6
  import { noopLogger } from "./logger.js";
7
+ import { getRequestContext, resolveOwnerPrincipal } from "./request-context.js";
7
8
  export const CLI_TYPES = ["claude", "codex", "gemini", "grok", "mistral"];
8
9
  export const API_PROVIDER_TYPES = ["grok-api"];
9
10
  export const PROVIDER_TYPES = [...CLI_TYPES, ...API_PROVIDER_TYPES];
@@ -113,6 +114,7 @@ export class FileSessionManager {
113
114
  createdAt: new Date().toISOString(),
114
115
  lastUsedAt: new Date().toISOString(),
115
116
  description: sessionDescription,
117
+ ownerPrincipal: resolveOwnerPrincipal(getRequestContext()),
116
118
  };
117
119
  this.storage.sessions[id] = session;
118
120
  if (!this.storage.activeSession[cli]) {
@@ -107,6 +107,20 @@ export interface ProviderSubcommandCompactCatalog {
107
107
  ];
108
108
  rows: readonly (readonly string[])[];
109
109
  }
110
+ export type AcpEntrypointStatus = "native" | "adapter_mediated_deferred" | "absent_watchlist";
111
+ export interface AcpEntrypointContract {
112
+ cli: CliType;
113
+ displayName: string;
114
+ status: AcpEntrypointStatus;
115
+ executable: string;
116
+ entrypointArgs: readonly string[];
117
+ targetVersion: string;
118
+ probeArgs: readonly (readonly string[])[];
119
+ adapterCandidates?: readonly string[];
120
+ evidence: string;
121
+ docsRef: string;
122
+ }
123
+ export declare const ACP_ENTRYPOINT_CONTRACTS: Record<CliType, AcpEntrypointContract>;
110
124
  export declare const UPSTREAM_CLI_CONTRACTS: Record<CliType, CliContract>;
111
125
  export declare function validateUpstreamCliArgs(cli: CliType, args: readonly string[]): ContractValidationResult;
112
126
  export declare function assertUpstreamCliArgs(cli: CliType, args: readonly string[]): void;
@@ -167,6 +181,19 @@ export interface InstalledCliContractProbe {
167
181
  warnings: string[];
168
182
  }
169
183
  export declare function probeInstalledCliContract(cli: CliType, timeoutMs?: number): InstalledCliContractProbe;
184
+ export interface InstalledAcpEntrypointProbe {
185
+ cli: CliType;
186
+ status: AcpEntrypointStatus;
187
+ executable: string;
188
+ entrypointArgs: readonly string[];
189
+ targetVersion: string;
190
+ checkedProbeCommands: readonly (readonly string[])[];
191
+ available: boolean | null;
192
+ entrypointDrift: boolean;
193
+ warnings: string[];
194
+ probedAt: string;
195
+ }
196
+ export declare function probeInstalledAcpEntrypoint(cli: CliType, timeoutMs?: number): InstalledAcpEntrypointProbe;
170
197
  export declare function buildUpstreamContractReport(options?: {
171
198
  cli?: CliType;
172
199
  probeInstalled?: boolean;