@simonren/quorum 0.7.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/LICENSE +21 -0
- package/README.md +144 -0
- package/commands/multi-consult.md +109 -0
- package/commands/multi-review.md +139 -0
- package/dist/adapters/base.d.ts +120 -0
- package/dist/adapters/base.js +98 -0
- package/dist/adapters/claude.d.ts +25 -0
- package/dist/adapters/claude.js +217 -0
- package/dist/adapters/codex.d.ts +20 -0
- package/dist/adapters/codex.js +227 -0
- package/dist/adapters/gemini.d.ts +20 -0
- package/dist/adapters/gemini.js +197 -0
- package/dist/adapters/index.d.ts +12 -0
- package/dist/adapters/index.js +15 -0
- package/dist/cli/check.d.ts +20 -0
- package/dist/cli/check.js +78 -0
- package/dist/cli/codex.d.ts +11 -0
- package/dist/cli/codex.js +255 -0
- package/dist/cli/gemini.d.ts +12 -0
- package/dist/cli/gemini.js +253 -0
- package/dist/commands.d.ts +28 -0
- package/dist/commands.js +105 -0
- package/dist/config.d.ts +244 -0
- package/dist/config.js +179 -0
- package/dist/consult-prompt.d.ts +10 -0
- package/dist/consult-prompt.js +72 -0
- package/dist/context.d.ts +1538 -0
- package/dist/context.js +383 -0
- package/dist/decoders/claude.d.ts +53 -0
- package/dist/decoders/claude.js +106 -0
- package/dist/decoders/codex.d.ts +71 -0
- package/dist/decoders/codex.js +145 -0
- package/dist/decoders/gemini.d.ts +33 -0
- package/dist/decoders/gemini.js +58 -0
- package/dist/decoders/index.d.ts +6 -0
- package/dist/decoders/index.js +3 -0
- package/dist/errors.d.ts +46 -0
- package/dist/errors.js +192 -0
- package/dist/executor.d.ts +103 -0
- package/dist/executor.js +244 -0
- package/dist/handoff.d.ts +270 -0
- package/dist/handoff.js +599 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +134 -0
- package/dist/pipeline.d.ts +135 -0
- package/dist/pipeline.js +462 -0
- package/dist/prompt-v2.d.ts +38 -0
- package/dist/prompt-v2.js +391 -0
- package/dist/prompt.d.ts +71 -0
- package/dist/prompt.js +309 -0
- package/dist/schema.d.ts +660 -0
- package/dist/schema.js +536 -0
- package/dist/tools/consult.d.ts +104 -0
- package/dist/tools/consult.js +220 -0
- package/dist/tools/feedback.d.ts +91 -0
- package/dist/tools/feedback.js +117 -0
- package/dist/types.d.ts +105 -0
- package/dist/types.js +31 -0
- package/package.json +54 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gemini CLI Wrapper
|
|
3
|
+
*
|
|
4
|
+
* Uses Google's Gemini CLI in non-interactive mode (gemini -p)
|
|
5
|
+
* Reference: https://github.com/google-gemini/gemini-cli
|
|
6
|
+
* Package: @google/gemini-cli
|
|
7
|
+
*/
|
|
8
|
+
import { spawn } from 'child_process';
|
|
9
|
+
import { existsSync } from 'fs';
|
|
10
|
+
import { build7SectionPrompt, buildDeveloperInstructions, buildRetryPrompt, isValidFeedbackOutput } from '../prompt.js';
|
|
11
|
+
import { createTimeoutError, createCliNotFoundError, getSuggestion } from '../errors.js';
|
|
12
|
+
// Activity-based timeout: reset on output, kill on silence
|
|
13
|
+
const INACTIVITY_TIMEOUT_MS = 120000; // 2 min of no output = timeout
|
|
14
|
+
const MAX_TIMEOUT_MS = 3600000; // 60 min absolute max (edge case safety)
|
|
15
|
+
const MAX_RETRIES = 2;
|
|
16
|
+
const MAX_BUFFER_SIZE = 1024 * 1024; // 1MB max buffer to prevent memory issues
|
|
17
|
+
/**
|
|
18
|
+
* Run Gemini CLI with the given request
|
|
19
|
+
*/
|
|
20
|
+
export async function runGeminiReview(request) {
|
|
21
|
+
// Validate workingDir exists before running
|
|
22
|
+
if (!existsSync(request.workingDir)) {
|
|
23
|
+
return {
|
|
24
|
+
success: false,
|
|
25
|
+
error: {
|
|
26
|
+
type: 'cli_error',
|
|
27
|
+
cli: 'gemini',
|
|
28
|
+
exitCode: -1,
|
|
29
|
+
stderr: `Working directory does not exist: ${request.workingDir}`
|
|
30
|
+
},
|
|
31
|
+
suggestion: 'Check that the working directory path is correct',
|
|
32
|
+
model: 'gemini'
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
return runWithRetry(request, 0);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Run Gemini with retry logic
|
|
39
|
+
*/
|
|
40
|
+
async function runWithRetry(request, attempt, previousError, previousOutput) {
|
|
41
|
+
try {
|
|
42
|
+
// Build the prompt (use retry prompt if this is a retry)
|
|
43
|
+
const basePrompt = attempt === 0
|
|
44
|
+
? build7SectionPrompt(request)
|
|
45
|
+
: buildRetryPrompt(request, attempt + 1, previousError, previousOutput);
|
|
46
|
+
const developerInstructions = buildDeveloperInstructions('gemini');
|
|
47
|
+
// Combine developer instructions with the prompt
|
|
48
|
+
// Gemini CLI doesn't have a separate system instruction flag in non-interactive mode
|
|
49
|
+
const fullPrompt = `${developerInstructions}\n\n---\n\n${basePrompt}`;
|
|
50
|
+
// Run the CLI
|
|
51
|
+
const result = await runGeminiCli(fullPrompt, request.workingDir);
|
|
52
|
+
// Check for CLI errors
|
|
53
|
+
if (result.exitCode !== 0) {
|
|
54
|
+
// Check for specific error patterns in stderr
|
|
55
|
+
if (result.stderr.toLowerCase().includes('rate limit') ||
|
|
56
|
+
result.stderr.toLowerCase().includes('quota')) {
|
|
57
|
+
return {
|
|
58
|
+
success: false,
|
|
59
|
+
error: {
|
|
60
|
+
type: 'rate_limit',
|
|
61
|
+
cli: 'gemini',
|
|
62
|
+
retryAfterMs: parseRetryAfter(result.stderr)
|
|
63
|
+
},
|
|
64
|
+
suggestion: 'Wait and retry, or use /codex-review instead',
|
|
65
|
+
model: 'gemini'
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
if (result.stderr.toLowerCase().includes('unauthorized') ||
|
|
69
|
+
result.stderr.toLowerCase().includes('authentication') ||
|
|
70
|
+
result.stderr.toLowerCase().includes('api key') ||
|
|
71
|
+
result.stderr.includes('401') ||
|
|
72
|
+
result.stderr.includes('403')) {
|
|
73
|
+
return {
|
|
74
|
+
success: false,
|
|
75
|
+
error: {
|
|
76
|
+
type: 'auth_error',
|
|
77
|
+
cli: 'gemini',
|
|
78
|
+
message: result.stderr
|
|
79
|
+
},
|
|
80
|
+
suggestion: 'Run `gemini` and follow the authentication prompts, or set GEMINI_API_KEY',
|
|
81
|
+
model: 'gemini'
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
success: false,
|
|
86
|
+
error: {
|
|
87
|
+
type: 'cli_error',
|
|
88
|
+
cli: 'gemini',
|
|
89
|
+
exitCode: result.exitCode,
|
|
90
|
+
stderr: result.stderr
|
|
91
|
+
},
|
|
92
|
+
model: 'gemini'
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
// Check for buffer truncation warning
|
|
96
|
+
if (result.truncated) {
|
|
97
|
+
return {
|
|
98
|
+
success: false,
|
|
99
|
+
error: {
|
|
100
|
+
type: 'cli_error',
|
|
101
|
+
cli: 'gemini',
|
|
102
|
+
exitCode: 0,
|
|
103
|
+
stderr: 'Output exceeded maximum buffer size (1MB) and was truncated'
|
|
104
|
+
},
|
|
105
|
+
suggestion: 'Try reviewing a smaller scope with --focus',
|
|
106
|
+
model: 'gemini'
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
// Validate the response structure
|
|
110
|
+
if (!isValidFeedbackOutput(result.stdout)) {
|
|
111
|
+
if (attempt < MAX_RETRIES) {
|
|
112
|
+
// Retry with history
|
|
113
|
+
return runWithRetry(request, attempt + 1, 'Output missing required sections (Agreements, Disagreements, Additions, Alternatives, Risk Assessment)', result.stdout);
|
|
114
|
+
}
|
|
115
|
+
// Max retries reached, return invalid response error
|
|
116
|
+
return {
|
|
117
|
+
success: false,
|
|
118
|
+
error: {
|
|
119
|
+
type: 'invalid_response',
|
|
120
|
+
cli: 'gemini',
|
|
121
|
+
rawOutput: result.stdout
|
|
122
|
+
},
|
|
123
|
+
suggestion: getSuggestion({ type: 'invalid_response', cli: 'gemini', rawOutput: result.stdout }),
|
|
124
|
+
model: 'gemini'
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
success: true,
|
|
129
|
+
feedback: result.stdout,
|
|
130
|
+
model: 'gemini'
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
const err = error;
|
|
135
|
+
// Handle CLI not found (ENOENT for the gemini binary itself)
|
|
136
|
+
if (err.code === 'ENOENT') {
|
|
137
|
+
return {
|
|
138
|
+
success: false,
|
|
139
|
+
error: createCliNotFoundError('gemini'),
|
|
140
|
+
suggestion: getSuggestion(createCliNotFoundError('gemini')),
|
|
141
|
+
model: 'gemini'
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
if (err.message === 'TIMEOUT' || err.message === 'MAX_TIMEOUT') {
|
|
145
|
+
const isMaxTimeout = err.message === 'MAX_TIMEOUT';
|
|
146
|
+
const timeoutMs = isMaxTimeout ? MAX_TIMEOUT_MS : INACTIVITY_TIMEOUT_MS;
|
|
147
|
+
return {
|
|
148
|
+
success: false,
|
|
149
|
+
error: createTimeoutError('gemini', timeoutMs),
|
|
150
|
+
suggestion: isMaxTimeout
|
|
151
|
+
? 'Task exceeded 60 minute maximum. Try a smaller scope.'
|
|
152
|
+
: 'No output for 2 minutes. Process may be hung. Try a smaller scope or use --focus.',
|
|
153
|
+
model: 'gemini'
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
// Generic error
|
|
157
|
+
return {
|
|
158
|
+
success: false,
|
|
159
|
+
error: {
|
|
160
|
+
type: 'cli_error',
|
|
161
|
+
cli: 'gemini',
|
|
162
|
+
exitCode: -1,
|
|
163
|
+
stderr: err.message
|
|
164
|
+
},
|
|
165
|
+
model: 'gemini'
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Execute the Gemini CLI in non-interactive mode
|
|
171
|
+
*
|
|
172
|
+
* Uses: gemini --yolo --include-directories <workingDir> "<prompt>"
|
|
173
|
+
*/
|
|
174
|
+
function runGeminiCli(prompt, workingDir) {
|
|
175
|
+
return new Promise((resolve, reject) => {
|
|
176
|
+
// Build CLI arguments for non-interactive execution
|
|
177
|
+
// Use positional prompt (not deprecated -p flag) and --yolo for auto-approval
|
|
178
|
+
const args = [
|
|
179
|
+
'--yolo',
|
|
180
|
+
'--model', 'gemini-3.1-pro-preview',
|
|
181
|
+
'--include-directories', workingDir,
|
|
182
|
+
prompt
|
|
183
|
+
];
|
|
184
|
+
const proc = spawn('gemini', args, {
|
|
185
|
+
cwd: workingDir,
|
|
186
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
187
|
+
env: { ...process.env }
|
|
188
|
+
});
|
|
189
|
+
let stdout = '';
|
|
190
|
+
let stderr = '';
|
|
191
|
+
let truncated = false;
|
|
192
|
+
let inactivityTimer;
|
|
193
|
+
// Max timeout - absolute cap (60 min)
|
|
194
|
+
const maxTimer = setTimeout(() => {
|
|
195
|
+
proc.kill('SIGTERM');
|
|
196
|
+
reject(new Error('MAX_TIMEOUT'));
|
|
197
|
+
}, MAX_TIMEOUT_MS);
|
|
198
|
+
// Activity-based timeout - reset on any output
|
|
199
|
+
const resetInactivityTimer = () => {
|
|
200
|
+
clearTimeout(inactivityTimer);
|
|
201
|
+
inactivityTimer = setTimeout(() => {
|
|
202
|
+
proc.kill('SIGTERM');
|
|
203
|
+
reject(new Error('TIMEOUT'));
|
|
204
|
+
}, INACTIVITY_TIMEOUT_MS);
|
|
205
|
+
};
|
|
206
|
+
// Start inactivity timer
|
|
207
|
+
resetInactivityTimer();
|
|
208
|
+
proc.stdout.on('data', (data) => {
|
|
209
|
+
resetInactivityTimer(); // Still streaming = reset timer
|
|
210
|
+
if (stdout.length < MAX_BUFFER_SIZE) {
|
|
211
|
+
stdout += data.toString();
|
|
212
|
+
if (stdout.length > MAX_BUFFER_SIZE) {
|
|
213
|
+
stdout = stdout.slice(0, MAX_BUFFER_SIZE);
|
|
214
|
+
truncated = true;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
proc.stderr.on('data', (data) => {
|
|
219
|
+
resetInactivityTimer(); // Still streaming = reset timer
|
|
220
|
+
if (stderr.length < MAX_BUFFER_SIZE) {
|
|
221
|
+
stderr += data.toString();
|
|
222
|
+
if (stderr.length > MAX_BUFFER_SIZE) {
|
|
223
|
+
stderr = stderr.slice(0, MAX_BUFFER_SIZE);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
proc.on('close', (code) => {
|
|
228
|
+
clearTimeout(inactivityTimer);
|
|
229
|
+
clearTimeout(maxTimer);
|
|
230
|
+
resolve({
|
|
231
|
+
stdout,
|
|
232
|
+
stderr,
|
|
233
|
+
exitCode: code ?? -1,
|
|
234
|
+
truncated
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
proc.on('error', (err) => {
|
|
238
|
+
clearTimeout(inactivityTimer);
|
|
239
|
+
clearTimeout(maxTimer);
|
|
240
|
+
reject(err);
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Parse retry-after duration from error message
|
|
246
|
+
*/
|
|
247
|
+
function parseRetryAfter(errorMessage) {
|
|
248
|
+
const match = errorMessage.match(/retry[- ]?after[:\s]+(\d+)/i);
|
|
249
|
+
if (match) {
|
|
250
|
+
return parseInt(match[1]) * 1000; // Convert to ms
|
|
251
|
+
}
|
|
252
|
+
return undefined;
|
|
253
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared module for slash command installation
|
|
3
|
+
*
|
|
4
|
+
* Used by index.ts (auto-install on MCP server startup and `update` subcommand)
|
|
5
|
+
*/
|
|
6
|
+
export interface InstallResult {
|
|
7
|
+
success: boolean;
|
|
8
|
+
installed: string[];
|
|
9
|
+
removed: string[];
|
|
10
|
+
error?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Get source and target paths for command files
|
|
14
|
+
*/
|
|
15
|
+
export declare function getCommandPaths(): {
|
|
16
|
+
source: string;
|
|
17
|
+
target: string;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Install slash commands to ~/.claude/commands/
|
|
21
|
+
*
|
|
22
|
+
* @param overrides Test-only path overrides; production callers pass nothing.
|
|
23
|
+
* @returns Result object with success status and installed commands
|
|
24
|
+
*/
|
|
25
|
+
export declare function installCommands(overrides?: Partial<{
|
|
26
|
+
source: string;
|
|
27
|
+
target: string;
|
|
28
|
+
}>): InstallResult;
|
package/dist/commands.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared module for slash command installation
|
|
3
|
+
*
|
|
4
|
+
* Used by index.ts (auto-install on MCP server startup and `update` subcommand)
|
|
5
|
+
*/
|
|
6
|
+
import { existsSync, mkdirSync, copyFileSync, readdirSync, renameSync, statSync } from 'fs';
|
|
7
|
+
import { join, dirname } from 'path';
|
|
8
|
+
import { homedir } from 'os';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = dirname(__filename);
|
|
12
|
+
/** Old command filenames that should be pruned on upgrade */
|
|
13
|
+
const DEPRECATED_COMMANDS = [
|
|
14
|
+
'codex.md',
|
|
15
|
+
'gemini.md',
|
|
16
|
+
'multi.md',
|
|
17
|
+
'codex-xhigh.md',
|
|
18
|
+
'ask-codex.md',
|
|
19
|
+
'ask-gemini.md',
|
|
20
|
+
'ask-multi.md',
|
|
21
|
+
'multi-review-adv.md',
|
|
22
|
+
// Removed in favor of /multi-review and /multi-consult only:
|
|
23
|
+
'codex-review.md',
|
|
24
|
+
'codex-xhigh-review.md',
|
|
25
|
+
'gemini-review.md',
|
|
26
|
+
'claude-review.md',
|
|
27
|
+
];
|
|
28
|
+
/**
|
|
29
|
+
* Get source and target paths for command files
|
|
30
|
+
*/
|
|
31
|
+
export function getCommandPaths() {
|
|
32
|
+
return {
|
|
33
|
+
source: join(__dirname, '..', 'commands'),
|
|
34
|
+
target: join(homedir(), '.claude', 'commands'),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Install slash commands to ~/.claude/commands/
|
|
39
|
+
*
|
|
40
|
+
* @param overrides Test-only path overrides; production callers pass nothing.
|
|
41
|
+
* @returns Result object with success status and installed commands
|
|
42
|
+
*/
|
|
43
|
+
export function installCommands(overrides) {
|
|
44
|
+
const defaults = getCommandPaths();
|
|
45
|
+
const source = overrides?.source ?? defaults.source;
|
|
46
|
+
const target = overrides?.target ?? defaults.target;
|
|
47
|
+
// Check source exists
|
|
48
|
+
if (!existsSync(source)) {
|
|
49
|
+
return { success: false, installed: [], removed: [], error: 'Commands directory not found' };
|
|
50
|
+
}
|
|
51
|
+
// Create target directory, handle errors (not a dir, permission denied)
|
|
52
|
+
try {
|
|
53
|
+
if (existsSync(target)) {
|
|
54
|
+
if (!statSync(target).isDirectory()) {
|
|
55
|
+
return { success: false, installed: [], removed: [], error: `${target} exists but is not a directory` };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
mkdirSync(target, { recursive: true });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
64
|
+
return { success: false, installed: [], removed: [], error: `Cannot create target directory: ${msg}` };
|
|
65
|
+
}
|
|
66
|
+
const files = readdirSync(source).filter(f => f.endsWith('.md'));
|
|
67
|
+
if (files.length === 0) {
|
|
68
|
+
return { success: false, installed: [], removed: [], error: 'No command files found' };
|
|
69
|
+
}
|
|
70
|
+
// Prune deprecated commands from target by renaming to .deprecated.bak
|
|
71
|
+
// (lossless — preserves any user edits the operator may have made). If the
|
|
72
|
+
// backup already exists from a previous upgrade, leave it alone.
|
|
73
|
+
const removed = [];
|
|
74
|
+
for (const oldFile of DEPRECATED_COMMANDS) {
|
|
75
|
+
const oldPath = join(target, oldFile);
|
|
76
|
+
if (existsSync(oldPath)) {
|
|
77
|
+
const backupPath = `${oldPath}.deprecated.bak`;
|
|
78
|
+
try {
|
|
79
|
+
if (!existsSync(backupPath)) {
|
|
80
|
+
renameSync(oldPath, backupPath);
|
|
81
|
+
}
|
|
82
|
+
// If the backup already exists, the original was already moved on a
|
|
83
|
+
// prior install. The file we see now must be a recreation; leave it
|
|
84
|
+
// alone — the user clearly wants it.
|
|
85
|
+
removed.push(oldFile.replace('.md', ''));
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// Best-effort removal — don't fail the install
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// Copy current files
|
|
93
|
+
const installed = [];
|
|
94
|
+
try {
|
|
95
|
+
for (const file of files) {
|
|
96
|
+
copyFileSync(join(source, file), join(target, file));
|
|
97
|
+
installed.push(file.replace('.md', ''));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
102
|
+
return { success: false, installed, removed, error: `Copy failed: ${msg}` };
|
|
103
|
+
}
|
|
104
|
+
return { success: true, installed, removed };
|
|
105
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime configuration for quorum.
|
|
3
|
+
*
|
|
4
|
+
* Config file: ~/.config/quorum/config.json
|
|
5
|
+
*
|
|
6
|
+
* Semantics:
|
|
7
|
+
* - Lazy, cached load. `getConfig()` returns the cached config or reads once.
|
|
8
|
+
* - Missing file → defaults in memory (no write). Use `initConfig()` from the
|
|
9
|
+
* server entry point to create the file with defaults on first launch.
|
|
10
|
+
* - Invalid JSON or schema violations → fall back to defaults, warn on stderr.
|
|
11
|
+
* - Partial user configs are deep-merged against defaults via Zod `.default()`.
|
|
12
|
+
* - Tool-call arguments still override config (e.g. `reasoningEffort` on a
|
|
13
|
+
* single `codex_review` call). Config only sets defaults.
|
|
14
|
+
*/
|
|
15
|
+
import { z } from 'zod';
|
|
16
|
+
export declare const CodexConfigSchema: z.ZodDefault<z.ZodObject<{
|
|
17
|
+
model: z.ZodDefault<z.ZodString>;
|
|
18
|
+
reasoningEffort: z.ZodDefault<z.ZodEnum<["high", "xhigh"]>>;
|
|
19
|
+
serviceTier: z.ZodDefault<z.ZodEnum<["default", "fast", "flex"]>>;
|
|
20
|
+
/** Consult-specific defaults — separate from review knobs because consult
|
|
21
|
+
* questions are deeper and warrant more reasoning. Users can override
|
|
22
|
+
* these to cap cost without affecting review behavior. */
|
|
23
|
+
consultReasoningEffort: z.ZodDefault<z.ZodEnum<["high", "xhigh"]>>;
|
|
24
|
+
consultServiceTier: z.ZodDefault<z.ZodEnum<["default", "fast", "flex"]>>;
|
|
25
|
+
inactivityTimeoutMs: z.ZodDefault<z.ZodObject<{
|
|
26
|
+
high: z.ZodDefault<z.ZodNumber>;
|
|
27
|
+
xhigh: z.ZodDefault<z.ZodNumber>;
|
|
28
|
+
}, "strip", z.ZodTypeAny, {
|
|
29
|
+
high: number;
|
|
30
|
+
xhigh: number;
|
|
31
|
+
}, {
|
|
32
|
+
high?: number | undefined;
|
|
33
|
+
xhigh?: number | undefined;
|
|
34
|
+
}>>;
|
|
35
|
+
maxTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
36
|
+
maxBufferSize: z.ZodDefault<z.ZodNumber>;
|
|
37
|
+
}, "strip", z.ZodTypeAny, {
|
|
38
|
+
model: string;
|
|
39
|
+
reasoningEffort: "high" | "xhigh";
|
|
40
|
+
serviceTier: "default" | "fast" | "flex";
|
|
41
|
+
consultReasoningEffort: "high" | "xhigh";
|
|
42
|
+
consultServiceTier: "default" | "fast" | "flex";
|
|
43
|
+
inactivityTimeoutMs: {
|
|
44
|
+
high: number;
|
|
45
|
+
xhigh: number;
|
|
46
|
+
};
|
|
47
|
+
maxTimeoutMs: number;
|
|
48
|
+
maxBufferSize: number;
|
|
49
|
+
}, {
|
|
50
|
+
model?: string | undefined;
|
|
51
|
+
reasoningEffort?: "high" | "xhigh" | undefined;
|
|
52
|
+
serviceTier?: "default" | "fast" | "flex" | undefined;
|
|
53
|
+
consultReasoningEffort?: "high" | "xhigh" | undefined;
|
|
54
|
+
consultServiceTier?: "default" | "fast" | "flex" | undefined;
|
|
55
|
+
inactivityTimeoutMs?: {
|
|
56
|
+
high?: number | undefined;
|
|
57
|
+
xhigh?: number | undefined;
|
|
58
|
+
} | undefined;
|
|
59
|
+
maxTimeoutMs?: number | undefined;
|
|
60
|
+
maxBufferSize?: number | undefined;
|
|
61
|
+
}>>;
|
|
62
|
+
export declare const ClaudeConfigSchema: z.ZodDefault<z.ZodObject<{
|
|
63
|
+
model: z.ZodDefault<z.ZodString>;
|
|
64
|
+
inactivityTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
65
|
+
maxTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
66
|
+
maxBufferSize: z.ZodDefault<z.ZodNumber>;
|
|
67
|
+
}, "strip", z.ZodTypeAny, {
|
|
68
|
+
model: string;
|
|
69
|
+
inactivityTimeoutMs: number;
|
|
70
|
+
maxTimeoutMs: number;
|
|
71
|
+
maxBufferSize: number;
|
|
72
|
+
}, {
|
|
73
|
+
model?: string | undefined;
|
|
74
|
+
inactivityTimeoutMs?: number | undefined;
|
|
75
|
+
maxTimeoutMs?: number | undefined;
|
|
76
|
+
maxBufferSize?: number | undefined;
|
|
77
|
+
}>>;
|
|
78
|
+
export declare const GeminiConfigSchema: z.ZodDefault<z.ZodObject<{
|
|
79
|
+
model: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
80
|
+
inactivityTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
81
|
+
maxTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
82
|
+
maxBufferSize: z.ZodDefault<z.ZodNumber>;
|
|
83
|
+
}, "strip", z.ZodTypeAny, {
|
|
84
|
+
model: string | null;
|
|
85
|
+
inactivityTimeoutMs: number;
|
|
86
|
+
maxTimeoutMs: number;
|
|
87
|
+
maxBufferSize: number;
|
|
88
|
+
}, {
|
|
89
|
+
model?: string | null | undefined;
|
|
90
|
+
inactivityTimeoutMs?: number | undefined;
|
|
91
|
+
maxTimeoutMs?: number | undefined;
|
|
92
|
+
maxBufferSize?: number | undefined;
|
|
93
|
+
}>>;
|
|
94
|
+
export declare const ConfigSchema: z.ZodDefault<z.ZodObject<{
|
|
95
|
+
codex: z.ZodDefault<z.ZodObject<{
|
|
96
|
+
model: z.ZodDefault<z.ZodString>;
|
|
97
|
+
reasoningEffort: z.ZodDefault<z.ZodEnum<["high", "xhigh"]>>;
|
|
98
|
+
serviceTier: z.ZodDefault<z.ZodEnum<["default", "fast", "flex"]>>;
|
|
99
|
+
/** Consult-specific defaults — separate from review knobs because consult
|
|
100
|
+
* questions are deeper and warrant more reasoning. Users can override
|
|
101
|
+
* these to cap cost without affecting review behavior. */
|
|
102
|
+
consultReasoningEffort: z.ZodDefault<z.ZodEnum<["high", "xhigh"]>>;
|
|
103
|
+
consultServiceTier: z.ZodDefault<z.ZodEnum<["default", "fast", "flex"]>>;
|
|
104
|
+
inactivityTimeoutMs: z.ZodDefault<z.ZodObject<{
|
|
105
|
+
high: z.ZodDefault<z.ZodNumber>;
|
|
106
|
+
xhigh: z.ZodDefault<z.ZodNumber>;
|
|
107
|
+
}, "strip", z.ZodTypeAny, {
|
|
108
|
+
high: number;
|
|
109
|
+
xhigh: number;
|
|
110
|
+
}, {
|
|
111
|
+
high?: number | undefined;
|
|
112
|
+
xhigh?: number | undefined;
|
|
113
|
+
}>>;
|
|
114
|
+
maxTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
115
|
+
maxBufferSize: z.ZodDefault<z.ZodNumber>;
|
|
116
|
+
}, "strip", z.ZodTypeAny, {
|
|
117
|
+
model: string;
|
|
118
|
+
reasoningEffort: "high" | "xhigh";
|
|
119
|
+
serviceTier: "default" | "fast" | "flex";
|
|
120
|
+
consultReasoningEffort: "high" | "xhigh";
|
|
121
|
+
consultServiceTier: "default" | "fast" | "flex";
|
|
122
|
+
inactivityTimeoutMs: {
|
|
123
|
+
high: number;
|
|
124
|
+
xhigh: number;
|
|
125
|
+
};
|
|
126
|
+
maxTimeoutMs: number;
|
|
127
|
+
maxBufferSize: number;
|
|
128
|
+
}, {
|
|
129
|
+
model?: string | undefined;
|
|
130
|
+
reasoningEffort?: "high" | "xhigh" | undefined;
|
|
131
|
+
serviceTier?: "default" | "fast" | "flex" | undefined;
|
|
132
|
+
consultReasoningEffort?: "high" | "xhigh" | undefined;
|
|
133
|
+
consultServiceTier?: "default" | "fast" | "flex" | undefined;
|
|
134
|
+
inactivityTimeoutMs?: {
|
|
135
|
+
high?: number | undefined;
|
|
136
|
+
xhigh?: number | undefined;
|
|
137
|
+
} | undefined;
|
|
138
|
+
maxTimeoutMs?: number | undefined;
|
|
139
|
+
maxBufferSize?: number | undefined;
|
|
140
|
+
}>>;
|
|
141
|
+
claude: z.ZodDefault<z.ZodObject<{
|
|
142
|
+
model: z.ZodDefault<z.ZodString>;
|
|
143
|
+
inactivityTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
144
|
+
maxTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
145
|
+
maxBufferSize: z.ZodDefault<z.ZodNumber>;
|
|
146
|
+
}, "strip", z.ZodTypeAny, {
|
|
147
|
+
model: string;
|
|
148
|
+
inactivityTimeoutMs: number;
|
|
149
|
+
maxTimeoutMs: number;
|
|
150
|
+
maxBufferSize: number;
|
|
151
|
+
}, {
|
|
152
|
+
model?: string | undefined;
|
|
153
|
+
inactivityTimeoutMs?: number | undefined;
|
|
154
|
+
maxTimeoutMs?: number | undefined;
|
|
155
|
+
maxBufferSize?: number | undefined;
|
|
156
|
+
}>>;
|
|
157
|
+
gemini: z.ZodDefault<z.ZodObject<{
|
|
158
|
+
model: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
159
|
+
inactivityTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
160
|
+
maxTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
161
|
+
maxBufferSize: z.ZodDefault<z.ZodNumber>;
|
|
162
|
+
}, "strip", z.ZodTypeAny, {
|
|
163
|
+
model: string | null;
|
|
164
|
+
inactivityTimeoutMs: number;
|
|
165
|
+
maxTimeoutMs: number;
|
|
166
|
+
maxBufferSize: number;
|
|
167
|
+
}, {
|
|
168
|
+
model?: string | null | undefined;
|
|
169
|
+
inactivityTimeoutMs?: number | undefined;
|
|
170
|
+
maxTimeoutMs?: number | undefined;
|
|
171
|
+
maxBufferSize?: number | undefined;
|
|
172
|
+
}>>;
|
|
173
|
+
}, "strip", z.ZodTypeAny, {
|
|
174
|
+
codex: {
|
|
175
|
+
model: string;
|
|
176
|
+
reasoningEffort: "high" | "xhigh";
|
|
177
|
+
serviceTier: "default" | "fast" | "flex";
|
|
178
|
+
consultReasoningEffort: "high" | "xhigh";
|
|
179
|
+
consultServiceTier: "default" | "fast" | "flex";
|
|
180
|
+
inactivityTimeoutMs: {
|
|
181
|
+
high: number;
|
|
182
|
+
xhigh: number;
|
|
183
|
+
};
|
|
184
|
+
maxTimeoutMs: number;
|
|
185
|
+
maxBufferSize: number;
|
|
186
|
+
};
|
|
187
|
+
claude: {
|
|
188
|
+
model: string;
|
|
189
|
+
inactivityTimeoutMs: number;
|
|
190
|
+
maxTimeoutMs: number;
|
|
191
|
+
maxBufferSize: number;
|
|
192
|
+
};
|
|
193
|
+
gemini: {
|
|
194
|
+
model: string | null;
|
|
195
|
+
inactivityTimeoutMs: number;
|
|
196
|
+
maxTimeoutMs: number;
|
|
197
|
+
maxBufferSize: number;
|
|
198
|
+
};
|
|
199
|
+
}, {
|
|
200
|
+
codex?: {
|
|
201
|
+
model?: string | undefined;
|
|
202
|
+
reasoningEffort?: "high" | "xhigh" | undefined;
|
|
203
|
+
serviceTier?: "default" | "fast" | "flex" | undefined;
|
|
204
|
+
consultReasoningEffort?: "high" | "xhigh" | undefined;
|
|
205
|
+
consultServiceTier?: "default" | "fast" | "flex" | undefined;
|
|
206
|
+
inactivityTimeoutMs?: {
|
|
207
|
+
high?: number | undefined;
|
|
208
|
+
xhigh?: number | undefined;
|
|
209
|
+
} | undefined;
|
|
210
|
+
maxTimeoutMs?: number | undefined;
|
|
211
|
+
maxBufferSize?: number | undefined;
|
|
212
|
+
} | undefined;
|
|
213
|
+
claude?: {
|
|
214
|
+
model?: string | undefined;
|
|
215
|
+
inactivityTimeoutMs?: number | undefined;
|
|
216
|
+
maxTimeoutMs?: number | undefined;
|
|
217
|
+
maxBufferSize?: number | undefined;
|
|
218
|
+
} | undefined;
|
|
219
|
+
gemini?: {
|
|
220
|
+
model?: string | null | undefined;
|
|
221
|
+
inactivityTimeoutMs?: number | undefined;
|
|
222
|
+
maxTimeoutMs?: number | undefined;
|
|
223
|
+
maxBufferSize?: number | undefined;
|
|
224
|
+
} | undefined;
|
|
225
|
+
}>>;
|
|
226
|
+
export type Config = z.infer<typeof ConfigSchema>;
|
|
227
|
+
export type CodexConfig = z.infer<typeof CodexConfigSchema>;
|
|
228
|
+
export type ClaudeConfig = z.infer<typeof ClaudeConfigSchema>;
|
|
229
|
+
export type GeminiConfig = z.infer<typeof GeminiConfigSchema>;
|
|
230
|
+
export declare const DEFAULT_CONFIG: Config;
|
|
231
|
+
export declare function getConfigPath(): string;
|
|
232
|
+
export declare function getConfig(): Config;
|
|
233
|
+
/**
|
|
234
|
+
* Create the config file with defaults if it does not exist.
|
|
235
|
+
* Uses the exclusive `wx` flag for atomic creation — safe against TOCTOU races
|
|
236
|
+
* when multiple server instances start concurrently.
|
|
237
|
+
* Refreshes the cached config so subsequent `getConfig()` calls see disk state.
|
|
238
|
+
*/
|
|
239
|
+
export declare function initConfig(): {
|
|
240
|
+
path: string;
|
|
241
|
+
created: boolean;
|
|
242
|
+
};
|
|
243
|
+
/** Test-only hook. Redirects the config path and clears the cache. */
|
|
244
|
+
export declare function setConfigPathForTesting(path: string | null): void;
|