cc-reviewer 2.0.0 → 2.1.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/dist/adapters/base.d.ts +3 -6
- package/dist/adapters/codex.d.ts +3 -4
- package/dist/adapters/codex.js +76 -291
- package/dist/adapters/gemini.d.ts +3 -4
- package/dist/adapters/gemini.js +52 -243
- package/dist/decoders/codex.d.ts +11 -0
- package/dist/decoders/codex.js +29 -0
- package/dist/handoff.d.ts +3 -4
- package/dist/handoff.js +5 -57
- package/dist/schema.d.ts +6 -6
- package/dist/schema.js +1 -1
- package/dist/tools/feedback.d.ts +5 -6
- package/dist/tools/feedback.js +60 -335
- package/dist/tools/peer.d.ts +0 -2
- package/dist/tools/peer.js +19 -102
- package/package.json +1 -1
package/dist/adapters/base.d.ts
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
* Makes it easy to add new models (Ollama, Azure, etc.) without
|
|
6
6
|
* changing the core orchestration logic.
|
|
7
7
|
*/
|
|
8
|
-
import { ReviewOutput, PeerOutput } from '../schema.js';
|
|
9
8
|
import { FocusArea, OutputType, ReasoningEffort, ServiceTier, TaskType } from '../types.js';
|
|
10
9
|
export interface ReviewerCapabilities {
|
|
11
10
|
/** Display name for this reviewer */
|
|
@@ -77,8 +76,7 @@ export interface PeerRequest {
|
|
|
77
76
|
}
|
|
78
77
|
export interface ReviewSuccess {
|
|
79
78
|
success: true;
|
|
80
|
-
output:
|
|
81
|
-
rawOutput?: string;
|
|
79
|
+
output: string;
|
|
82
80
|
executionTimeMs: number;
|
|
83
81
|
}
|
|
84
82
|
export interface ReviewFailure {
|
|
@@ -96,8 +94,7 @@ export interface ReviewError {
|
|
|
96
94
|
}
|
|
97
95
|
export interface PeerSuccess {
|
|
98
96
|
success: true;
|
|
99
|
-
output:
|
|
100
|
-
rawOutput?: string;
|
|
97
|
+
output: string;
|
|
101
98
|
executionTimeMs: number;
|
|
102
99
|
}
|
|
103
100
|
export interface PeerFailure {
|
|
@@ -127,7 +124,7 @@ export interface ReviewerAdapter {
|
|
|
127
124
|
* Optional: Run peer review of another model's output
|
|
128
125
|
* Future capability - not currently implemented by any adapter
|
|
129
126
|
*/
|
|
130
|
-
runPeerReview?(originalRequest: ReviewRequest, reviewToScore:
|
|
127
|
+
runPeerReview?(originalRequest: ReviewRequest, reviewToScore: string): Promise<ReviewResult>;
|
|
131
128
|
}
|
|
132
129
|
export declare function registerAdapter(adapter: ReviewerAdapter): void;
|
|
133
130
|
export declare function getAdapter(id: string): ReviewerAdapter | undefined;
|
package/dist/adapters/codex.d.ts
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* Codex CLI Adapter
|
|
3
3
|
*
|
|
4
4
|
* Implements the ReviewerAdapter interface for OpenAI's Codex CLI.
|
|
5
|
-
*
|
|
5
|
+
* Returns raw text — no JSON parsing or schema enforcement.
|
|
6
|
+
* CC handles interpretation of the reviewer's response.
|
|
6
7
|
*/
|
|
7
8
|
import { ReviewerAdapter, ReviewerCapabilities, ReviewRequest, ReviewResult, PeerRequest, PeerResult } from './base.js';
|
|
8
9
|
export declare class CodexAdapter implements ReviewerAdapter {
|
|
@@ -10,12 +11,10 @@ export declare class CodexAdapter implements ReviewerAdapter {
|
|
|
10
11
|
getCapabilities(): ReviewerCapabilities;
|
|
11
12
|
isAvailable(): Promise<boolean>;
|
|
12
13
|
runReview(request: ReviewRequest): Promise<ReviewResult>;
|
|
13
|
-
private runWithRetry;
|
|
14
14
|
runPeerRequest(request: PeerRequest): Promise<PeerResult>;
|
|
15
|
-
private runPeerWithRetry;
|
|
16
15
|
private runCli;
|
|
16
|
+
private handleException;
|
|
17
17
|
private categorizeError;
|
|
18
18
|
private getSuggestion;
|
|
19
|
-
private parseRetryAfter;
|
|
20
19
|
}
|
|
21
20
|
export declare const codexAdapter: CodexAdapter;
|
package/dist/adapters/codex.js
CHANGED
|
@@ -2,27 +2,23 @@
|
|
|
2
2
|
* Codex CLI Adapter
|
|
3
3
|
*
|
|
4
4
|
* Implements the ReviewerAdapter interface for OpenAI's Codex CLI.
|
|
5
|
-
*
|
|
5
|
+
* Returns raw text — no JSON parsing or schema enforcement.
|
|
6
|
+
* CC handles interpretation of the reviewer's response.
|
|
6
7
|
*/
|
|
7
8
|
import { spawn } from 'child_process';
|
|
8
|
-
import { existsSync
|
|
9
|
-
import { tmpdir } from 'os';
|
|
10
|
-
import { join } from 'path';
|
|
9
|
+
import { existsSync } from 'fs';
|
|
11
10
|
import { registerAdapter, } from './base.js';
|
|
12
|
-
import { parseReviewOutput, parseLegacyMarkdownOutput, getReviewOutputJsonSchema, getPeerOutputJsonSchema, parsePeerOutput, isSubstantiveReview } from '../schema.js';
|
|
13
11
|
import { CliExecutor } from '../executor.js';
|
|
14
12
|
import { CodexEventDecoder } from '../decoders/index.js';
|
|
15
13
|
import { buildSimpleHandoff, buildHandoffPrompt, buildPeerPrompt, selectRole, } from '../handoff.js';
|
|
16
14
|
// =============================================================================
|
|
17
15
|
// CONFIGURATION
|
|
18
16
|
// =============================================================================
|
|
19
|
-
const
|
|
20
|
-
high: 180_000, // 3 min —
|
|
21
|
-
xhigh: 300_000, // 5 min — xhigh
|
|
17
|
+
const INACTIVITY_TIMEOUT_MS = {
|
|
18
|
+
high: 180_000, // 3 min — covers reasoning gaps between tool use bursts
|
|
19
|
+
xhigh: 300_000, // 5 min — xhigh has longer reasoning phases
|
|
22
20
|
};
|
|
23
|
-
const STREAMING_TIMEOUT_MS = 90_000; // 90s — if events stop mid-stream
|
|
24
21
|
const MAX_TIMEOUT_MS = 3_600_000; // 60 min absolute max
|
|
25
|
-
const MAX_RETRIES = 2;
|
|
26
22
|
const MAX_BUFFER_SIZE = 1024 * 1024; // 1MB max buffer
|
|
27
23
|
// =============================================================================
|
|
28
24
|
// CODEX ADAPTER
|
|
@@ -36,7 +32,7 @@ export class CodexAdapter {
|
|
|
36
32
|
strengths: ['correctness', 'performance', 'security', 'testing'],
|
|
37
33
|
weaknesses: ['documentation'],
|
|
38
34
|
hasFilesystemAccess: true,
|
|
39
|
-
supportsStructuredOutput:
|
|
35
|
+
supportsStructuredOutput: false,
|
|
40
36
|
maxContextTokens: 128000,
|
|
41
37
|
reasoningLevels: ['high', 'xhigh'],
|
|
42
38
|
};
|
|
@@ -46,175 +42,42 @@ export class CodexAdapter {
|
|
|
46
42
|
const proc = spawn('codex', ['--version'], {
|
|
47
43
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
48
44
|
});
|
|
49
|
-
proc.on('close', (code) =>
|
|
50
|
-
|
|
51
|
-
});
|
|
52
|
-
proc.on('error', () => {
|
|
53
|
-
resolve(false);
|
|
54
|
-
});
|
|
55
|
-
// Timeout after 5s
|
|
56
|
-
setTimeout(() => {
|
|
57
|
-
proc.kill();
|
|
58
|
-
resolve(false);
|
|
59
|
-
}, 5000);
|
|
45
|
+
proc.on('close', (code) => resolve(code === 0));
|
|
46
|
+
proc.on('error', () => resolve(false));
|
|
47
|
+
setTimeout(() => { proc.kill(); resolve(false); }, 5000);
|
|
60
48
|
});
|
|
61
49
|
}
|
|
62
50
|
async runReview(request) {
|
|
63
51
|
const startTime = Date.now();
|
|
64
|
-
// Validate working directory
|
|
65
52
|
if (!existsSync(request.workingDir)) {
|
|
66
53
|
return {
|
|
67
54
|
success: false,
|
|
68
|
-
error: {
|
|
69
|
-
type: 'cli_error',
|
|
70
|
-
message: `Working directory does not exist: ${request.workingDir}`,
|
|
71
|
-
},
|
|
55
|
+
error: { type: 'cli_error', message: `Working directory does not exist: ${request.workingDir}` },
|
|
72
56
|
suggestion: 'Check that the working directory path is correct',
|
|
73
57
|
executionTimeMs: Date.now() - startTime,
|
|
74
58
|
};
|
|
75
59
|
}
|
|
76
|
-
return this.runWithRetry(request, 0, startTime);
|
|
77
|
-
}
|
|
78
|
-
async runWithRetry(request, attempt, startTime, previousError, previousOutput) {
|
|
79
60
|
try {
|
|
80
|
-
// Build the prompt using handoff protocol
|
|
81
61
|
const handoff = buildSimpleHandoff(request.workingDir, request.ccOutput, request.analyzedFiles, request.focusAreas, request.customPrompt);
|
|
82
|
-
// Select role based on focus areas
|
|
83
62
|
const role = selectRole(request.focusAreas);
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
let prompt = buildHandoffPrompt({
|
|
87
|
-
handoff,
|
|
88
|
-
role,
|
|
89
|
-
outputFormat: 'schema-enforced',
|
|
90
|
-
});
|
|
91
|
-
// Add retry context if this is a retry attempt
|
|
92
|
-
if (attempt > 0) {
|
|
93
|
-
prompt += `\n\n---\n\n# RETRY ATTEMPT ${attempt + 1}\n\n` +
|
|
94
|
-
`Previous output had issues: ${previousError}\n` +
|
|
95
|
-
`Please fix these issues and provide valid JSON output.\n` +
|
|
96
|
-
(previousOutput ? `\nPrevious output (for reference):\n${previousOutput.slice(0, 500)}...` : '');
|
|
97
|
-
}
|
|
98
|
-
// Run the CLI
|
|
99
|
-
const result = await this.runCli(prompt, request.workingDir, request.reasoningEffort || 'high', getReviewOutputJsonSchema, request.serviceTier);
|
|
100
|
-
// Handle CLI errors
|
|
63
|
+
const prompt = buildHandoffPrompt({ handoff, role });
|
|
64
|
+
const result = await this.runCli(prompt, request.workingDir, request.reasoningEffort || 'high', request.serviceTier);
|
|
101
65
|
if (result.exitCode !== 0) {
|
|
102
66
|
const error = this.categorizeError(result.stderr);
|
|
103
|
-
return {
|
|
104
|
-
success: false,
|
|
105
|
-
error,
|
|
106
|
-
suggestion: this.getSuggestion(error),
|
|
107
|
-
rawOutput: result.stderr,
|
|
108
|
-
executionTimeMs: Date.now() - startTime,
|
|
109
|
-
};
|
|
67
|
+
return { success: false, error, suggestion: this.getSuggestion(error), executionTimeMs: Date.now() - startTime };
|
|
110
68
|
}
|
|
111
|
-
|
|
112
|
-
if (result.truncated) {
|
|
69
|
+
if (!result.stdout.trim()) {
|
|
113
70
|
return {
|
|
114
71
|
success: false,
|
|
115
|
-
error: {
|
|
116
|
-
|
|
117
|
-
message: 'Output exceeded maximum buffer size (1MB) and was truncated',
|
|
118
|
-
},
|
|
119
|
-
suggestion: 'Try reviewing a smaller scope with --focus',
|
|
72
|
+
error: { type: 'cli_error', message: 'Codex returned empty response' },
|
|
73
|
+
suggestion: 'Try again or use /gemini instead',
|
|
120
74
|
executionTimeMs: Date.now() - startTime,
|
|
121
75
|
};
|
|
122
76
|
}
|
|
123
|
-
|
|
124
|
-
let output = parseReviewOutput(result.stdout);
|
|
125
|
-
let usedFallback = false;
|
|
126
|
-
// If JSON parsing fails, try legacy markdown
|
|
127
|
-
if (!output) {
|
|
128
|
-
output = parseLegacyMarkdownOutput(result.stdout, 'codex');
|
|
129
|
-
usedFallback = true;
|
|
130
|
-
}
|
|
131
|
-
// If no valid output, retry or fail
|
|
132
|
-
if (!output) {
|
|
133
|
-
if (attempt < MAX_RETRIES) {
|
|
134
|
-
return this.runWithRetry(request, attempt + 1, startTime, 'Output did not match expected JSON schema', result.stdout);
|
|
135
|
-
}
|
|
136
|
-
return {
|
|
137
|
-
success: false,
|
|
138
|
-
error: {
|
|
139
|
-
type: 'parse_error',
|
|
140
|
-
message: 'Failed to parse reviewer output after retries',
|
|
141
|
-
details: { rawOutput: result.stdout.slice(0, 1000) },
|
|
142
|
-
},
|
|
143
|
-
suggestion: 'The model may not be following the output format. Try a different focus area.',
|
|
144
|
-
rawOutput: result.stdout,
|
|
145
|
-
executionTimeMs: Date.now() - startTime,
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
// Check for empty/minimal output — centralized substance check
|
|
149
|
-
if (!isSubstantiveReview(output)) {
|
|
150
|
-
if (attempt < MAX_RETRIES) {
|
|
151
|
-
console.error(`[codex] Received empty output, retrying...`);
|
|
152
|
-
return this.runWithRetry(request, attempt + 1, startTime, usedFallback
|
|
153
|
-
? 'Received markdown output instead of JSON. Please provide valid JSON output.'
|
|
154
|
-
: 'Output contained no substantive review content. Please provide findings or analysis.', result.stdout);
|
|
155
|
-
}
|
|
156
|
-
return {
|
|
157
|
-
success: false,
|
|
158
|
-
error: {
|
|
159
|
-
type: 'parse_error',
|
|
160
|
-
message: 'Reviewer returned empty output after retries',
|
|
161
|
-
details: { rawOutput: result.stdout.slice(0, 1000) },
|
|
162
|
-
},
|
|
163
|
-
suggestion: 'The model returned no substantive review. Try a different focus area.',
|
|
164
|
-
rawOutput: result.stdout,
|
|
165
|
-
executionTimeMs: Date.now() - startTime,
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
return {
|
|
169
|
-
success: true,
|
|
170
|
-
output,
|
|
171
|
-
rawOutput: result.stdout,
|
|
172
|
-
executionTimeMs: Date.now() - startTime,
|
|
173
|
-
};
|
|
77
|
+
return { success: true, output: result.stdout, executionTimeMs: Date.now() - startTime };
|
|
174
78
|
}
|
|
175
79
|
catch (error) {
|
|
176
|
-
|
|
177
|
-
if (err.code === 'ENOENT') {
|
|
178
|
-
return {
|
|
179
|
-
success: false,
|
|
180
|
-
error: {
|
|
181
|
-
type: 'cli_not_found',
|
|
182
|
-
message: 'Codex CLI not found',
|
|
183
|
-
},
|
|
184
|
-
suggestion: 'Install with: npm install -g @openai/codex',
|
|
185
|
-
executionTimeMs: Date.now() - startTime,
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
if (err.message === 'TIMEOUT') {
|
|
189
|
-
return {
|
|
190
|
-
success: false,
|
|
191
|
-
error: {
|
|
192
|
-
type: 'timeout',
|
|
193
|
-
message: 'No output for 2 minutes - process may be hung',
|
|
194
|
-
},
|
|
195
|
-
suggestion: 'Try a smaller scope or use --focus',
|
|
196
|
-
executionTimeMs: Date.now() - startTime,
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
if (err.message === 'MAX_TIMEOUT') {
|
|
200
|
-
return {
|
|
201
|
-
success: false,
|
|
202
|
-
error: {
|
|
203
|
-
type: 'timeout',
|
|
204
|
-
message: 'Task exceeded 60 minute maximum',
|
|
205
|
-
},
|
|
206
|
-
suggestion: 'Try a smaller scope',
|
|
207
|
-
executionTimeMs: Date.now() - startTime,
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
return {
|
|
211
|
-
success: false,
|
|
212
|
-
error: {
|
|
213
|
-
type: 'cli_error',
|
|
214
|
-
message: err.message,
|
|
215
|
-
},
|
|
216
|
-
executionTimeMs: Date.now() - startTime,
|
|
217
|
-
};
|
|
80
|
+
return this.handleException(error, startTime);
|
|
218
81
|
}
|
|
219
82
|
}
|
|
220
83
|
async runPeerRequest(request) {
|
|
@@ -222,20 +85,13 @@ export class CodexAdapter {
|
|
|
222
85
|
if (!existsSync(request.workingDir)) {
|
|
223
86
|
return {
|
|
224
87
|
success: false,
|
|
225
|
-
error: {
|
|
226
|
-
type: 'cli_error',
|
|
227
|
-
message: `Working directory does not exist: ${request.workingDir}`,
|
|
228
|
-
},
|
|
88
|
+
error: { type: 'cli_error', message: `Working directory does not exist: ${request.workingDir}` },
|
|
229
89
|
suggestion: 'Check that the working directory path is correct',
|
|
230
90
|
executionTimeMs: Date.now() - startTime,
|
|
231
91
|
};
|
|
232
92
|
}
|
|
233
|
-
return this.runPeerWithRetry(request, 0, startTime);
|
|
234
|
-
}
|
|
235
|
-
async runPeerWithRetry(request, attempt, startTime, previousError, previousOutput) {
|
|
236
93
|
try {
|
|
237
|
-
|
|
238
|
-
let prompt = buildPeerPrompt({
|
|
94
|
+
const prompt = buildPeerPrompt({
|
|
239
95
|
workingDir: request.workingDir,
|
|
240
96
|
prompt: request.prompt,
|
|
241
97
|
taskType: request.taskType,
|
|
@@ -243,85 +99,27 @@ export class CodexAdapter {
|
|
|
243
99
|
context: request.context,
|
|
244
100
|
focusAreas: request.focusAreas,
|
|
245
101
|
customInstructions: request.customPrompt,
|
|
246
|
-
outputFormat: 'schema-enforced',
|
|
247
102
|
});
|
|
248
|
-
|
|
249
|
-
prompt += `\n\n---\n\n# RETRY ATTEMPT ${attempt + 1}\n\n` +
|
|
250
|
-
`Previous output had issues: ${previousError}\n` +
|
|
251
|
-
`Please fix these issues and provide valid JSON output.\n` +
|
|
252
|
-
(previousOutput ? `\nPrevious output (for reference):\n${previousOutput.slice(0, 500)}...` : '');
|
|
253
|
-
}
|
|
254
|
-
const result = await this.runCli(prompt, request.workingDir, request.reasoningEffort || 'high', getPeerOutputJsonSchema, request.serviceTier);
|
|
103
|
+
const result = await this.runCli(prompt, request.workingDir, request.reasoningEffort || 'high', request.serviceTier);
|
|
255
104
|
if (result.exitCode !== 0) {
|
|
256
105
|
const error = this.categorizeError(result.stderr);
|
|
257
|
-
return {
|
|
258
|
-
success: false,
|
|
259
|
-
error,
|
|
260
|
-
suggestion: this.getSuggestion(error),
|
|
261
|
-
rawOutput: result.stderr,
|
|
262
|
-
executionTimeMs: Date.now() - startTime,
|
|
263
|
-
};
|
|
106
|
+
return { success: false, error, suggestion: this.getSuggestion(error), executionTimeMs: Date.now() - startTime };
|
|
264
107
|
}
|
|
265
|
-
if (result.
|
|
108
|
+
if (!result.stdout.trim()) {
|
|
266
109
|
return {
|
|
267
110
|
success: false,
|
|
268
|
-
error: { type: 'cli_error', message: '
|
|
269
|
-
suggestion: 'Try
|
|
111
|
+
error: { type: 'cli_error', message: 'Codex returned empty response' },
|
|
112
|
+
suggestion: 'Try again or use /ask-gemini instead',
|
|
270
113
|
executionTimeMs: Date.now() - startTime,
|
|
271
114
|
};
|
|
272
115
|
}
|
|
273
|
-
|
|
274
|
-
if (!output) {
|
|
275
|
-
if (attempt < MAX_RETRIES) {
|
|
276
|
-
return this.runPeerWithRetry(request, attempt + 1, startTime, 'Output did not match expected JSON schema', result.stdout);
|
|
277
|
-
}
|
|
278
|
-
return {
|
|
279
|
-
success: false,
|
|
280
|
-
error: { type: 'parse_error', message: 'Failed to parse peer output after retries',
|
|
281
|
-
details: { rawOutput: result.stdout.slice(0, 1000) } },
|
|
282
|
-
suggestion: 'The model may not be following the output format.',
|
|
283
|
-
rawOutput: result.stdout,
|
|
284
|
-
executionTimeMs: Date.now() - startTime,
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
|
-
return {
|
|
288
|
-
success: true,
|
|
289
|
-
output,
|
|
290
|
-
rawOutput: result.stdout,
|
|
291
|
-
executionTimeMs: Date.now() - startTime,
|
|
292
|
-
};
|
|
116
|
+
return { success: true, output: result.stdout, executionTimeMs: Date.now() - startTime };
|
|
293
117
|
}
|
|
294
118
|
catch (error) {
|
|
295
|
-
|
|
296
|
-
if (err.code === 'ENOENT') {
|
|
297
|
-
return { success: false, error: { type: 'cli_not_found', message: 'Codex CLI not found' },
|
|
298
|
-
suggestion: 'Install with: npm install -g @openai/codex', executionTimeMs: Date.now() - startTime };
|
|
299
|
-
}
|
|
300
|
-
if (err.message === 'TIMEOUT') {
|
|
301
|
-
return { success: false, error: { type: 'timeout', message: 'No output for 2 minutes' },
|
|
302
|
-
suggestion: 'Try a simpler request', executionTimeMs: Date.now() - startTime };
|
|
303
|
-
}
|
|
304
|
-
if (err.message === 'MAX_TIMEOUT') {
|
|
305
|
-
return { success: false, error: { type: 'timeout', message: 'Task exceeded 60 minute maximum' },
|
|
306
|
-
suggestion: 'Try a smaller scope', executionTimeMs: Date.now() - startTime };
|
|
307
|
-
}
|
|
308
|
-
return { success: false, error: { type: 'cli_error', message: err.message },
|
|
309
|
-
executionTimeMs: Date.now() - startTime };
|
|
119
|
+
return this.handleException(error, startTime);
|
|
310
120
|
}
|
|
311
121
|
}
|
|
312
|
-
async runCli(prompt, workingDir, reasoningEffort,
|
|
313
|
-
// Create temp schema file for structured output
|
|
314
|
-
let schemaFile = null;
|
|
315
|
-
try {
|
|
316
|
-
const tempDir = mkdtempSync(join(tmpdir(), 'codex-schema-'));
|
|
317
|
-
schemaFile = join(tempDir, 'schema.json');
|
|
318
|
-
const schema = schemaGetter();
|
|
319
|
-
writeFileSync(schemaFile, JSON.stringify(schema, null, 2), 'utf-8');
|
|
320
|
-
}
|
|
321
|
-
catch (err) {
|
|
322
|
-
console.error('[codex] Warning: Failed to create schema file:', err);
|
|
323
|
-
schemaFile = null;
|
|
324
|
-
}
|
|
122
|
+
async runCli(prompt, workingDir, reasoningEffort, serviceTier) {
|
|
325
123
|
const args = [
|
|
326
124
|
'exec',
|
|
327
125
|
'--json', // JSONL streaming events
|
|
@@ -331,19 +129,15 @@ export class CodexAdapter {
|
|
|
331
129
|
'--dangerously-bypass-approvals-and-sandbox',
|
|
332
130
|
'--skip-git-repo-check',
|
|
333
131
|
'-C', workingDir,
|
|
132
|
+
'-', // Read prompt from stdin
|
|
334
133
|
];
|
|
335
134
|
if (serviceTier && serviceTier !== 'default') {
|
|
336
135
|
args.push('-c', `service_tier=${serviceTier}`);
|
|
337
136
|
}
|
|
338
|
-
if (schemaFile) {
|
|
339
|
-
args.push('--output-schema', schemaFile);
|
|
340
|
-
}
|
|
341
|
-
args.push('-'); // Read prompt from stdin
|
|
342
137
|
const decoder = new CodexEventDecoder();
|
|
343
138
|
const cliStartTime = Date.now();
|
|
344
|
-
let firstEventReceived = false;
|
|
345
139
|
const tierLabel = serviceTier && serviceTier !== 'default' ? ` [${serviceTier}]` : '';
|
|
346
|
-
console.error(`[codex] Running
|
|
140
|
+
console.error(`[codex] Running with ${reasoningEffort} reasoning${tierLabel}...`);
|
|
347
141
|
decoder.onProgress = (eventType, detail) => {
|
|
348
142
|
const elapsed = Math.round((Date.now() - cliStartTime) / 1000);
|
|
349
143
|
const detailStr = detail ? ` — ${detail}` : '';
|
|
@@ -354,79 +148,70 @@ export class CodexAdapter {
|
|
|
354
148
|
args,
|
|
355
149
|
cwd: workingDir,
|
|
356
150
|
stdin: prompt,
|
|
357
|
-
inactivityTimeoutMs:
|
|
151
|
+
inactivityTimeoutMs: INACTIVITY_TIMEOUT_MS[reasoningEffort] || INACTIVITY_TIMEOUT_MS.high,
|
|
358
152
|
maxTimeoutMs: MAX_TIMEOUT_MS,
|
|
359
153
|
maxBufferSize: MAX_BUFFER_SIZE,
|
|
360
154
|
onLine: (line) => {
|
|
361
155
|
decoder.processLine(line);
|
|
362
|
-
// Phase transition: tighten timeout after first event
|
|
363
|
-
if (!firstEventReceived) {
|
|
364
|
-
firstEventReceived = true;
|
|
365
|
-
executor.setInactivityTimeout(STREAMING_TIMEOUT_MS);
|
|
366
|
-
}
|
|
367
156
|
},
|
|
368
157
|
});
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
stderr: result.stderr,
|
|
377
|
-
exitCode: result.exitCode,
|
|
378
|
-
truncated: result.truncated,
|
|
379
|
-
};
|
|
158
|
+
const result = await executor.run();
|
|
159
|
+
const elapsed = Math.round((Date.now() - cliStartTime) / 1000);
|
|
160
|
+
console.error(`[codex] ✓ complete (${elapsed}s)`);
|
|
161
|
+
// Check for errors captured from JSONL events
|
|
162
|
+
const decoderError = decoder.getError();
|
|
163
|
+
if (decoderError) {
|
|
164
|
+
return { stdout: '', stderr: decoderError, exitCode: 1, truncated: false };
|
|
380
165
|
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
166
|
+
const finalResponse = decoder.getFinalResponse();
|
|
167
|
+
if (!finalResponse && decoder.hasNoOutput()) {
|
|
168
|
+
return { stdout: '', stderr: 'No response from Codex — possible rate limit or model rejection', exitCode: 1, truncated: false };
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
stdout: finalResponse || result.rawStdout,
|
|
172
|
+
stderr: result.stderr,
|
|
173
|
+
exitCode: result.exitCode,
|
|
174
|
+
truncated: result.truncated,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
handleException(error, startTime) {
|
|
178
|
+
const err = error;
|
|
179
|
+
if (err.code === 'ENOENT') {
|
|
180
|
+
return { success: false, error: { type: 'cli_not_found', message: 'Codex CLI not found' },
|
|
181
|
+
suggestion: 'Install with: npm install -g @openai/codex', executionTimeMs: Date.now() - startTime };
|
|
388
182
|
}
|
|
183
|
+
if (err.message === 'TIMEOUT') {
|
|
184
|
+
return { success: false, error: { type: 'timeout', message: 'Codex timed out — no events received' },
|
|
185
|
+
suggestion: 'Try a smaller scope or use /gemini', executionTimeMs: Date.now() - startTime };
|
|
186
|
+
}
|
|
187
|
+
if (err.message === 'MAX_TIMEOUT') {
|
|
188
|
+
return { success: false, error: { type: 'timeout', message: 'Task exceeded 60 minute maximum' },
|
|
189
|
+
suggestion: 'Try a smaller scope', executionTimeMs: Date.now() - startTime };
|
|
190
|
+
}
|
|
191
|
+
return { success: false, error: { type: 'cli_error', message: err.message }, executionTimeMs: Date.now() - startTime };
|
|
389
192
|
}
|
|
390
193
|
categorizeError(stderr) {
|
|
391
194
|
const lower = stderr.toLowerCase();
|
|
392
|
-
if (lower.includes('rate limit')) {
|
|
393
|
-
return {
|
|
394
|
-
type: 'rate_limit',
|
|
395
|
-
message: 'Rate limit exceeded',
|
|
396
|
-
details: { retryAfterMs: this.parseRetryAfter(stderr) },
|
|
397
|
-
};
|
|
195
|
+
if (lower.includes('rate limit') || lower.includes('possible rate limit') || lower.includes('no response from codex')) {
|
|
196
|
+
return { type: 'rate_limit', message: 'Codex rate limit — no tokens available' };
|
|
398
197
|
}
|
|
399
|
-
if (lower.includes('unauthorized') || lower.includes('authentication') ||
|
|
400
|
-
|
|
401
|
-
return {
|
|
402
|
-
type: 'auth_error',
|
|
403
|
-
message: 'Authentication failed',
|
|
404
|
-
details: { stderr },
|
|
405
|
-
};
|
|
198
|
+
if (lower.includes('unauthorized') || lower.includes('authentication') || stderr.includes('401') || stderr.includes('403')) {
|
|
199
|
+
return { type: 'auth_error', message: 'Authentication failed', details: { stderr } };
|
|
406
200
|
}
|
|
407
|
-
|
|
408
|
-
type: 'cli_error',
|
|
409
|
-
|
|
410
|
-
};
|
|
201
|
+
if (lower.includes('invalid_json_schema') || lower.includes('invalid_request_error')) {
|
|
202
|
+
return { type: 'cli_error', message: `API error: ${stderr.slice(0, 300)}` };
|
|
203
|
+
}
|
|
204
|
+
return { type: 'cli_error', message: stderr || 'Unknown error' };
|
|
411
205
|
}
|
|
412
206
|
getSuggestion(error) {
|
|
413
207
|
switch (error.type) {
|
|
414
|
-
case 'rate_limit':
|
|
415
|
-
|
|
416
|
-
case '
|
|
417
|
-
|
|
418
|
-
case 'cli_not_found':
|
|
419
|
-
return 'Install with: npm install -g @openai/codex';
|
|
420
|
-
default:
|
|
421
|
-
return 'Check the error message and try again';
|
|
208
|
+
case 'rate_limit': return 'Wait and retry, or use /gemini instead';
|
|
209
|
+
case 'auth_error': return 'Run `codex login` to authenticate';
|
|
210
|
+
case 'cli_not_found': return 'Install with: npm install -g @openai/codex';
|
|
211
|
+
default: return 'Check the error message and try again';
|
|
422
212
|
}
|
|
423
213
|
}
|
|
424
|
-
parseRetryAfter(errorMessage) {
|
|
425
|
-
const match = errorMessage.match(/retry[- ]?after[:\s]+(\d+)/i);
|
|
426
|
-
return match ? parseInt(match[1]) * 1000 : undefined;
|
|
427
|
-
}
|
|
428
214
|
}
|
|
429
215
|
// Register the adapter
|
|
430
216
|
registerAdapter(new CodexAdapter());
|
|
431
|
-
// Export for direct use
|
|
432
217
|
export const codexAdapter = new CodexAdapter();
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* Gemini CLI Adapter
|
|
3
3
|
*
|
|
4
4
|
* Implements the ReviewerAdapter interface for Google's Gemini CLI.
|
|
5
|
-
*
|
|
5
|
+
* Returns raw text — no JSON parsing or schema enforcement.
|
|
6
|
+
* CC handles interpretation of the reviewer's response.
|
|
6
7
|
*/
|
|
7
8
|
import { ReviewerAdapter, ReviewerCapabilities, ReviewRequest, ReviewResult, PeerRequest, PeerResult } from './base.js';
|
|
8
9
|
export declare class GeminiAdapter implements ReviewerAdapter {
|
|
@@ -10,12 +11,10 @@ export declare class GeminiAdapter implements ReviewerAdapter {
|
|
|
10
11
|
getCapabilities(): ReviewerCapabilities;
|
|
11
12
|
isAvailable(): Promise<boolean>;
|
|
12
13
|
runReview(request: ReviewRequest): Promise<ReviewResult>;
|
|
13
|
-
private runWithRetry;
|
|
14
14
|
runPeerRequest(request: PeerRequest): Promise<PeerResult>;
|
|
15
|
-
private runPeerWithRetry;
|
|
16
15
|
private runCli;
|
|
16
|
+
private handleException;
|
|
17
17
|
private categorizeError;
|
|
18
18
|
private getSuggestion;
|
|
19
|
-
private parseRetryAfter;
|
|
20
19
|
}
|
|
21
20
|
export declare const geminiAdapter: GeminiAdapter;
|