@simonren/quorum 0.7.0 → 0.8.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.
- package/README.md +3 -3
- package/dist/adapters/claude.js +16 -13
- package/dist/adapters/codex.js +31 -13
- package/dist/adapters/gemini.d.ts +12 -4
- package/dist/adapters/gemini.js +31 -43
- package/dist/config.d.ts +0 -8
- package/dist/config.js +3 -1
- package/dist/decoders/codex.d.ts +11 -1
- package/dist/decoders/codex.js +26 -4
- package/dist/decoders/index.d.ts +0 -2
- package/dist/decoders/index.js +0 -1
- package/dist/errors.js +17 -11
- package/dist/index.js +0 -0
- package/dist/tools/consult.js +1 -1
- package/dist/tools/feedback.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -42,9 +42,9 @@ Install at least one AI CLI:
|
|
|
42
42
|
npm install -g @openai/codex-cli
|
|
43
43
|
codex login
|
|
44
44
|
|
|
45
|
-
# Google Gemini CLI
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
# Google Gemini — via Antigravity CLI (`agy`). `gemini-cli` is sunset on 2026-06-18.
|
|
46
|
+
curl -fsSL https://antigravity.google/cli/install.sh | bash
|
|
47
|
+
agy # follow the Google OAuth flow
|
|
48
48
|
```
|
|
49
49
|
|
|
50
50
|
## Usage
|
package/dist/adapters/claude.js
CHANGED
|
@@ -124,27 +124,30 @@ export class ClaudeAdapter {
|
|
|
124
124
|
const result = await executor.run();
|
|
125
125
|
const elapsed = Math.round((Date.now() - cliStartTime) / 1000);
|
|
126
126
|
console.error(`[claude] ✓ complete (${elapsed}s)`);
|
|
127
|
-
//
|
|
127
|
+
// Prefer a completed response over a captured error: a non-fatal error event
|
|
128
|
+
// that precedes a successful agent_message must not discard the result (same
|
|
129
|
+
// class of bug fixed in the codex adapter). Only treat a decoder error as
|
|
130
|
+
// fatal when no final response was produced.
|
|
131
|
+
const finalResponse = decoder.getFinalResponse();
|
|
132
|
+
if (finalResponse) {
|
|
133
|
+
return {
|
|
134
|
+
stdout: finalResponse,
|
|
135
|
+
stderr: result.stderr,
|
|
136
|
+
exitCode: result.exitCode,
|
|
137
|
+
truncated: result.truncated,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
128
140
|
const decoderError = decoder.getError();
|
|
129
141
|
if (decoderError) {
|
|
130
142
|
const combined = result.stderr ? `${decoderError}\n\nCLI stderr: ${result.stderr}` : decoderError;
|
|
131
143
|
return { stdout: '', stderr: combined, exitCode: 1, truncated: false };
|
|
132
144
|
}
|
|
133
|
-
|
|
134
|
-
if (!finalResponse && decoder.hasNoOutput()) {
|
|
145
|
+
if (decoder.hasNoOutput()) {
|
|
135
146
|
const combined = result.stderr ? `No output from Claude\n\nCLI stderr: ${result.stderr}` : 'No output from Claude';
|
|
136
147
|
return { stdout: '', stderr: combined, exitCode: 1, truncated: false };
|
|
137
148
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
return { stdout: '', stderr: combined, exitCode: 1, truncated: false };
|
|
141
|
-
}
|
|
142
|
-
return {
|
|
143
|
-
stdout: finalResponse,
|
|
144
|
-
stderr: result.stderr,
|
|
145
|
-
exitCode: result.exitCode,
|
|
146
|
-
truncated: result.truncated,
|
|
147
|
-
};
|
|
149
|
+
const combined = result.stderr ? `No result event from Claude\n\nCLI stderr: ${result.stderr}` : 'No result event from Claude';
|
|
150
|
+
return { stdout: '', stderr: combined, exitCode: 1, truncated: false };
|
|
148
151
|
}
|
|
149
152
|
handleException(error, startTime) {
|
|
150
153
|
const err = error;
|
package/dist/adapters/codex.js
CHANGED
|
@@ -125,27 +125,45 @@ export class CodexAdapter {
|
|
|
125
125
|
const result = await executor.run();
|
|
126
126
|
const elapsed = Math.round((Date.now() - cliStartTime) / 1000);
|
|
127
127
|
console.error(`[codex] ✓ complete (${elapsed}s)`);
|
|
128
|
-
//
|
|
128
|
+
// A genuine fatal error (a top-level `error` event or `turn.failed`) is
|
|
129
|
+
// TERMINAL and wins even when a — possibly intermediate/stale — agent_message
|
|
130
|
+
// exists. `--full-auto` runs a multi-turn loop, so a turn can emit a message
|
|
131
|
+
// and a later turn can fail; without this check, that failure would be
|
|
132
|
+
// reported as a successful review with stale content.
|
|
133
|
+
const fatalError = decoder.getFatalError();
|
|
134
|
+
if (fatalError) {
|
|
135
|
+
const combined = result.stderr ? `${fatalError}\n\nCLI stderr: ${result.stderr}` : fatalError;
|
|
136
|
+
return { stdout: '', stderr: combined, exitCode: 1, truncated: false };
|
|
137
|
+
}
|
|
138
|
+
// No fatal error: a completed response wins even if a NON-fatal error-item
|
|
139
|
+
// appeared earlier. Codex emits its "Skill descriptions were shortened to fit
|
|
140
|
+
// the 2% skills context budget" notice as an item.completed/type:error event —
|
|
141
|
+
// purely informational ("Codex can still see every skill"); the turn still
|
|
142
|
+
// completes and produces an agent_message. Use `!== null` (not truthiness) so
|
|
143
|
+
// an empty-string response is preserved for the caller's empty-response
|
|
144
|
+
// handling rather than misclassified as "no result event".
|
|
145
|
+
// (Checking getError() first discarded otherwise-successful reviews.)
|
|
146
|
+
const finalResponse = decoder.getFinalResponse();
|
|
147
|
+
if (finalResponse !== null) {
|
|
148
|
+
return {
|
|
149
|
+
stdout: finalResponse,
|
|
150
|
+
stderr: result.stderr,
|
|
151
|
+
exitCode: result.exitCode,
|
|
152
|
+
truncated: result.truncated,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
// No response: surface a non-fatal item-level error if one was captured.
|
|
129
156
|
const decoderError = decoder.getError();
|
|
130
157
|
if (decoderError) {
|
|
131
158
|
const combined = result.stderr ? `${decoderError}\n\nCLI stderr: ${result.stderr}` : decoderError;
|
|
132
159
|
return { stdout: '', stderr: combined, exitCode: 1, truncated: false };
|
|
133
160
|
}
|
|
134
|
-
|
|
135
|
-
if (!finalResponse && decoder.hasNoOutput()) {
|
|
161
|
+
if (decoder.hasNoOutput()) {
|
|
136
162
|
const combined = result.stderr ? `No output from Codex\n\nCLI stderr: ${result.stderr}` : 'No output from Codex';
|
|
137
163
|
return { stdout: '', stderr: combined, exitCode: 1, truncated: false };
|
|
138
164
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
return { stdout: '', stderr: combined, exitCode: 1, truncated: false };
|
|
142
|
-
}
|
|
143
|
-
return {
|
|
144
|
-
stdout: finalResponse,
|
|
145
|
-
stderr: result.stderr,
|
|
146
|
-
exitCode: result.exitCode,
|
|
147
|
-
truncated: result.truncated,
|
|
148
|
-
};
|
|
165
|
+
const combined = result.stderr ? `No result event from Codex\n\nCLI stderr: ${result.stderr}` : 'No result event from Codex';
|
|
166
|
+
return { stdout: '', stderr: combined, exitCode: 1, truncated: false };
|
|
149
167
|
}
|
|
150
168
|
handleException(error, startTime) {
|
|
151
169
|
const err = error;
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Gemini CLI
|
|
2
|
+
* Gemini Adapter (via Antigravity CLI `agy`)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Google replaced `gemini-cli` with the Antigravity CLI (`agy`) at I/O 2026.
|
|
5
|
+
* The free-tier `gemini` binary stops serving requests on 2026-06-18, so this
|
|
6
|
+
* adapter now spawns `agy --print` with the prompt on stdin. The model is still
|
|
7
|
+
* Gemini under the hood — only the CLI brand changed — so the adapter id and
|
|
8
|
+
* config key remain `gemini`.
|
|
9
|
+
*
|
|
10
|
+
* Differences from the old gemini-cli adapter:
|
|
11
|
+
* - No `--output-format stream-json` → no live progress events
|
|
12
|
+
* - No `--model` flag → model selection is done in agy's settings file
|
|
13
|
+
* - `--include-directories` → `--add-dir`
|
|
14
|
+
* - `--approval-mode plan` → folded into `--sandbox`
|
|
7
15
|
*/
|
|
8
16
|
import { ReviewerAdapter, ReviewerCapabilities, ReviewRequest, ReviewResult, ConsultRequest, ConsultResult } from './base.js';
|
|
9
17
|
export declare class GeminiAdapter implements ReviewerAdapter {
|
package/dist/adapters/gemini.js
CHANGED
|
@@ -1,27 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Gemini CLI
|
|
2
|
+
* Gemini Adapter (via Antigravity CLI `agy`)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Google replaced `gemini-cli` with the Antigravity CLI (`agy`) at I/O 2026.
|
|
5
|
+
* The free-tier `gemini` binary stops serving requests on 2026-06-18, so this
|
|
6
|
+
* adapter now spawns `agy --print` with the prompt on stdin. The model is still
|
|
7
|
+
* Gemini under the hood — only the CLI brand changed — so the adapter id and
|
|
8
|
+
* config key remain `gemini`.
|
|
9
|
+
*
|
|
10
|
+
* Differences from the old gemini-cli adapter:
|
|
11
|
+
* - No `--output-format stream-json` → no live progress events
|
|
12
|
+
* - No `--model` flag → model selection is done in agy's settings file
|
|
13
|
+
* - `--include-directories` → `--add-dir`
|
|
14
|
+
* - `--approval-mode plan` → folded into `--sandbox`
|
|
7
15
|
*/
|
|
8
16
|
import { spawn } from 'child_process';
|
|
9
17
|
import { existsSync } from 'fs';
|
|
10
18
|
import { registerAdapter, } from './base.js';
|
|
11
19
|
import { CliExecutor } from '../executor.js';
|
|
12
|
-
import { GeminiEventDecoder } from '../decoders/index.js';
|
|
13
20
|
import { buildSimpleHandoff, buildHandoffPrompt, buildAdversarialHandoffPrompt, selectRole, } from '../handoff.js';
|
|
14
21
|
import { buildConsultPrompt } from '../consult-prompt.js';
|
|
15
22
|
import { getConfig } from '../config.js';
|
|
16
|
-
|
|
17
|
-
// GEMINI ADAPTER
|
|
18
|
-
// =============================================================================
|
|
23
|
+
const AGY_INSTALL_CMD = 'curl -fsSL https://antigravity.google/cli/install.sh | bash';
|
|
19
24
|
export class GeminiAdapter {
|
|
20
25
|
id = 'gemini';
|
|
21
26
|
getCapabilities() {
|
|
22
27
|
return {
|
|
23
28
|
name: 'Gemini',
|
|
24
|
-
description: 'Google Gemini - excels at architecture analysis, design patterns, and large codebase understanding',
|
|
29
|
+
description: 'Google Gemini (via Antigravity CLI) - excels at architecture analysis, design patterns, and large codebase understanding',
|
|
25
30
|
strengths: ['architecture', 'maintainability', 'scalability', 'documentation'],
|
|
26
31
|
weaknesses: ['security'],
|
|
27
32
|
hasFilesystemAccess: true,
|
|
@@ -38,7 +43,7 @@ export class GeminiAdapter {
|
|
|
38
43
|
clearTimeout(timer);
|
|
39
44
|
resolve(result);
|
|
40
45
|
} };
|
|
41
|
-
const proc = spawn('
|
|
46
|
+
const proc = spawn('agy', ['--version'], {
|
|
42
47
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
43
48
|
});
|
|
44
49
|
proc.on('close', (code) => done(code === 0));
|
|
@@ -69,7 +74,7 @@ export class GeminiAdapter {
|
|
|
69
74
|
if (!result.stdout.trim()) {
|
|
70
75
|
return {
|
|
71
76
|
success: false,
|
|
72
|
-
error: { type: 'cli_error', message: '
|
|
77
|
+
error: { type: 'cli_error', message: 'agy returned empty response' },
|
|
73
78
|
suggestion: 'Try again or use /multi-review instead',
|
|
74
79
|
executionTimeMs: Date.now() - startTime,
|
|
75
80
|
};
|
|
@@ -82,45 +87,29 @@ export class GeminiAdapter {
|
|
|
82
87
|
}
|
|
83
88
|
async runCli(prompt, workingDir) {
|
|
84
89
|
const cfg = getConfig().gemini;
|
|
90
|
+
// agy requires the prompt as the positional after --print; passing only via
|
|
91
|
+
// stdin makes it print --help and exit 0. Sandbox keeps terminal-restricted
|
|
92
|
+
// execution like the old gemini-cli's `--approval-mode plan`.
|
|
85
93
|
const args = [
|
|
86
94
|
'--sandbox',
|
|
87
|
-
'--
|
|
88
|
-
'--
|
|
89
|
-
'--include-directories', workingDir,
|
|
90
|
-
'-p', '',
|
|
95
|
+
'--add-dir', workingDir,
|
|
96
|
+
'--print', prompt,
|
|
91
97
|
];
|
|
92
|
-
if (cfg.model) {
|
|
93
|
-
args.push('--model', cfg.model);
|
|
94
|
-
}
|
|
95
|
-
const decoder = new GeminiEventDecoder();
|
|
96
98
|
const cliStartTime = Date.now();
|
|
97
|
-
console.error('[gemini] Running...');
|
|
98
|
-
decoder.onProgress = (eventType, detail) => {
|
|
99
|
-
const elapsed = Math.round((Date.now() - cliStartTime) / 1000);
|
|
100
|
-
const detailStr = detail ? ` — ${detail}` : '';
|
|
101
|
-
console.error(`[gemini] ${eventType}${detailStr} (${elapsed}s)`);
|
|
102
|
-
};
|
|
99
|
+
console.error('[gemini] Running agy...');
|
|
103
100
|
const executor = new CliExecutor({
|
|
104
|
-
command: '
|
|
101
|
+
command: 'agy',
|
|
105
102
|
args,
|
|
106
103
|
cwd: workingDir,
|
|
107
|
-
stdin: prompt,
|
|
108
104
|
inactivityTimeoutMs: cfg.inactivityTimeoutMs,
|
|
109
105
|
maxTimeoutMs: cfg.maxTimeoutMs,
|
|
110
106
|
maxBufferSize: cfg.maxBufferSize,
|
|
111
|
-
onLine: (line) => {
|
|
112
|
-
decoder.processLine(line);
|
|
113
|
-
},
|
|
114
107
|
});
|
|
115
108
|
const result = await executor.run();
|
|
116
109
|
const elapsed = Math.round((Date.now() - cliStartTime) / 1000);
|
|
117
110
|
console.error(`[gemini] ✓ complete (${elapsed}s)`);
|
|
118
|
-
const finalResponse = decoder.getFinalResponse();
|
|
119
|
-
if (!finalResponse && result.exitCode === 0) {
|
|
120
|
-
return { stdout: '', stderr: 'Gemini produced no output — review may have failed silently', exitCode: 1, truncated: false };
|
|
121
|
-
}
|
|
122
111
|
return {
|
|
123
|
-
stdout:
|
|
112
|
+
stdout: result.rawStdout,
|
|
124
113
|
stderr: result.stderr,
|
|
125
114
|
exitCode: result.exitCode,
|
|
126
115
|
truncated: result.truncated,
|
|
@@ -129,11 +118,11 @@ export class GeminiAdapter {
|
|
|
129
118
|
handleException(error, startTime) {
|
|
130
119
|
const err = error;
|
|
131
120
|
if (err.code === 'ENOENT') {
|
|
132
|
-
return { success: false, error: { type: 'cli_not_found', message: '
|
|
133
|
-
suggestion:
|
|
121
|
+
return { success: false, error: { type: 'cli_not_found', message: 'agy CLI not found' },
|
|
122
|
+
suggestion: `Install with: ${AGY_INSTALL_CMD}`, executionTimeMs: Date.now() - startTime };
|
|
134
123
|
}
|
|
135
124
|
if (err.message === 'TIMEOUT') {
|
|
136
|
-
return { success: false, error: { type: 'timeout', message: '
|
|
125
|
+
return { success: false, error: { type: 'timeout', message: 'agy timed out — no output received' },
|
|
137
126
|
suggestion: 'Try a smaller scope or use /multi-review', executionTimeMs: Date.now() - startTime };
|
|
138
127
|
}
|
|
139
128
|
if (err.message === 'MAX_TIMEOUT') {
|
|
@@ -147,7 +136,7 @@ export class GeminiAdapter {
|
|
|
147
136
|
if (lower.includes('rate limit') || lower.includes('quota')) {
|
|
148
137
|
return { type: 'rate_limit', message: `Rate limit or quota exceeded: ${stderr.slice(0, 500)}` };
|
|
149
138
|
}
|
|
150
|
-
if (lower.includes('unauthorized') || lower.includes('authentication') || lower.includes('
|
|
139
|
+
if (lower.includes('unauthorized') || lower.includes('authentication') || lower.includes('oauth') || stderr.includes('401') || stderr.includes('403')) {
|
|
151
140
|
return { type: 'auth_error', message: `Authentication failed: ${stderr.slice(0, 500)}`, details: { stderr } };
|
|
152
141
|
}
|
|
153
142
|
return { type: 'cli_error', message: stderr.slice(0, 500) || 'Unknown error' };
|
|
@@ -155,8 +144,8 @@ export class GeminiAdapter {
|
|
|
155
144
|
getSuggestion(error) {
|
|
156
145
|
switch (error.type) {
|
|
157
146
|
case 'rate_limit': return 'Wait and retry, or use /multi-review instead';
|
|
158
|
-
case 'auth_error': return 'Run `
|
|
159
|
-
case 'cli_not_found': return
|
|
147
|
+
case 'auth_error': return 'Run `agy` and complete the Google OAuth sign-in';
|
|
148
|
+
case 'cli_not_found': return `Install with: ${AGY_INSTALL_CMD}`;
|
|
160
149
|
default: return 'Check the error message and try again';
|
|
161
150
|
}
|
|
162
151
|
}
|
|
@@ -180,7 +169,7 @@ export class GeminiAdapter {
|
|
|
180
169
|
if (!result.stdout.trim()) {
|
|
181
170
|
return {
|
|
182
171
|
success: false,
|
|
183
|
-
error: { type: 'cli_error', message: '
|
|
172
|
+
error: { type: 'cli_error', message: 'agy returned empty response' },
|
|
184
173
|
suggestion: 'Try again or use /multi-review instead',
|
|
185
174
|
executionTimeMs: Date.now() - startTime,
|
|
186
175
|
};
|
|
@@ -192,6 +181,5 @@ export class GeminiAdapter {
|
|
|
192
181
|
}
|
|
193
182
|
}
|
|
194
183
|
}
|
|
195
|
-
// Register the adapter
|
|
196
184
|
registerAdapter(new GeminiAdapter());
|
|
197
185
|
export const geminiAdapter = new GeminiAdapter();
|
package/dist/config.d.ts
CHANGED
|
@@ -76,17 +76,14 @@ export declare const ClaudeConfigSchema: z.ZodDefault<z.ZodObject<{
|
|
|
76
76
|
maxBufferSize?: number | undefined;
|
|
77
77
|
}>>;
|
|
78
78
|
export declare const GeminiConfigSchema: z.ZodDefault<z.ZodObject<{
|
|
79
|
-
model: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
80
79
|
inactivityTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
81
80
|
maxTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
82
81
|
maxBufferSize: z.ZodDefault<z.ZodNumber>;
|
|
83
82
|
}, "strip", z.ZodTypeAny, {
|
|
84
|
-
model: string | null;
|
|
85
83
|
inactivityTimeoutMs: number;
|
|
86
84
|
maxTimeoutMs: number;
|
|
87
85
|
maxBufferSize: number;
|
|
88
86
|
}, {
|
|
89
|
-
model?: string | null | undefined;
|
|
90
87
|
inactivityTimeoutMs?: number | undefined;
|
|
91
88
|
maxTimeoutMs?: number | undefined;
|
|
92
89
|
maxBufferSize?: number | undefined;
|
|
@@ -155,17 +152,14 @@ export declare const ConfigSchema: z.ZodDefault<z.ZodObject<{
|
|
|
155
152
|
maxBufferSize?: number | undefined;
|
|
156
153
|
}>>;
|
|
157
154
|
gemini: z.ZodDefault<z.ZodObject<{
|
|
158
|
-
model: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
159
155
|
inactivityTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
160
156
|
maxTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
161
157
|
maxBufferSize: z.ZodDefault<z.ZodNumber>;
|
|
162
158
|
}, "strip", z.ZodTypeAny, {
|
|
163
|
-
model: string | null;
|
|
164
159
|
inactivityTimeoutMs: number;
|
|
165
160
|
maxTimeoutMs: number;
|
|
166
161
|
maxBufferSize: number;
|
|
167
162
|
}, {
|
|
168
|
-
model?: string | null | undefined;
|
|
169
163
|
inactivityTimeoutMs?: number | undefined;
|
|
170
164
|
maxTimeoutMs?: number | undefined;
|
|
171
165
|
maxBufferSize?: number | undefined;
|
|
@@ -191,7 +185,6 @@ export declare const ConfigSchema: z.ZodDefault<z.ZodObject<{
|
|
|
191
185
|
maxBufferSize: number;
|
|
192
186
|
};
|
|
193
187
|
gemini: {
|
|
194
|
-
model: string | null;
|
|
195
188
|
inactivityTimeoutMs: number;
|
|
196
189
|
maxTimeoutMs: number;
|
|
197
190
|
maxBufferSize: number;
|
|
@@ -217,7 +210,6 @@ export declare const ConfigSchema: z.ZodDefault<z.ZodObject<{
|
|
|
217
210
|
maxBufferSize?: number | undefined;
|
|
218
211
|
} | undefined;
|
|
219
212
|
gemini?: {
|
|
220
|
-
model?: string | null | undefined;
|
|
221
213
|
inactivityTimeoutMs?: number | undefined;
|
|
222
214
|
maxTimeoutMs?: number | undefined;
|
|
223
215
|
maxBufferSize?: number | undefined;
|
package/dist/config.js
CHANGED
|
@@ -47,9 +47,11 @@ export const ClaudeConfigSchema = z
|
|
|
47
47
|
maxBufferSize: z.number().int().positive().default(1_048_576),
|
|
48
48
|
})
|
|
49
49
|
.default({});
|
|
50
|
+
// agy (Antigravity CLI) does not expose `--model`; model selection lives in
|
|
51
|
+
// agy's own settings file. We keep the timeout/buffer knobs because CliExecutor
|
|
52
|
+
// enforces them regardless of the underlying CLI.
|
|
50
53
|
export const GeminiConfigSchema = z
|
|
51
54
|
.object({
|
|
52
|
-
model: z.string().nullable().default('gemini-3.1-pro-preview'),
|
|
53
55
|
inactivityTimeoutMs: z.number().int().positive().default(300_000),
|
|
54
56
|
maxTimeoutMs: z.number().int().positive().default(3_600_000),
|
|
55
57
|
maxBufferSize: z.number().int().positive().default(1_048_576),
|
package/dist/decoders/codex.d.ts
CHANGED
|
@@ -43,6 +43,7 @@ export declare class CodexEventDecoder {
|
|
|
43
43
|
private _finalResponse;
|
|
44
44
|
private _usage;
|
|
45
45
|
private _error;
|
|
46
|
+
private _fatalError;
|
|
46
47
|
private _eventCount;
|
|
47
48
|
/**
|
|
48
49
|
* Parse a single JSONL line. Silently skips malformed or empty input.
|
|
@@ -59,9 +60,18 @@ export declare class CodexEventDecoder {
|
|
|
59
60
|
*/
|
|
60
61
|
getUsage(): CodexEvent['usage'] | null;
|
|
61
62
|
/**
|
|
62
|
-
* Returns the error message from
|
|
63
|
+
* Returns the error message from any source — a top-level `error`, a
|
|
64
|
+
* `turn.failed`, or a non-fatal item-level error notice — or `null`.
|
|
65
|
+
* Superset of `getFatalError()`.
|
|
63
66
|
*/
|
|
64
67
|
getError(): string | null;
|
|
68
|
+
/**
|
|
69
|
+
* Returns the message from a TERMINAL fatal error only — a top-level `error`
|
|
70
|
+
* event or `turn.failed` — or `null`. The benign item-level error notice
|
|
71
|
+
* (e.g. the "skills shortened" budget message) is NOT fatal and is excluded,
|
|
72
|
+
* so a successful turn can win over it without masking a genuine failure.
|
|
73
|
+
*/
|
|
74
|
+
getFatalError(): string | null;
|
|
65
75
|
/**
|
|
66
76
|
* Returns true if events were received but no agent_message was produced.
|
|
67
77
|
* Combined with a fast exit, this indicates rate limiting or instant rejection.
|
package/dist/decoders/codex.js
CHANGED
|
@@ -25,8 +25,14 @@ export class CodexEventDecoder {
|
|
|
25
25
|
_finalResponse = null;
|
|
26
26
|
// Token usage from the most recently seen turn.completed
|
|
27
27
|
_usage = null;
|
|
28
|
-
// Error message from error
|
|
28
|
+
// Error message from ANY source (top-level error, turn.failed, or a non-fatal
|
|
29
|
+
// item-level error notice). Superset of _fatalError.
|
|
29
30
|
_error = null;
|
|
31
|
+
// Error message from a TERMINAL fatal source ONLY: a top-level `error` event
|
|
32
|
+
// or `turn.failed`. Kept separate from the benign item-level error notice
|
|
33
|
+
// (e.g. Codex's "skills shortened" budget message) so the adapter can let a
|
|
34
|
+
// completed response override the benign notice WITHOUT masking a real failure.
|
|
35
|
+
_fatalError = null;
|
|
30
36
|
// Count of events received (0 = possible rate limit / instant rejection)
|
|
31
37
|
_eventCount = 0;
|
|
32
38
|
// =============================================================================
|
|
@@ -68,11 +74,22 @@ export class CodexEventDecoder {
|
|
|
68
74
|
return this._usage;
|
|
69
75
|
}
|
|
70
76
|
/**
|
|
71
|
-
* Returns the error message from
|
|
77
|
+
* Returns the error message from any source — a top-level `error`, a
|
|
78
|
+
* `turn.failed`, or a non-fatal item-level error notice — or `null`.
|
|
79
|
+
* Superset of `getFatalError()`.
|
|
72
80
|
*/
|
|
73
81
|
getError() {
|
|
74
82
|
return this._error;
|
|
75
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* Returns the message from a TERMINAL fatal error only — a top-level `error`
|
|
86
|
+
* event or `turn.failed` — or `null`. The benign item-level error notice
|
|
87
|
+
* (e.g. the "skills shortened" budget message) is NOT fatal and is excluded,
|
|
88
|
+
* so a successful turn can win over it without masking a genuine failure.
|
|
89
|
+
*/
|
|
90
|
+
getFatalError() {
|
|
91
|
+
return this._fatalError;
|
|
92
|
+
}
|
|
76
93
|
/**
|
|
77
94
|
* Returns true if events were received but no agent_message was produced.
|
|
78
95
|
* Combined with a fast exit, this indicates rate limiting or instant rejection.
|
|
@@ -95,14 +112,19 @@ export class CodexEventDecoder {
|
|
|
95
112
|
if (event.type === 'turn.completed' && event.usage != null) {
|
|
96
113
|
this._usage = event.usage;
|
|
97
114
|
}
|
|
98
|
-
// Capture errors
|
|
115
|
+
// Capture errors. A top-level `error` event and `turn.failed` are TERMINAL
|
|
116
|
+
// failures — record them as fatal (and in the general _error superset).
|
|
99
117
|
if (event.type === 'error') {
|
|
100
118
|
this._error = event.message || 'Unknown error from Codex';
|
|
119
|
+
this._fatalError = this._error;
|
|
101
120
|
}
|
|
102
121
|
if (event.type === 'turn.failed') {
|
|
103
122
|
this._error = event.error?.message || 'Turn failed';
|
|
123
|
+
this._fatalError = this._error;
|
|
104
124
|
}
|
|
105
|
-
//
|
|
125
|
+
// An item.completed/type:error is a NON-fatal notice (e.g. the "skills
|
|
126
|
+
// shortened" budget message, or a per-item model error). Record it only in
|
|
127
|
+
// the general _error bucket — never as fatal — so it cannot mask a response.
|
|
106
128
|
if (event.type === 'item.completed' && event.item?.type === 'error') {
|
|
107
129
|
this._error = event.item.message || event.item.text || 'Model error';
|
|
108
130
|
}
|
package/dist/decoders/index.d.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
export { CodexEventDecoder } from './codex.js';
|
|
2
2
|
export type { CodexEvent } from './codex.js';
|
|
3
|
-
export { GeminiEventDecoder } from './gemini.js';
|
|
4
|
-
export type { GeminiEvent } from './gemini.js';
|
|
5
3
|
export { ClaudeEventDecoder } from './claude.js';
|
|
6
4
|
export type { ClaudeEvent } from './claude.js';
|
package/dist/decoders/index.js
CHANGED
package/dist/errors.js
CHANGED
|
@@ -3,22 +3,23 @@
|
|
|
3
3
|
*/
|
|
4
4
|
// CLI installation commands
|
|
5
5
|
// Codex: https://developers.openai.com/codex/cli/
|
|
6
|
-
// Gemini: https://
|
|
6
|
+
// Gemini: now ships as `agy` (Antigravity CLI) — https://antigravity.google/download
|
|
7
7
|
const INSTALL_COMMANDS = {
|
|
8
8
|
codex: 'npm install -g @openai/codex-cli',
|
|
9
|
-
gemini: '
|
|
9
|
+
gemini: 'curl -fsSL https://antigravity.google/cli/install.sh | bash',
|
|
10
10
|
claude: 'https://docs.anthropic.com/en/docs/claude-code'
|
|
11
11
|
};
|
|
12
|
-
// Environment variables for API keys
|
|
12
|
+
// Environment variables for API keys.
|
|
13
|
+
// agy uses Google OAuth and has no env var — null surfaces "no key needed".
|
|
13
14
|
const ENV_VARS = {
|
|
14
15
|
codex: 'OPENAI_API_KEY',
|
|
15
|
-
gemini:
|
|
16
|
+
gemini: null,
|
|
16
17
|
claude: 'ANTHROPIC_API_KEY'
|
|
17
18
|
};
|
|
18
19
|
// Authentication commands
|
|
19
20
|
const AUTH_COMMANDS = {
|
|
20
21
|
codex: 'codex login',
|
|
21
|
-
gemini: '
|
|
22
|
+
gemini: 'agy (complete Google OAuth)',
|
|
22
23
|
claude: 'claude auth'
|
|
23
24
|
};
|
|
24
25
|
/**
|
|
@@ -110,13 +111,14 @@ This might happen with complex reviews. Try:
|
|
|
110
111
|
${retryMsg}
|
|
111
112
|
|
|
112
113
|
Alternative: Use /${otherCli}-review instead`;
|
|
113
|
-
case 'auth_error':
|
|
114
|
+
case 'auth_error': {
|
|
115
|
+
const envVar = ENV_VARS[error.cli];
|
|
116
|
+
const keyLine = envVar ? `\nCheck your API key: ${envVar}` : '';
|
|
114
117
|
return `🔐 ${error.cli} authentication failed.
|
|
115
118
|
|
|
116
|
-
${error.message}
|
|
117
|
-
|
|
118
|
-
Check your API key: ${ENV_VARS[error.cli]}
|
|
119
|
+
${error.message}${keyLine}
|
|
119
120
|
Run: ${AUTH_COMMANDS[error.cli]}`;
|
|
121
|
+
}
|
|
120
122
|
case 'invalid_response':
|
|
121
123
|
return `⚠️ ${error.cli} returned an unusable response.
|
|
122
124
|
|
|
@@ -180,8 +182,12 @@ export function getSuggestion(error) {
|
|
|
180
182
|
return error.retryAfterMs
|
|
181
183
|
? `Wait ${Math.ceil(error.retryAfterMs / 1000)}s and retry`
|
|
182
184
|
: 'Wait a moment and retry';
|
|
183
|
-
case 'auth_error':
|
|
184
|
-
|
|
185
|
+
case 'auth_error': {
|
|
186
|
+
const envVar = ENV_VARS[error.cli];
|
|
187
|
+
return envVar
|
|
188
|
+
? `Check your ${envVar} environment variable`
|
|
189
|
+
: `Re-run \`${AUTH_COMMANDS[error.cli]}\` to refresh credentials`;
|
|
190
|
+
}
|
|
185
191
|
case 'invalid_response':
|
|
186
192
|
return 'Retry with a more specific focus area';
|
|
187
193
|
case 'cli_error':
|
package/dist/index.js
CHANGED
|
File without changes
|
package/dist/tools/consult.js
CHANGED
|
@@ -163,7 +163,7 @@ export async function handleMultiConsult(input) {
|
|
|
163
163
|
return {
|
|
164
164
|
content: [{
|
|
165
165
|
type: 'text',
|
|
166
|
-
text: '❌ No AI CLIs found.\n\nInstall at least one:\n - Codex: npm install -g @openai/codex-cli\n - Gemini:
|
|
166
|
+
text: '❌ No AI CLIs found.\n\nInstall at least one:\n - Codex: npm install -g @openai/codex-cli\n - Gemini (agy): curl -fsSL https://antigravity.google/cli/install.sh | bash',
|
|
167
167
|
}],
|
|
168
168
|
};
|
|
169
169
|
}
|
package/dist/tools/feedback.js
CHANGED
|
@@ -57,7 +57,7 @@ export async function handleMultiReview(input) {
|
|
|
57
57
|
const request = toReviewRequest(input);
|
|
58
58
|
const availableAdapters = await getAvailableAdapters();
|
|
59
59
|
if (availableAdapters.length === 0) {
|
|
60
|
-
return { content: [{ type: 'text', text: '❌ No AI CLIs found.\n\nInstall at least one:\n - Codex: npm install -g @openai/codex-cli\n - Gemini:
|
|
60
|
+
return { content: [{ type: 'text', text: '❌ No AI CLIs found.\n\nInstall at least one:\n - Codex: npm install -g @openai/codex-cli\n - Gemini (agy): curl -fsSL https://antigravity.google/cli/install.sh | bash' }] };
|
|
61
61
|
}
|
|
62
62
|
// Spawn 2 reviews per adapter: standard + adversarial (all in parallel)
|
|
63
63
|
// customPrompt steers the adversarial focus only — strip it from standard pass to avoid bias
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simonren/quorum",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "MCP server for Claude Code — a quorum of AI models (Codex, Gemini, Claude) for adversarial review and consultation, synthesized by Claude Code",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|