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.
Files changed (64) hide show
  1. package/CHANGELOG.md +75 -1
  2. package/README.md +46 -14
  3. package/dist/acp/event-normalizer.d.ts +42 -0
  4. package/dist/acp/event-normalizer.js +71 -0
  5. package/dist/acp/flight-redaction.d.ts +25 -0
  6. package/dist/acp/flight-redaction.js +40 -0
  7. package/dist/acp/host-services.d.ts +16 -0
  8. package/dist/acp/host-services.js +29 -0
  9. package/dist/acp/permission-bridge.d.ts +15 -0
  10. package/dist/acp/permission-bridge.js +90 -0
  11. package/dist/acp/process-manager.js +7 -1
  12. package/dist/acp/provider-registry.d.ts +1 -1
  13. package/dist/acp/provider-registry.js +18 -5
  14. package/dist/acp/runtime.d.ts +35 -0
  15. package/dist/acp/runtime.js +125 -0
  16. package/dist/acp/session-map.d.ts +42 -0
  17. package/dist/acp/session-map.js +67 -0
  18. package/dist/acp/smoke-harness.d.ts +28 -0
  19. package/dist/acp/smoke-harness.js +90 -0
  20. package/dist/api-http.d.ts +18 -0
  21. package/dist/api-http.js +122 -0
  22. package/dist/api-provider.d.ts +83 -0
  23. package/dist/api-provider.js +258 -0
  24. package/dist/api-request.d.ts +30 -0
  25. package/dist/api-request.js +51 -0
  26. package/dist/approval-manager.d.ts +1 -1
  27. package/dist/approval-manager.js +6 -7
  28. package/dist/async-job-manager.d.ts +19 -4
  29. package/dist/async-job-manager.js +211 -35
  30. package/dist/claude-mcp-config.d.ts +2 -2
  31. package/dist/claude-mcp-config.js +42 -52
  32. package/dist/cli-updater.js +16 -1
  33. package/dist/config.d.ts +20 -0
  34. package/dist/config.js +93 -35
  35. package/dist/doctor.d.ts +1 -1
  36. package/dist/flight-recorder.d.ts +1 -0
  37. package/dist/flight-recorder.js +11 -0
  38. package/dist/index.d.ts +56 -5
  39. package/dist/index.js +639 -38
  40. package/dist/job-store.d.ts +15 -0
  41. package/dist/job-store.js +39 -5
  42. package/dist/mcp-registry.d.ts +17 -0
  43. package/dist/mcp-registry.js +5 -0
  44. package/dist/metrics.js +7 -2
  45. package/dist/model-registry.js +11 -0
  46. package/dist/prompt-parts.d.ts +6 -6
  47. package/dist/provider-login-guidance.js +21 -0
  48. package/dist/provider-status.js +4 -1
  49. package/dist/provider-tool-capabilities.d.ts +8 -3
  50. package/dist/provider-tool-capabilities.js +107 -17
  51. package/dist/request-helpers.d.ts +6 -6
  52. package/dist/request-helpers.js +1 -4
  53. package/dist/session-manager-pg.js +2 -9
  54. package/dist/session-manager.d.ts +9 -4
  55. package/dist/session-manager.js +13 -4
  56. package/dist/upstream-contracts.js +184 -24
  57. package/dist/validation-normalizer.d.ts +2 -2
  58. package/dist/validation-orchestrator.d.ts +2 -0
  59. package/dist/validation-orchestrator.js +28 -7
  60. package/dist/validation-tools.d.ts +61 -0
  61. package/dist/validation-tools.js +36 -21
  62. package/migrations/005_provider_type_open_api_names.sql +28 -0
  63. package/npm-shrinkwrap.json +6 -5
  64. package/package.json +12 -9
@@ -32,8 +32,8 @@ export declare function resolveMistralSessionArgs(opts: {
32
32
  resumeLatest?: boolean;
33
33
  createNewSession?: boolean;
34
34
  }): SessionResumeResult;
35
- export declare const MISTRAL_AGENT_MODES: readonly ["default", "plan", "accept-edits", "auto-approve", "chat", "explore", "lean"];
36
- export type MistralAgentMode = (typeof MISTRAL_AGENT_MODES)[number];
35
+ export declare const MISTRAL_BUILTIN_AGENT_MODES: readonly ["default", "plan", "accept-edits", "auto-approve"];
36
+ export type MistralAgentMode = string;
37
37
  export declare const MISTRAL_DEFAULT_AGENT_MODE: MistralAgentMode;
38
38
  export interface PrepareMistralRequestInput {
39
39
  prompt: string;
@@ -105,7 +105,7 @@ export declare const CLAUDE_HIGH_IMPACT_PARAMS_SCHEMA: z.ZodEffects<z.ZodObject<
105
105
  appendSystemPrompt?: string | undefined;
106
106
  maxBudgetUsd?: number | undefined;
107
107
  maxTurns?: number | undefined;
108
- effort?: "medium" | "low" | "high" | "xhigh" | "max" | undefined;
108
+ effort?: "low" | "medium" | "high" | "xhigh" | "max" | undefined;
109
109
  excludeDynamicSystemPromptSections?: boolean | undefined;
110
110
  }, {
111
111
  agent?: string | undefined;
@@ -115,7 +115,7 @@ export declare const CLAUDE_HIGH_IMPACT_PARAMS_SCHEMA: z.ZodEffects<z.ZodObject<
115
115
  appendSystemPrompt?: string | undefined;
116
116
  maxBudgetUsd?: number | undefined;
117
117
  maxTurns?: number | undefined;
118
- effort?: "medium" | "low" | "high" | "xhigh" | "max" | undefined;
118
+ effort?: "low" | "medium" | "high" | "xhigh" | "max" | undefined;
119
119
  excludeDynamicSystemPromptSections?: boolean | undefined;
120
120
  }>, {
121
121
  agent?: string | undefined;
@@ -125,7 +125,7 @@ export declare const CLAUDE_HIGH_IMPACT_PARAMS_SCHEMA: z.ZodEffects<z.ZodObject<
125
125
  appendSystemPrompt?: string | undefined;
126
126
  maxBudgetUsd?: number | undefined;
127
127
  maxTurns?: number | undefined;
128
- effort?: "medium" | "low" | "high" | "xhigh" | "max" | undefined;
128
+ effort?: "low" | "medium" | "high" | "xhigh" | "max" | undefined;
129
129
  excludeDynamicSystemPromptSections?: boolean | undefined;
130
130
  }, {
131
131
  agent?: string | undefined;
@@ -135,7 +135,7 @@ export declare const CLAUDE_HIGH_IMPACT_PARAMS_SCHEMA: z.ZodEffects<z.ZodObject<
135
135
  appendSystemPrompt?: string | undefined;
136
136
  maxBudgetUsd?: number | undefined;
137
137
  maxTurns?: number | undefined;
138
- effort?: "medium" | "low" | "high" | "xhigh" | "max" | undefined;
138
+ effort?: "low" | "medium" | "high" | "xhigh" | "max" | undefined;
139
139
  excludeDynamicSystemPromptSections?: boolean | undefined;
140
140
  }>;
141
141
  export declare const CLAUDE_AGENT_DEFINITION_SCHEMA: z.ZodObject<{
@@ -93,14 +93,11 @@ export function resolveMistralSessionArgs(opts) {
93
93
  }
94
94
  return { resumeArgs: [], effectiveSessionId: undefined, userProvidedSession: false };
95
95
  }
96
- export const MISTRAL_AGENT_MODES = [
96
+ export const MISTRAL_BUILTIN_AGENT_MODES = [
97
97
  "default",
98
98
  "plan",
99
99
  "accept-edits",
100
100
  "auto-approve",
101
- "chat",
102
- "explore",
103
- "lean",
104
101
  ];
105
102
  export const MISTRAL_DEFAULT_AGENT_MODE = "auto-approve";
106
103
  export function prepareMistralRequest(input) {
@@ -1,13 +1,6 @@
1
1
  import { randomUUID } from "crypto";
2
+ import { defaultSessionDescription } from "./session-manager.js";
2
3
  import { getRequestContext, resolveOwnerPrincipal } from "./request-context.js";
3
- const DEFAULT_SESSION_DESCRIPTIONS = {
4
- claude: "Claude Session",
5
- codex: "Codex Session",
6
- gemini: "Gemini Session",
7
- grok: "Grok Session",
8
- mistral: "Mistral Session",
9
- "grok-api": "Grok API Session",
10
- };
11
4
  export class PostgreSQLSessionManager {
12
5
  pool;
13
6
  constructor(pool) {
@@ -15,7 +8,7 @@ export class PostgreSQLSessionManager {
15
8
  }
16
9
  async createSession(cli, description, sessionId) {
17
10
  const id = sessionId || randomUUID();
18
- const sessionDescription = description ?? DEFAULT_SESSION_DESCRIPTIONS[cli];
11
+ const sessionDescription = description ?? defaultSessionDescription(cli);
19
12
  const now = new Date().toISOString();
20
13
  const ownerPrincipal = resolveOwnerPrincipal(getRequestContext());
21
14
  const client = await this.pool.connect();
@@ -1,12 +1,17 @@
1
1
  import type { Config } from "./config.js";
2
2
  import type { DatabaseConnection } from "./db.js";
3
3
  import type { Logger } from "./logger.js";
4
- export declare const CLI_TYPES: readonly ["claude", "codex", "gemini", "grok", "mistral"];
4
+ export declare const CLI_TYPES: readonly ["claude", "codex", "gemini", "grok", "mistral", "devin"];
5
5
  export type CliType = (typeof CLI_TYPES)[number];
6
6
  export declare const API_PROVIDER_TYPES: readonly ["grok-api"];
7
- export type ApiProviderType = (typeof API_PROVIDER_TYPES)[number];
8
- export declare const PROVIDER_TYPES: readonly ["claude", "codex", "gemini", "grok", "mistral", "grok-api"];
9
- export type ProviderType = (typeof PROVIDER_TYPES)[number];
7
+ export type KnownApiProviderType = (typeof API_PROVIDER_TYPES)[number];
8
+ export type ApiProviderType = KnownApiProviderType | (string & {});
9
+ export type ProviderType = CliType | ApiProviderType;
10
+ export type ProviderKind = "cli" | "api";
11
+ export declare const PROVIDER_TYPES: readonly ["claude", "codex", "gemini", "grok", "mistral", "devin", "grok-api"];
12
+ export declare function isCliType(provider: string): provider is CliType;
13
+ export declare function providerKind(provider: ProviderType): ProviderKind;
14
+ export declare function defaultSessionDescription(provider: ProviderType): string;
10
15
  export interface Session {
11
16
  id: string;
12
17
  cli: ProviderType;
@@ -5,11 +5,16 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync, openSyn
5
5
  import { DEFAULT_SESSION_TTL_SECONDS } from "./config.js";
6
6
  import { noopLogger } from "./logger.js";
7
7
  import { getRequestContext, resolveOwnerPrincipal } from "./request-context.js";
8
- export const CLI_TYPES = ["claude", "codex", "gemini", "grok", "mistral"];
8
+ export const CLI_TYPES = ["claude", "codex", "gemini", "grok", "mistral", "devin"];
9
9
  export const API_PROVIDER_TYPES = ["grok-api"];
10
10
  export const PROVIDER_TYPES = [...CLI_TYPES, ...API_PROVIDER_TYPES];
11
- const createEmptyActiveSessions = () => Object.fromEntries(PROVIDER_TYPES.map(provider => [provider, null]));
12
- const DEFAULT_SESSION_DESCRIPTIONS = {
11
+ export function isCliType(provider) {
12
+ return CLI_TYPES.includes(provider);
13
+ }
14
+ export function providerKind(provider) {
15
+ return isCliType(provider) ? "cli" : "api";
16
+ }
17
+ const KNOWN_SESSION_DESCRIPTIONS = {
13
18
  claude: "Claude Session",
14
19
  codex: "Codex Session",
15
20
  gemini: "Gemini Session",
@@ -17,6 +22,10 @@ const DEFAULT_SESSION_DESCRIPTIONS = {
17
22
  mistral: "Mistral Session",
18
23
  "grok-api": "Grok API Session",
19
24
  };
25
+ export function defaultSessionDescription(provider) {
26
+ return KNOWN_SESSION_DESCRIPTIONS[provider] ?? `${provider} Session`;
27
+ }
28
+ const createEmptyActiveSessions = () => Object.fromEntries(PROVIDER_TYPES.map(provider => [provider, null]));
20
29
  export class FileSessionManager {
21
30
  storagePath;
22
31
  storage = { sessions: {}, activeSession: createEmptyActiveSessions() };
@@ -107,7 +116,7 @@ export class FileSessionManager {
107
116
  createSession(cli, description, sessionId) {
108
117
  this.evictExpiredSessions();
109
118
  const id = sessionId || randomUUID();
110
- const sessionDescription = description ?? DEFAULT_SESSION_DESCRIPTIONS[cli];
119
+ const sessionDescription = description ?? defaultSessionDescription(cli);
111
120
  const session = {
112
121
  id,
113
122
  cli,
@@ -8,7 +8,7 @@ export const ACP_ENTRYPOINT_CONTRACTS = {
8
8
  status: "native",
9
9
  executable: "vibe-acp",
10
10
  entrypointArgs: [],
11
- targetVersion: "vibe 2.14.1",
11
+ targetVersion: "vibe 2.17.1",
12
12
  probeArgs: [["--version"], ["--help"]],
13
13
  evidence: "Native ACP executable vibe-acp; manual initialize + session/new smoke passed. First runtime pilot.",
14
14
  docsRef: "docs/plans/first-class-acp-gateway-extension.dag.toml#provider_matrix.mistral",
@@ -19,9 +19,9 @@ export const ACP_ENTRYPOINT_CONTRACTS = {
19
19
  status: "native",
20
20
  executable: "grok",
21
21
  entrypointArgs: ["agent", "stdio"],
22
- targetVersion: "grok 0.2.50 (cadf94855)",
22
+ targetVersion: "grok 0.2.60 (474c2bbfc)",
23
23
  probeArgs: [["agent", "stdio", "--help"]],
24
- evidence: "Native ACP via `grok agent stdio`; initialize + session/new smoke passed with isolated leader socket. Second runtime pilot.",
24
+ evidence: "Native ACP via `grok agent stdio`; initialize + session/new smoke passed with isolated leader socket. Second runtime pilot. Bumped for 0.2.60.",
25
25
  docsRef: "docs/plans/first-class-acp-gateway-extension.dag.toml#provider_matrix.grok",
26
26
  },
27
27
  codex: {
@@ -30,10 +30,10 @@ export const ACP_ENTRYPOINT_CONTRACTS = {
30
30
  status: "adapter_mediated_deferred",
31
31
  executable: "codex",
32
32
  entrypointArgs: [],
33
- targetVersion: "codex-cli 0.139.0",
33
+ targetVersion: "codex-cli 0.141.0",
34
34
  probeArgs: [],
35
35
  adapterCandidates: ["zed-industries/codex-acp", "agentclientprotocol/codex-acp"],
36
- evidence: "No native ACP entrypoint at codex-cli 0.139.0. Adapter evidence tracked as documentation only; not native gateway ACP support.",
36
+ evidence: "No native ACP entrypoint at codex-cli 0.141.0. Adapter evidence tracked as documentation only; not native gateway ACP support.",
37
37
  docsRef: "docs/plans/first-class-acp-gateway-extension.dag.toml#provider_matrix.codex",
38
38
  },
39
39
  claude: {
@@ -42,10 +42,10 @@ export const ACP_ENTRYPOINT_CONTRACTS = {
42
42
  status: "adapter_mediated_deferred",
43
43
  executable: "claude",
44
44
  entrypointArgs: [],
45
- targetVersion: "claude 2.1.175",
45
+ targetVersion: "claude 2.1.185",
46
46
  probeArgs: [],
47
47
  adapterCandidates: ["Claude Agent SDK ACP adapter"],
48
- evidence: "No native Claude Code CLI ACP entrypoint at claude 2.1.175. Adapter ownership/permission bridging unresolved; deferred.",
48
+ evidence: "No native Claude Code CLI ACP entrypoint at claude 2.1.185. Adapter ownership/permission bridging unresolved; deferred.",
49
49
  docsRef: "docs/plans/first-class-acp-gateway-extension.dag.toml#provider_matrix.claude",
50
50
  },
51
51
  gemini: {
@@ -54,11 +54,22 @@ export const ACP_ENTRYPOINT_CONTRACTS = {
54
54
  status: "absent_watchlist",
55
55
  executable: "agy",
56
56
  entrypointArgs: [],
57
- targetVersion: "agy 1.0.7",
57
+ targetVersion: "agy 1.0.10",
58
58
  probeArgs: [],
59
- evidence: "agy 1.0.7 has no ACP flag or subcommand. Legacy Gemini CLI ACP evidence does not transfer. Watchlist item.",
59
+ evidence: "agy 1.0.10 has no ACP flag or subcommand. Legacy Gemini CLI ACP evidence does not transfer. Watchlist item.",
60
60
  docsRef: "docs/plans/first-class-acp-gateway-extension.dag.toml#provider_matrix.gemini",
61
61
  },
62
+ devin: {
63
+ cli: "devin",
64
+ displayName: "Cognition Devin CLI",
65
+ status: "native",
66
+ executable: "devin",
67
+ entrypointArgs: ["acp"],
68
+ targetVersion: "devin 2026.7.23 (3bd47f77)",
69
+ probeArgs: [["--version"]],
70
+ evidence: 'Native ACP entrypoint `devin acp` (stdio JSON-RPC). Slice D1 manual initialize + session/new smoke passed (protocolVersion 1, agent "Affogato", session created). Third native runtime pilot; routing stays config-gated.',
71
+ docsRef: "docs/plans/first-class-acp-gateway-extension.dag.toml#provider_matrix.devin",
72
+ },
62
73
  };
63
74
  const PERMISSION_MODES = [
64
75
  "default",
@@ -136,6 +147,7 @@ export const UPSTREAM_CLI_CONTRACTS = {
136
147
  agents: subcommand(["agents"], "Inspect and manage Claude agent definitions.", "writes_local_config", [
137
148
  "--add-dir",
138
149
  "--agent",
150
+ "--all",
139
151
  "--allow-dangerously-skip-permissions",
140
152
  "--cwd",
141
153
  "--dangerously-skip-permissions",
@@ -294,6 +306,7 @@ export const UPSTREAM_CLI_CONTRACTS = {
294
306
  acknowledgedUpstreamFlags: [
295
307
  "--allow-dangerously-skip-permissions",
296
308
  "--allowed",
309
+ "--ax-screen-reader",
297
310
  "--bare",
298
311
  "--betas",
299
312
  "--brief",
@@ -318,6 +331,7 @@ export const UPSTREAM_CLI_CONTRACTS = {
318
331
  "--remote-control-session-name-prefix",
319
332
  "--replay-user-messages",
320
333
  "--resume",
334
+ "--safe-mode",
321
335
  "--tmux",
322
336
  "--version",
323
337
  "--worktree",
@@ -460,33 +474,40 @@ export const UPSTREAM_CLI_CONTRACTS = {
460
474
  ], {
461
475
  children: {
462
476
  resume: subcommand(["exec", "resume"], "Resume Codex sessions from the interactive CLI.", "executes_agent", [
463
- "--add-dir",
464
477
  "--all",
465
- "--cd",
466
478
  "--config",
467
479
  "--dangerously-bypass-approvals-and-sandbox",
468
480
  "--dangerously-bypass-hook-trust",
469
481
  "--disable",
470
482
  "--enable",
483
+ "--ephemeral",
484
+ "--ignore-rules",
485
+ "--ignore-user-config",
471
486
  "--image",
472
- "--include-non-interactive",
487
+ "--json",
473
488
  "--last",
474
- "--local-provider",
475
489
  "--model",
476
- "--no-alt-screen",
477
- "--oss",
478
- "--profile",
479
- "--remote",
480
- "--remote-auth-token-env",
481
- "--sandbox",
490
+ "--output-last-message",
491
+ "--output-schema",
492
+ "--skip-git-repo-check",
482
493
  "--strict-config",
483
494
  ]),
484
495
  review: subcommand(["exec", "review"], "Run Codex code review workflows.", "executes_agent", [
485
496
  "--base",
486
497
  "--commit",
487
498
  "--config",
499
+ "--dangerously-bypass-approvals-and-sandbox",
500
+ "--dangerously-bypass-hook-trust",
488
501
  "--disable",
489
502
  "--enable",
503
+ "--ephemeral",
504
+ "--ignore-rules",
505
+ "--ignore-user-config",
506
+ "--json",
507
+ "--model",
508
+ "--output-last-message",
509
+ "--output-schema",
510
+ "--skip-git-repo-check",
490
511
  "--strict-config",
491
512
  "--title",
492
513
  "--uncommitted",
@@ -1011,6 +1032,17 @@ export const UPSTREAM_CLI_CONTRACTS = {
1011
1032
  },
1012
1033
  }),
1013
1034
  completions: subcommand(["completions"], "Generate Grok shell completions.", "read_only", ["--leader-socket"], { tier: "inspect" }),
1035
+ dashboard: subcommand(["dashboard"], "Open the Agent Dashboard view at startup.", "read_only", ["--leader-socket"], {
1036
+ tier: "inspect",
1037
+ fixtures: [
1038
+ {
1039
+ id: "grok-dashboard",
1040
+ description: "grok dashboard subcommand (leader socket passthrough)",
1041
+ args: ["--leader-socket", "/tmp/dash.sock"],
1042
+ expect: "pass",
1043
+ },
1044
+ ],
1045
+ }),
1014
1046
  export: subcommand(["export"], "Export Grok session data.", "read_only", ["--clipboard", "--leader-socket"], { tier: "inspect" }),
1015
1047
  import: subcommand(["import"], "Import Grok session data.", "writes_local_config", [
1016
1048
  "--json",
@@ -1186,7 +1218,11 @@ export const UPSTREAM_CLI_CONTRACTS = {
1186
1218
  description: "Custom leader socket path (isolated leader, Grok 0.2.32+)",
1187
1219
  },
1188
1220
  "--single": { arity: "one", description: "Single-turn prompt" },
1189
- "--todo-gate": { arity: "none", description: "Enable runtime turn-end TodoGate" },
1221
+ "--todo-gate": {
1222
+ arity: "none",
1223
+ description: "Enable runtime turn-end TodoGate (accepted at 0.2.60+ but hidden from --help)",
1224
+ hiddenFromHelp: true,
1225
+ },
1190
1226
  "--verbatim": { arity: "none", description: "Send prompt exactly as given" },
1191
1227
  "--version": { arity: "none", description: "Print version" },
1192
1228
  "--worktree": {
@@ -1197,11 +1233,13 @@ export const UPSTREAM_CLI_CONTRACTS = {
1197
1233
  arity: "one",
1198
1234
  values: ["summary", "transcript", "segments"],
1199
1235
  description: "Compaction mode (default summary; sets GROK_COMPACTION_MODE). `segments` persists per-segment markdown.",
1236
+ hiddenFromHelp: true,
1200
1237
  },
1201
1238
  "--compaction-detail": {
1202
1239
  arity: "one",
1203
1240
  values: ["none", "minimal", "balanced", "verbose"],
1204
1241
  description: "Segment verbatim detail (default verbose; sets GROK_COMPACTION_DETAIL). Only affects `--compaction-mode segments`.",
1242
+ hiddenFromHelp: true,
1205
1243
  },
1206
1244
  },
1207
1245
  env: {},
@@ -1366,7 +1404,7 @@ export const UPSTREAM_CLI_CONTRACTS = {
1366
1404
  executable: "vibe",
1367
1405
  upstream: "Mistral Vibe CLI",
1368
1406
  upstreamMetadata: {
1369
- sourceUrls: ["https://github.com/mistralai/mistral-vibe/releases"],
1407
+ sourceUrls: ["https://api.github.com/repos/mistralai/mistral-vibe/releases/latest"],
1370
1408
  packageName: "mistral-vibe",
1371
1409
  repo: "https://github.com/mistralai/mistral-vibe",
1372
1410
  installDocsUrl: "https://github.com/mistralai/mistral-vibe#installation",
@@ -1412,8 +1450,7 @@ export const UPSTREAM_CLI_CONTRACTS = {
1412
1450
  },
1413
1451
  "--agent": {
1414
1452
  arity: "one",
1415
- values: ["default", "plan", "accept-edits", "auto-approve", "chat", "explore", "lean"],
1416
- description: "Agent/permission mode",
1453
+ description: "Agent/permission mode (builtin, install-gated, or custom agent name)",
1417
1454
  },
1418
1455
  "--enabled-tools": { arity: "one", description: "Enabled tool" },
1419
1456
  "--resume": {
@@ -1449,6 +1486,7 @@ export const UPSTREAM_CLI_CONTRACTS = {
1449
1486
  description: "Additional writable workspace directory (Phase 4 slice ζ; repeat once per directory)",
1450
1487
  },
1451
1488
  },
1489
+ acknowledgedUpstreamFlags: ["--auto-approve", "--check-upgrade", "--yolo"],
1452
1490
  env: {
1453
1491
  VIBE_ACTIVE_MODEL: {
1454
1492
  arity: "one",
@@ -1547,11 +1585,25 @@ export const UPSTREAM_CLI_CONTRACTS = {
1547
1585
  },
1548
1586
  {
1549
1587
  id: "mistral-current-help-surface",
1550
- description: "Vibe 2.12.x help surface: --prompt, -v, --version, --setup accepted",
1588
+ description: "Vibe 2.17.1 request-time help surface: --prompt, -v, --version, --setup accepted",
1551
1589
  args: ["--prompt", "hello", "--agent", "auto-approve", "-v", "--version", "--setup"],
1552
1590
  env: { VIBE_ACTIVE_MODEL: "mistral-medium-3.5" },
1553
1591
  expect: "pass",
1554
1592
  },
1593
+ {
1594
+ id: "mistral-yolo-shortcut-rejected",
1595
+ description: "Vibe 2.17.1 advertises --yolo as a shortcut, but the gateway keeps using explicit --agent auto-approve",
1596
+ args: ["-p", "hello", "--yolo"],
1597
+ env: { VIBE_ACTIVE_MODEL: "mistral-medium-3.5" },
1598
+ expect: "fail",
1599
+ },
1600
+ {
1601
+ id: "mistral-check-upgrade-rejected",
1602
+ description: "Vibe 2.17.1 advertises --check-upgrade, but gateway request validation rejects update-prompt flags",
1603
+ args: ["--check-upgrade"],
1604
+ env: { VIBE_ACTIVE_MODEL: "mistral-medium-3.5" },
1605
+ expect: "fail",
1606
+ },
1555
1607
  {
1556
1608
  id: "mistral-resume-bare",
1557
1609
  description: "Vibe --resume without session ID is accepted (optional arity)",
@@ -1561,6 +1613,112 @@ export const UPSTREAM_CLI_CONTRACTS = {
1561
1613
  },
1562
1614
  ],
1563
1615
  },
1616
+ devin: {
1617
+ cli: "devin",
1618
+ executable: "devin",
1619
+ upstream: "Cognition Devin CLI",
1620
+ upstreamMetadata: {
1621
+ sourceUrls: ["https://cli.devin.ai/docs/reference/commands", "https://docs.devin.ai/cli"],
1622
+ packageName: "devin",
1623
+ installDocsUrl: "https://docs.devin.ai/cli",
1624
+ releaseChannel: "vendor",
1625
+ watchCategories: ["flags", "subcommands", "permission-modes", "acp-entrypoint"],
1626
+ },
1627
+ helpArgs: [["--help"]],
1628
+ subcommands: {},
1629
+ maxPositionals: 0,
1630
+ mcpTools: ["devin_request", "devin_request_async"],
1631
+ mcpParameters: [
1632
+ "prompt",
1633
+ "model",
1634
+ "permissionMode",
1635
+ "sessionId",
1636
+ "resumeLatest",
1637
+ "createNewSession",
1638
+ "promptFile",
1639
+ ],
1640
+ flags: {
1641
+ "-p": {
1642
+ arity: "one",
1643
+ description: "Print response and exit (non-interactive); prompt value",
1644
+ },
1645
+ "--model": { arity: "one", description: "AI model for this session" },
1646
+ "--permission-mode": {
1647
+ arity: "one",
1648
+ values: ["auto", "smart", "dangerous"],
1649
+ description: "Permission mode (auto = read-only auto-approve; smart = additionally auto-runs safe actions per fast model; dangerous = approve all)",
1650
+ },
1651
+ "--prompt-file": { arity: "one", description: "Load the initial prompt from a file" },
1652
+ "--resume": { arity: "one", description: "Resume a specific session by ID" },
1653
+ "--continue": { arity: "none", description: "Resume the most recent session in cwd" },
1654
+ },
1655
+ acknowledgedUpstreamFlags: [
1656
+ "--agent-config",
1657
+ "--config",
1658
+ "--export",
1659
+ "--print",
1660
+ "--respect-workspace-trust",
1661
+ "--sandbox",
1662
+ "--version",
1663
+ ],
1664
+ env: {},
1665
+ conformanceFixtures: [
1666
+ {
1667
+ id: "devin-minimal",
1668
+ description: "Minimal print-mode prompt request",
1669
+ args: ["-p", "hello"],
1670
+ expect: "pass",
1671
+ },
1672
+ {
1673
+ id: "devin-unsupported-flag",
1674
+ description: "Unsupported flag is rejected before spawn",
1675
+ args: ["-p", "hello", "--not-a-devin-flag"],
1676
+ expect: "fail",
1677
+ },
1678
+ {
1679
+ id: "devin-model",
1680
+ description: "--model is accepted",
1681
+ args: ["-p", "hello", "--model", "opus"],
1682
+ expect: "pass",
1683
+ },
1684
+ {
1685
+ id: "devin-permission-mode",
1686
+ description: "Valid --permission-mode 'dangerous' accepted",
1687
+ args: ["-p", "hello", "--permission-mode", "dangerous"],
1688
+ expect: "pass",
1689
+ },
1690
+ {
1691
+ id: "devin-permission-mode-auto",
1692
+ description: "Valid --permission-mode 'auto' accepted",
1693
+ args: ["-p", "hello", "--permission-mode", "auto"],
1694
+ expect: "pass",
1695
+ },
1696
+ {
1697
+ id: "devin-permission-mode-smart",
1698
+ description: "Valid --permission-mode 'smart' accepted",
1699
+ args: ["-p", "hello", "--permission-mode", "smart"],
1700
+ expect: "pass",
1701
+ },
1702
+ {
1703
+ id: "devin-permission-mode-invalid",
1704
+ description: "Invalid --permission-mode value rejected by contract",
1705
+ args: ["-p", "hello", "--permission-mode", "ludicrous"],
1706
+ expect: "fail",
1707
+ },
1708
+ {
1709
+ id: "devin-prompt-file",
1710
+ description: "--prompt-file is accepted",
1711
+ args: ["-p", "hello", "--prompt-file", "/tmp/prompt.txt"],
1712
+ expect: "pass",
1713
+ },
1714
+ {
1715
+ id: "devin-resume",
1716
+ description: "Resume by session id accepted",
1717
+ args: ["-p", "hello", "--resume", "abc12345"],
1718
+ expect: "pass",
1719
+ },
1720
+ ],
1721
+ },
1564
1722
  };
1565
1723
  export function validateUpstreamCliArgs(cli, args) {
1566
1724
  const contract = UPSTREAM_CLI_CONTRACTS[cli];
@@ -1936,6 +2094,8 @@ export function extractDiscoveredFlags(helpText) {
1936
2094
  const name = `--${match[1].toLowerCase().replace(/_/g, "-")}`;
1937
2095
  if (name === "--help")
1938
2096
  continue;
2097
+ if (name.endsWith("-"))
2098
+ continue;
1939
2099
  discovered.add(name);
1940
2100
  }
1941
2101
  }
@@ -1,5 +1,5 @@
1
- import type { AsyncJobResult, AsyncJobSnapshot } from "./async-job-manager.js";
2
- export type ValidationProvider = "claude" | "codex" | "gemini" | "grok" | "mistral";
1
+ import type { AsyncJobResult, AsyncJobSnapshot, JobProvider } from "./async-job-manager.js";
2
+ export type ValidationProvider = JobProvider;
3
3
  export type NormalizedValidationStatus = "running" | "completed" | "failed" | "canceled" | "orphaned" | "skipped";
4
4
  export interface RawJobReference {
5
5
  jobId: string;
@@ -1,11 +1,13 @@
1
1
  import type { AsyncJobManager } from "./async-job-manager.js";
2
2
  import { type ProviderRuntimeStatus } from "./provider-status.js";
3
+ import type { ApiProviderRuntime } from "./config.js";
3
4
  import { type NormalizedValidationResult, type ValidationProvider } from "./validation-normalizer.js";
4
5
  import { type ValidationReport } from "./validation-report.js";
5
6
  import { type ValidationIntent } from "./validation-prompts.js";
6
7
  export interface ValidationOrchestratorDeps {
7
8
  asyncJobManager: AsyncJobManager;
8
9
  getProviderRuntimeStatus?: (provider: ValidationProvider) => ProviderRuntimeStatus;
10
+ apiProviders?: ApiProviderRuntime[];
9
11
  }
10
12
  export interface StartValidationInput {
11
13
  intent: ValidationIntent;
@@ -1,8 +1,31 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { getProviderRuntimeStatus } from "./provider-status.js";
3
+ import { createApiProvider } from "./api-provider.js";
4
+ import { prepareApiRequest } from "./api-request.js";
3
5
  import { normalizeJobResult, normalizeSkippedProvider, normalizeStartedJob, } from "./validation-normalizer.js";
4
6
  import { buildValidationReport } from "./validation-report.js";
5
7
  import { buildJudgePrompt, buildValidationPrompt, } from "./validation-prompts.js";
8
+ function findApiReviewer(deps, provider) {
9
+ return deps.apiProviders?.find(p => p.name === provider) ?? null;
10
+ }
11
+ function resolveReviewerStatus(deps, provider) {
12
+ const api = findApiReviewer(deps, provider);
13
+ if (api) {
14
+ return { installed: true, version: null, loginStatus: "authenticated", displayName: api.name };
15
+ }
16
+ const runtimeStatus = deps.getProviderRuntimeStatus ?? getProviderRuntimeStatus;
17
+ return runtimeStatus(provider);
18
+ }
19
+ function dispatchProviderJob(deps, provider, prompt, correlationId) {
20
+ const api = findApiReviewer(deps, provider);
21
+ if (api) {
22
+ const apiProvider = createApiProvider(api.name, api.kind);
23
+ const apiRequest = prepareApiRequest(api, { prompt });
24
+ return deps.asyncJobManager.startHttpJob({ provider: apiProvider, apiRequest, correlationId })
25
+ .snapshot;
26
+ }
27
+ return deps.asyncJobManager.startJob(provider, buildProviderArgs(provider, prompt), correlationId);
28
+ }
6
29
  export function startValidationRun(deps, input) {
7
30
  const validationId = randomUUID();
8
31
  const startedAt = new Date().toISOString();
@@ -67,8 +90,7 @@ export function startJudgeSynthesis(deps, input) {
67
90
  note: "Judge synthesis requires at least one completed provider result; skipped, failed, canceled, or orphaned results are preserved in the report but are not judge evidence.",
68
91
  };
69
92
  }
70
- const runtimeStatus = deps.getProviderRuntimeStatus ?? getProviderRuntimeStatus;
71
- const runtime = runtimeStatus(input.judgeProvider);
93
+ const runtime = resolveReviewerStatus(deps, input.judgeProvider);
72
94
  if (!runtime.installed) {
73
95
  return {
74
96
  status: "skipped",
@@ -77,10 +99,10 @@ export function startJudgeSynthesis(deps, input) {
77
99
  note: `${runtime.displayName} was selected as judge but is not installed.`,
78
100
  };
79
101
  }
80
- const snapshot = deps.asyncJobManager.startJob(input.judgeProvider, buildProviderArgs(input.judgeProvider, buildJudgePrompt({
102
+ const snapshot = dispatchProviderJob(deps, input.judgeProvider, buildJudgePrompt({
81
103
  question: input.question,
82
104
  providerResults: completedResults,
83
- })), `validation-judge-${randomUUID()}-${input.judgeProvider}`);
105
+ }), `validation-judge-${randomUUID()}-${input.judgeProvider}`);
84
106
  return {
85
107
  status: "running",
86
108
  judgeModel: input.judgeProvider,
@@ -102,15 +124,14 @@ export function collectValidationJobResult(deps, provider, jobId, model, maxChar
102
124
  return normalizeJobResult(provider, model, result);
103
125
  }
104
126
  function startProviderJob(deps, provider, prompt, validationId) {
105
- const runtimeStatus = deps.getProviderRuntimeStatus ?? getProviderRuntimeStatus;
106
- const runtime = runtimeStatus(provider);
127
+ const runtime = resolveReviewerStatus(deps, provider);
107
128
  if (!runtime.installed) {
108
129
  return normalizeSkippedProvider(provider, `${runtime.displayName} runtime is not installed.`);
109
130
  }
110
131
  const warning = runtime.loginStatus === "authenticated"
111
132
  ? undefined
112
133
  : `${runtime.displayName} login status is ${runtime.loginStatus}; the job may fail until login is complete.`;
113
- const snapshot = deps.asyncJobManager.startJob(provider, buildProviderArgs(provider, prompt), `validation-${validationId}-${provider}`);
134
+ const snapshot = dispatchProviderJob(deps, provider, prompt, `validation-${validationId}-${provider}`);
114
135
  return normalizeStartedJob(provider, runtime.version, snapshot, warning);
115
136
  }
116
137
  function plannedJudgeSynthesis(input) {