llm-cli-gateway 1.4.0 → 1.5.13

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 (62) hide show
  1. package/CHANGELOG.md +135 -1
  2. package/README.md +358 -15
  3. package/dist/approval-manager.d.ts +1 -1
  4. package/dist/async-job-manager.d.ts +32 -2
  5. package/dist/async-job-manager.js +101 -16
  6. package/dist/auth.d.ts +15 -0
  7. package/dist/auth.js +46 -0
  8. package/dist/cli-updater.d.ts +19 -2
  9. package/dist/cli-updater.js +110 -7
  10. package/dist/codex-json-parser.d.ts +34 -0
  11. package/dist/codex-json-parser.js +105 -0
  12. package/dist/config.d.ts +30 -0
  13. package/dist/config.js +167 -0
  14. package/dist/doctor.d.ts +110 -0
  15. package/dist/doctor.js +280 -0
  16. package/dist/endpoint-exposure.d.ts +22 -0
  17. package/dist/endpoint-exposure.js +231 -0
  18. package/dist/entrypoint-url.d.ts +1 -0
  19. package/dist/entrypoint-url.js +5 -0
  20. package/dist/executor.d.ts +9 -1
  21. package/dist/executor.js +52 -17
  22. package/dist/flight-recorder.d.ts +3 -1
  23. package/dist/flight-recorder.js +31 -2
  24. package/dist/gateway-server.d.ts +2 -0
  25. package/dist/gateway-server.js +1 -0
  26. package/dist/gemini-json-parser.d.ts +21 -0
  27. package/dist/gemini-json-parser.js +47 -0
  28. package/dist/health.d.ts +7 -0
  29. package/dist/health.js +22 -0
  30. package/dist/http-transport.d.ts +22 -0
  31. package/dist/http-transport.js +164 -0
  32. package/dist/index.d.ts +186 -2
  33. package/dist/index.js +2761 -1454
  34. package/dist/job-store.d.ts +118 -2
  35. package/dist/job-store.js +176 -5
  36. package/dist/logger.d.ts +9 -0
  37. package/dist/logger.js +14 -0
  38. package/dist/model-registry.js +40 -6
  39. package/dist/provider-login-guidance.d.ts +21 -0
  40. package/dist/provider-login-guidance.js +98 -0
  41. package/dist/provider-status.d.ts +41 -0
  42. package/dist/provider-status.js +203 -0
  43. package/dist/request-helpers.d.ts +484 -4
  44. package/dist/request-helpers.js +613 -0
  45. package/dist/resources.js +44 -0
  46. package/dist/session-manager-pg.js +1 -0
  47. package/dist/session-manager.d.ts +1 -1
  48. package/dist/session-manager.js +2 -1
  49. package/dist/upstream-contracts.d.ts +62 -0
  50. package/dist/upstream-contracts.js +620 -0
  51. package/dist/validation-normalizer.d.ts +23 -0
  52. package/dist/validation-normalizer.js +79 -0
  53. package/dist/validation-orchestrator.d.ts +47 -0
  54. package/dist/validation-orchestrator.js +145 -0
  55. package/dist/validation-prompts.d.ts +15 -0
  56. package/dist/validation-prompts.js +52 -0
  57. package/dist/validation-report.d.ts +57 -0
  58. package/dist/validation-report.js +129 -0
  59. package/dist/validation-tools.d.ts +7 -0
  60. package/dist/validation-tools.js +198 -0
  61. package/package.json +25 -10
  62. package/setup/status.schema.json +271 -0
@@ -0,0 +1,198 @@
1
+ import { z } from "zod";
2
+ import { getCliInfo } from "./model-registry.js";
3
+ import { collectValidationJobResult, startJudgeSynthesis, startValidationRun, } from "./validation-orchestrator.js";
4
+ const providerSchema = z.enum(["claude", "codex", "gemini", "grok", "mistral"]);
5
+ const providerListSchema = z.array(providerSchema).min(1).default(["claude", "codex"]);
6
+ const normalizedProviderResultSchema = z.object({
7
+ provider: providerSchema,
8
+ model: z.string().nullable(),
9
+ status: z.enum(["running", "completed", "failed", "canceled", "orphaned", "skipped"]),
10
+ verdict: z.string().nullable(),
11
+ rationale: z.string().nullable(),
12
+ risks: z.array(z.string()).default([]),
13
+ rawJobReference: z
14
+ .object({
15
+ jobId: z.string(),
16
+ correlationId: z.string(),
17
+ statusTool: z.literal("job_status"),
18
+ resultTool: z.literal("job_result"),
19
+ })
20
+ .nullable(),
21
+ error: z.string().nullable(),
22
+ warning: z.string().optional(),
23
+ });
24
+ function textResponse(body) {
25
+ const text = responseText(body);
26
+ return {
27
+ content: [{ type: "text", text }],
28
+ structuredContent: body,
29
+ };
30
+ }
31
+ function responseText(body) {
32
+ const report = findHumanReadableReport(body);
33
+ if (report)
34
+ return report;
35
+ return JSON.stringify(body, null, 2);
36
+ }
37
+ function findHumanReadableReport(value) {
38
+ if (typeof value !== "object" || value === null)
39
+ return null;
40
+ if ("humanReadable" in value &&
41
+ typeof value.humanReadable === "string") {
42
+ return value.humanReadable;
43
+ }
44
+ if ("report" in value) {
45
+ return findHumanReadableReport(value.report);
46
+ }
47
+ return null;
48
+ }
49
+ export function registerValidationTools(server, deps) {
50
+ server.tool("validate_with_models", {
51
+ question: z.string().min(1).describe("Question or content to validate."),
52
+ models: providerListSchema.describe("Providers to ask. Defaults to Claude and Codex."),
53
+ focus: z
54
+ .string()
55
+ .default("correctness, missing assumptions, and practical next steps")
56
+ .describe("What reviewers should pay attention to."),
57
+ judgeModel: providerSchema
58
+ .optional()
59
+ .describe("Optional provider to run an explicit judge synthesis job."),
60
+ }, async ({ question, models, focus, judgeModel }) => textResponse({
61
+ success: true,
62
+ tool: "validate_with_models",
63
+ readMostly: true,
64
+ report: startValidationRun(deps, {
65
+ intent: "validate",
66
+ question,
67
+ providers: models,
68
+ focus,
69
+ judgeProvider: judgeModel,
70
+ }),
71
+ }));
72
+ server.tool("second_opinion", {
73
+ answer: z.string().min(1).describe("Answer to review."),
74
+ question: z.string().optional().describe("Original question, if available."),
75
+ model: providerSchema.default("codex").describe("Provider to ask for the second opinion."),
76
+ }, async ({ answer, question, model }) => textResponse({
77
+ success: true,
78
+ tool: "second_opinion",
79
+ readMostly: true,
80
+ report: startValidationRun(deps, {
81
+ intent: "second_opinion",
82
+ question,
83
+ content: answer,
84
+ providers: [model],
85
+ }),
86
+ }));
87
+ server.tool("compare_answers", {
88
+ question: z.string().min(1).describe("Question the answers respond to."),
89
+ answers: z.array(z.string().min(1)).min(2).describe("Two or more answers to compare."),
90
+ }, async ({ question, answers }) => textResponse({
91
+ success: true,
92
+ tool: "compare_answers",
93
+ readMostly: true,
94
+ comparison: {
95
+ question,
96
+ answerCount: answers.length,
97
+ checks: ["agreement", "contradictions", "missing evidence", "actionable recommendation"],
98
+ status: "local_summary_only",
99
+ note: "Use validate_with_models when independent provider review is needed.",
100
+ },
101
+ }));
102
+ server.tool("red_team_review", {
103
+ content: z.string().min(1).describe("Plan, answer, or document to challenge."),
104
+ riskLevel: z
105
+ .enum(["normal", "high"])
106
+ .default("normal")
107
+ .describe("How aggressively to review."),
108
+ models: providerListSchema.describe("Providers to ask for adversarial review."),
109
+ }, async ({ content, riskLevel, models }) => textResponse({
110
+ success: true,
111
+ tool: "red_team_review",
112
+ readMostly: true,
113
+ report: startValidationRun(deps, {
114
+ intent: "red_team",
115
+ content,
116
+ providers: models,
117
+ riskLevel,
118
+ }),
119
+ }));
120
+ server.tool("consensus_check", {
121
+ claim: z.string().min(1).describe("Claim to check across providers."),
122
+ models: providerListSchema.describe("Providers to ask for agreement or disagreement."),
123
+ }, async ({ claim, models }) => textResponse({
124
+ success: true,
125
+ tool: "consensus_check",
126
+ readMostly: true,
127
+ report: startValidationRun(deps, {
128
+ intent: "consensus",
129
+ content: claim,
130
+ providers: models,
131
+ }),
132
+ }));
133
+ server.tool("ask_model", {
134
+ question: z.string().min(1).describe("Question for one provider."),
135
+ model: providerSchema.default("claude").describe("Provider to ask."),
136
+ }, async ({ question, model }) => textResponse({
137
+ success: true,
138
+ tool: "ask_model",
139
+ readMostly: true,
140
+ report: startValidationRun(deps, {
141
+ intent: "ask_model",
142
+ question,
143
+ providers: [model],
144
+ }),
145
+ }));
146
+ server.tool("synthesize_validation", {
147
+ question: z.string().min(1).describe("Original request that was validated."),
148
+ providerResults: z
149
+ .array(normalizedProviderResultSchema)
150
+ .min(1)
151
+ .describe("Terminal normalized provider results from job_result."),
152
+ judgeModel: providerSchema.default("codex").describe("Provider to run the judge synthesis."),
153
+ }, async ({ question, providerResults, judgeModel }) => textResponse({
154
+ success: true,
155
+ tool: "synthesize_validation",
156
+ readMostly: true,
157
+ synthesis: startJudgeSynthesis(deps, {
158
+ question,
159
+ providerResults,
160
+ judgeProvider: judgeModel,
161
+ }),
162
+ }));
163
+ server.tool("list_available_models", {}, async () => textResponse({ success: true, models: getCliInfo() }));
164
+ server.tool("job_status", {
165
+ jobId: z.string().min(1).describe("Validation job ID."),
166
+ }, async ({ jobId }) => {
167
+ const job = deps.asyncJobManager.getJobSnapshot(jobId);
168
+ if (!job) {
169
+ return textResponse({ success: false, error: "Job not found", jobId });
170
+ }
171
+ return textResponse({ success: true, job });
172
+ });
173
+ server.tool("job_result", {
174
+ jobId: z.string().min(1).describe("Validation job ID."),
175
+ provider: providerSchema
176
+ .optional()
177
+ .describe("Provider that produced the job, used for normalized validation output."),
178
+ maxChars: z
179
+ .number()
180
+ .int()
181
+ .min(1000)
182
+ .max(2000000)
183
+ .default(200000)
184
+ .describe("Maximum result size."),
185
+ }, async ({ jobId, provider, maxChars }) => {
186
+ const result = deps.asyncJobManager.getJobResult(jobId, maxChars);
187
+ if (!result) {
188
+ return textResponse({ success: false, error: "Job not found", jobId });
189
+ }
190
+ return textResponse({
191
+ success: true,
192
+ result,
193
+ normalized: provider !== undefined
194
+ ? collectValidationJobResult(deps, provider, jobId, null, maxChars)
195
+ : null,
196
+ });
197
+ });
198
+ }
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "llm-cli-gateway",
3
- "version": "1.4.0",
3
+ "version": "1.5.13",
4
4
  "mcpName": "io.github.verivus-oss/llm-cli-gateway",
5
- "description": "MCP server providing unified access to Claude Code, Codex, Gemini, and Grok CLIs with session management, retry logic, async job orchestration, and durable job results.",
5
+ "description": "MCP server providing unified access to Claude Code, Codex, Gemini, Grok, and Mistral Vibe CLIs with session management, retry logic, async job orchestration, durable job results, and cross-LLM validation.",
6
6
  "license": "MIT",
7
7
  "author": {
8
8
  "name": "VerivusAI Labs",
@@ -10,7 +10,7 @@
10
10
  },
11
11
  "repository": {
12
12
  "type": "git",
13
- "url": "https://github.com/verivus-oss/llm-cli-gateway.git"
13
+ "url": "git+https://github.com/verivus-oss/llm-cli-gateway.git"
14
14
  },
15
15
  "homepage": "https://github.com/verivus-oss/llm-cli-gateway#readme",
16
16
  "bugs": {
@@ -33,10 +33,14 @@
33
33
  ".": {
34
34
  "types": "./dist/index.d.ts",
35
35
  "default": "./dist/index.js"
36
+ },
37
+ "./gateway-server": {
38
+ "types": "./dist/gateway-server.d.ts",
39
+ "default": "./dist/gateway-server.js"
36
40
  }
37
41
  },
38
42
  "bin": {
39
- "llm-cli-gateway": "./dist/index.js"
43
+ "llm-cli-gateway": "dist/index.js"
40
44
  },
41
45
  "engines": {
42
46
  "node": ">=20.0.0"
@@ -45,6 +49,7 @@
45
49
  "dist/**/*.js",
46
50
  "dist/**/*.d.ts",
47
51
  "!dist/__tests__/**",
52
+ "setup/status.schema.json",
48
53
  "README.md",
49
54
  "CHANGELOG.md",
50
55
  "LICENSE"
@@ -54,8 +59,11 @@
54
59
  "build:all": "tsc",
55
60
  "prepublishOnly": "npm run build && npm test",
56
61
  "start": "node dist/index.js",
62
+ "start:http": "node dist/index.js --transport=http",
63
+ "doctor": "node dist/index.js doctor --json",
57
64
  "migrate": "node dist/migrate.js",
58
65
  "test": "vitest run",
66
+ "test:ci": "vitest run --pool=forks --maxWorkers=1",
59
67
  "test:coverage": "vitest run --coverage",
60
68
  "test:watch": "vitest",
61
69
  "test:unit": "vitest run src/__tests__/executor.test.ts",
@@ -68,11 +76,14 @@
68
76
  "lint:fix": "eslint src/**/*.ts --fix",
69
77
  "format": "prettier --write 'src/**/*.ts'",
70
78
  "format:check": "prettier --check 'src/**/*.ts'",
71
- "check": "npm run build && npm run lint && npm test"
79
+ "check": "npm run build && npm run lint && npm test",
80
+ "release:build": "bash installer/build-release.sh",
81
+ "release:checksums": "cd installer/dist && sha256sum --check SHA256SUMS",
82
+ "release:docker": "docker compose -f docker-compose.personal.yml build"
72
83
  },
73
84
  "dependencies": {
74
- "@modelcontextprotocol/sdk": "^1.0.0",
75
- "better-sqlite3": "^12.9.0",
85
+ "@modelcontextprotocol/sdk": "^1.29.0",
86
+ "better-sqlite3": "^12.10.0",
76
87
  "ioredis": "^5.4.1",
77
88
  "pg": "^8.12.0",
78
89
  "toml": "^3.0.0",
@@ -82,13 +93,17 @@
82
93
  "@types/better-sqlite3": "^7.6.0",
83
94
  "@types/node": "^20.19.30",
84
95
  "@types/pg": "^8.11.10",
85
- "@typescript-eslint/eslint-plugin": "^6.0.0",
86
- "@typescript-eslint/parser": "^6.0.0",
96
+ "@typescript-eslint/eslint-plugin": "^8.59.4",
97
+ "@typescript-eslint/parser": "^8.59.4",
87
98
  "@vitest/coverage-v8": "^4.1.2",
88
- "eslint": "^8.0.0",
99
+ "eslint": "^8.57.1",
89
100
  "eslint-config-prettier": "^9.0.0",
90
101
  "prettier": "^3.0.0",
91
102
  "typescript": "^5.0.0",
92
103
  "vitest": "^4.0.18"
104
+ },
105
+ "overrides": {
106
+ "type-is": "2.0.1",
107
+ "content-type": "1.0.5"
93
108
  }
94
109
  }
@@ -0,0 +1,271 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://verivus.ai/schemas/llm-cli-gateway/status.schema.json",
4
+ "title": "llm-cli-gateway doctor status",
5
+ "type": "object",
6
+ "required": [
7
+ "schema_version",
8
+ "ok",
9
+ "generated_at",
10
+ "system",
11
+ "gateway",
12
+ "transport",
13
+ "auth",
14
+ "providers",
15
+ "endpoint_exposure",
16
+ "client_config",
17
+ "next_actions"
18
+ ],
19
+ "properties": {
20
+ "schema_version": { "const": "1.0" },
21
+ "ok": { "type": "boolean" },
22
+ "generated_at": { "type": "string", "format": "date-time" },
23
+ "system": {
24
+ "type": "object",
25
+ "required": ["os", "arch", "release", "node_version"],
26
+ "properties": {
27
+ "os": { "type": "string" },
28
+ "arch": { "type": "string" },
29
+ "release": { "type": "string" },
30
+ "node_version": { "type": "string" }
31
+ },
32
+ "additionalProperties": false
33
+ },
34
+ "gateway": {
35
+ "type": "object",
36
+ "required": ["name", "version"],
37
+ "properties": {
38
+ "name": { "const": "llm-cli-gateway" },
39
+ "version": { "type": "string" }
40
+ },
41
+ "additionalProperties": false
42
+ },
43
+ "transport": {
44
+ "type": "object",
45
+ "required": ["default", "http"],
46
+ "properties": {
47
+ "default": { "enum": ["stdio", "http"] },
48
+ "http": {
49
+ "type": "object",
50
+ "required": ["enabled", "host", "port", "path", "public_url_configured", "public_url"],
51
+ "properties": {
52
+ "enabled": { "type": "boolean" },
53
+ "host": { "type": "string" },
54
+ "port": { "type": "integer", "minimum": 1, "maximum": 65535 },
55
+ "path": { "type": "string" },
56
+ "public_url_configured": { "type": "boolean" },
57
+ "public_url": { "type": ["string", "null"] }
58
+ },
59
+ "additionalProperties": false
60
+ }
61
+ },
62
+ "additionalProperties": false
63
+ },
64
+ "auth": {
65
+ "type": "object",
66
+ "required": ["required", "token_configured", "source"],
67
+ "properties": {
68
+ "required": { "type": "boolean" },
69
+ "token_configured": { "type": "boolean" },
70
+ "source": { "enum": ["env", "disabled"] }
71
+ },
72
+ "additionalProperties": false
73
+ },
74
+ "providers": {
75
+ "type": "object",
76
+ "required": ["claude", "codex", "gemini", "grok", "mistral"],
77
+ "additionalProperties": {
78
+ "type": "object",
79
+ "required": [
80
+ "cli_available",
81
+ "version",
82
+ "login_status",
83
+ "version_command",
84
+ "login_check",
85
+ "install_guidance",
86
+ "login_guidance"
87
+ ],
88
+ "properties": {
89
+ "cli_available": { "type": "boolean" },
90
+ "version": { "type": ["string", "null"] },
91
+ "login_status": {
92
+ "enum": ["authenticated", "not_authenticated", "unknown", "not_checked"]
93
+ },
94
+ "version_command": {
95
+ "type": "array",
96
+ "items": { "type": "string" }
97
+ },
98
+ "login_check": {
99
+ "type": "object",
100
+ "required": ["method", "command", "credential_store", "detail"],
101
+ "properties": {
102
+ "method": { "enum": ["cli", "credential_store", "not_checked"] },
103
+ "command": {
104
+ "type": ["array", "null"],
105
+ "items": { "type": "string" }
106
+ },
107
+ "credential_store": { "enum": ["present", "not_found", "not_checked"] },
108
+ "detail": { "type": "string" }
109
+ },
110
+ "additionalProperties": false
111
+ },
112
+ "install_guidance": {
113
+ "type": "object",
114
+ "required": ["summary", "commands"],
115
+ "properties": {
116
+ "summary": { "type": "string" },
117
+ "commands": {
118
+ "type": "array",
119
+ "items": { "type": "string" }
120
+ },
121
+ "documentation_url": { "type": "string" }
122
+ },
123
+ "additionalProperties": false
124
+ },
125
+ "login_guidance": {
126
+ "type": "object",
127
+ "required": ["summary", "commands", "credential_handling"],
128
+ "properties": {
129
+ "summary": { "type": "string" },
130
+ "commands": {
131
+ "type": "array",
132
+ "items": { "type": "string" }
133
+ },
134
+ "credential_handling": { "type": "string" }
135
+ },
136
+ "additionalProperties": false
137
+ }
138
+ },
139
+ "additionalProperties": false
140
+ }
141
+ },
142
+ "endpoint_exposure": {
143
+ "type": "object",
144
+ "required": [
145
+ "mode",
146
+ "local_url",
147
+ "public_url_configured",
148
+ "public_url",
149
+ "https_required_for_web",
150
+ "https_configured",
151
+ "web_clients_supported",
152
+ "tunnel_provider",
153
+ "reachable_from_web",
154
+ "verification",
155
+ "next_actions"
156
+ ],
157
+ "properties": {
158
+ "mode": {
159
+ "enum": ["local_only", "lan", "tunnel", "byo_reverse_proxy", "misconfigured"]
160
+ },
161
+ "local_url": { "type": "string" },
162
+ "public_url_configured": { "type": "boolean" },
163
+ "public_url": { "type": ["string", "null"] },
164
+ "https_required_for_web": { "type": "boolean" },
165
+ "https_configured": { "type": "boolean" },
166
+ "web_clients_supported": { "type": "boolean" },
167
+ "tunnel_provider": { "type": ["string", "null"] },
168
+ "reachable_from_web": { "enum": ["not_checked", "reachable", "unreachable"] },
169
+ "verification": {
170
+ "type": "object",
171
+ "required": ["method", "checked_url", "status_code", "error"],
172
+ "properties": {
173
+ "method": { "enum": ["not_checked", "http_head"] },
174
+ "checked_url": { "type": ["string", "null"] },
175
+ "status_code": { "type": ["integer", "null"] },
176
+ "error": { "type": ["string", "null"] }
177
+ },
178
+ "additionalProperties": false
179
+ },
180
+ "next_actions": {
181
+ "type": "array",
182
+ "items": { "type": "string" }
183
+ }
184
+ },
185
+ "additionalProperties": false
186
+ },
187
+ "client_config": {
188
+ "type": "object",
189
+ "required": [
190
+ "claude_desktop_config_present",
191
+ "codex_config_present",
192
+ "gemini_settings_present",
193
+ "gemini_config",
194
+ "vibe_session_logging"
195
+ ],
196
+ "properties": {
197
+ "claude_desktop_config_present": { "type": "boolean" },
198
+ "codex_config_present": { "type": "boolean" },
199
+ "gemini_settings_present": { "type": "boolean" },
200
+ "gemini_config": {
201
+ "type": "object",
202
+ "required": [
203
+ "project_gemini_md_present",
204
+ "project_gemini_md_path",
205
+ "user_gemini_md_present",
206
+ "user_gemini_md_path",
207
+ "settings_json_present",
208
+ "settings_json_path",
209
+ "mcp_servers_registered",
210
+ "mcp_reconciliation",
211
+ "next_actions"
212
+ ],
213
+ "properties": {
214
+ "project_gemini_md_present": { "type": "boolean" },
215
+ "project_gemini_md_path": { "type": "string" },
216
+ "user_gemini_md_present": { "type": "boolean" },
217
+ "user_gemini_md_path": { "type": "string" },
218
+ "settings_json_present": { "type": "boolean" },
219
+ "settings_json_path": { "type": "string" },
220
+ "mcp_servers_registered": {
221
+ "type": "array",
222
+ "items": { "type": "string" }
223
+ },
224
+ "mcp_reconciliation": {
225
+ "type": "object",
226
+ "required": ["whitelisted", "missing_from_settings"],
227
+ "properties": {
228
+ "whitelisted": {
229
+ "type": "array",
230
+ "items": { "type": "string" }
231
+ },
232
+ "missing_from_settings": {
233
+ "type": "array",
234
+ "items": { "type": "string" }
235
+ }
236
+ },
237
+ "additionalProperties": false
238
+ },
239
+ "next_actions": {
240
+ "type": "array",
241
+ "items": { "type": "string" }
242
+ }
243
+ },
244
+ "additionalProperties": false
245
+ },
246
+ "vibe_session_logging": {
247
+ "type": "object",
248
+ "required": [
249
+ "config_path",
250
+ "config_present",
251
+ "session_logging_enabled",
252
+ "note"
253
+ ],
254
+ "properties": {
255
+ "config_path": { "type": "string" },
256
+ "config_present": { "type": "boolean" },
257
+ "session_logging_enabled": { "type": "boolean" },
258
+ "note": { "type": "string" }
259
+ },
260
+ "additionalProperties": false
261
+ }
262
+ },
263
+ "additionalProperties": false
264
+ },
265
+ "next_actions": {
266
+ "type": "array",
267
+ "items": { "type": "string" }
268
+ }
269
+ },
270
+ "additionalProperties": false
271
+ }