@simonren/quorum 0.8.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/dist/adapters/claude.js +16 -13
- package/dist/adapters/codex.js +31 -13
- package/dist/decoders/codex.d.ts +11 -1
- package/dist/decoders/codex.js +26 -4
- package/dist/index.js +0 -0
- package/package.json +1 -1
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;
|
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/index.js
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simonren/quorum",
|
|
3
|
-
"version": "0.8.
|
|
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",
|