steroids-cli 0.10.15 → 0.10.17
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/commands/loop-phases.d.ts +2 -17
- package/dist/commands/loop-phases.d.ts.map +1 -1
- package/dist/commands/loop-phases.js +79 -285
- package/dist/commands/loop-phases.js.map +1 -1
- package/dist/commands/loop.d.ts.map +1 -1
- package/dist/commands/loop.js +3 -62
- package/dist/commands/loop.js.map +1 -1
- package/dist/database/queries.d.ts +2 -0
- package/dist/database/queries.d.ts.map +1 -1
- package/dist/database/queries.js +24 -0
- package/dist/database/queries.js.map +1 -1
- package/dist/runners/orchestrator-loop.d.ts.map +1 -1
- package/dist/runners/orchestrator-loop.js +47 -87
- package/dist/runners/orchestrator-loop.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,17 +1,11 @@
|
|
|
1
1
|
import { getTask } from '../database/queries.js';
|
|
2
2
|
import type { openDatabase } from '../database/connection.js';
|
|
3
3
|
import { type CoordinatorResult } from '../orchestrator/coordinator.js';
|
|
4
|
-
import { type ReviewerConfig } from '../config/loader.js';
|
|
5
|
-
import type { InvokeResult } from '../providers/interface.js';
|
|
6
|
-
import type { ProviderError } from '../providers/interface.js';
|
|
7
4
|
export { type CoordinatorResult };
|
|
8
5
|
interface LeaseFenceContext {
|
|
9
6
|
parallelSessionId?: string;
|
|
10
7
|
runnerId?: string;
|
|
11
8
|
}
|
|
12
|
-
/**
|
|
13
|
-
* Returned when a provider invocation is classified as credit exhaustion or rate limit.
|
|
14
|
-
*/
|
|
15
9
|
export interface CreditExhaustionResult {
|
|
16
10
|
action: 'pause_credit_exhaustion' | 'rate_limit';
|
|
17
11
|
provider: string;
|
|
@@ -20,15 +14,6 @@ export interface CreditExhaustionResult {
|
|
|
20
14
|
message: string;
|
|
21
15
|
retryAfterMs?: number;
|
|
22
16
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
*/
|
|
26
|
-
export declare function checkSafetyViolation(result: InvokeResult, role: 'coder' | 'reviewer', projectPath: string, reviewerConfig?: ReviewerConfig, classification?: ProviderError | null): Promise<{
|
|
27
|
-
type: string;
|
|
28
|
-
provider: string;
|
|
29
|
-
model: string;
|
|
30
|
-
message: string;
|
|
31
|
-
} | null>;
|
|
32
|
-
export declare function runCoderPhase(db: ReturnType<typeof openDatabase>['db'], task: ReturnType<typeof getTask>, projectPath: string, action: 'start' | 'resume', jsonMode?: boolean, coordinatorCache?: Map<string, CoordinatorResult>, coordinatorThresholds?: number[], leaseFence?: LeaseFenceContext, branchName?: string): Promise<CreditExhaustionResult | void>;
|
|
33
|
-
export declare function runReviewerPhase(db: ReturnType<typeof openDatabase>['db'], task: ReturnType<typeof getTask>, projectPath: string, jsonMode?: boolean, coordinatorResult?: CoordinatorResult, branchName?: string, leaseFence?: LeaseFenceContext): Promise<CreditExhaustionResult | void>;
|
|
17
|
+
export declare function runCoderPhase(db: ReturnType<typeof openDatabase>['db'], task: ReturnType<typeof getTask>, projectPath: string, action: 'start' | 'resume', jsonMode?: boolean, coordinatorCache?: Map<string, CoordinatorResult>, coordinatorThresholds?: number[], leaseFence?: LeaseFenceContext, branchName?: string): Promise<void>;
|
|
18
|
+
export declare function runReviewerPhase(db: ReturnType<typeof openDatabase>['db'], task: ReturnType<typeof getTask>, projectPath: string, jsonMode?: boolean, coordinatorResult?: CoordinatorResult, branchName?: string, leaseFence?: LeaseFenceContext): Promise<void>;
|
|
34
19
|
//# sourceMappingURL=loop-phases.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loop-phases.d.ts","sourceRoot":"","sources":["../../src/commands/loop-phases.ts"],"names":[],"mappings":"AAOA,OAAO,EACL,OAAO,
|
|
1
|
+
{"version":3,"file":"loop-phases.d.ts","sourceRoot":"","sources":["../../src/commands/loop-phases.ts"],"names":[],"mappings":"AAOA,OAAO,EACL,OAAO,EAeR,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAU9D,OAAO,EAA8C,KAAK,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AA0BpH,OAAO,EAAE,KAAK,iBAAiB,EAAE,CAAC;AAElC,UAAU,iBAAiB;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AA2CD,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,yBAAyB,GAAG,YAAY,CAAC;IACjD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,OAAO,GAAG,UAAU,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AA0PD,wBAAsB,aAAa,CACjC,EAAE,EAAE,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,IAAI,CAAC,EACzC,IAAI,EAAE,UAAU,CAAC,OAAO,OAAO,CAAC,EAChC,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,OAAO,GAAG,QAAQ,EAC1B,QAAQ,UAAQ,EAChB,gBAAgB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,EACjD,qBAAqB,CAAC,EAAE,MAAM,EAAE,EAChC,UAAU,CAAC,EAAE,iBAAiB,EAC9B,UAAU,SAAS,GAClB,OAAO,CAAC,IAAI,CAAC,CA8iBf;AAED,wBAAsB,gBAAgB,CACpC,EAAE,EAAE,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,IAAI,CAAC,EACzC,IAAI,EAAE,UAAU,CAAC,OAAO,OAAO,CAAC,EAChC,WAAW,EAAE,MAAM,EACnB,QAAQ,UAAQ,EAChB,iBAAiB,CAAC,EAAE,iBAAiB,EACrC,UAAU,GAAE,MAAe,EAC3B,UAAU,CAAC,EAAE,iBAAiB,GAC7B,OAAO,CAAC,IAAI,CAAC,CA0cf"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.checkSafetyViolation = checkSafetyViolation;
|
|
4
3
|
exports.runCoderPhase = runCoderPhase;
|
|
5
4
|
exports.runReviewerPhase = runReviewerPhase;
|
|
6
5
|
const global_db_js_1 = require("../runners/global-db.js");
|
|
@@ -50,118 +49,6 @@ function refreshParallelWorkstreamLease(projectPath, leaseFence) {
|
|
|
50
49
|
}
|
|
51
50
|
});
|
|
52
51
|
}
|
|
53
|
-
/**
|
|
54
|
-
* Check a coder/reviewer result for credit exhaustion or rate limits using the provider's classifier.
|
|
55
|
-
* Uses provider.classifyResult() which checks both stderr and stdout.
|
|
56
|
-
* Returns a CreditExhaustionResult if credits are exhausted or rate limited, null otherwise.
|
|
57
|
-
*/
|
|
58
|
-
async function checkCreditExhaustion(result, role, projectPath, reviewerConfig, classification) {
|
|
59
|
-
if (result.success)
|
|
60
|
-
return null;
|
|
61
|
-
const resolvedClassification = classification === undefined ? await classifyProviderResult(result, role, projectPath, reviewerConfig) : classification;
|
|
62
|
-
if (!resolvedClassification)
|
|
63
|
-
return null;
|
|
64
|
-
const config = (0, loader_js_1.loadConfig)(projectPath);
|
|
65
|
-
const roleConfig = reviewerConfig || config.ai?.[role];
|
|
66
|
-
const providerName = roleConfig?.provider;
|
|
67
|
-
const modelName = roleConfig?.model;
|
|
68
|
-
if (!providerName || !modelName)
|
|
69
|
-
return null;
|
|
70
|
-
if (resolvedClassification.type === 'credit_exhaustion') {
|
|
71
|
-
return {
|
|
72
|
-
action: 'pause_credit_exhaustion',
|
|
73
|
-
provider: providerName,
|
|
74
|
-
model: modelName,
|
|
75
|
-
role,
|
|
76
|
-
message: resolvedClassification.message,
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
if (resolvedClassification.type === 'rate_limit') {
|
|
80
|
-
return {
|
|
81
|
-
action: 'rate_limit',
|
|
82
|
-
provider: providerName,
|
|
83
|
-
model: modelName,
|
|
84
|
-
role,
|
|
85
|
-
message: resolvedClassification.message,
|
|
86
|
-
retryAfterMs: resolvedClassification.retryAfterMs,
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
return null;
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Classify a provider invocation result exactly once per path.
|
|
93
|
-
*/
|
|
94
|
-
async function classifyProviderResult(result, role, projectPath, reviewerConfig) {
|
|
95
|
-
if (result.success)
|
|
96
|
-
return null;
|
|
97
|
-
const config = (0, loader_js_1.loadConfig)(projectPath);
|
|
98
|
-
const roleConfig = reviewerConfig || config.ai?.[role];
|
|
99
|
-
const providerName = roleConfig?.provider;
|
|
100
|
-
if (!providerName)
|
|
101
|
-
return null;
|
|
102
|
-
const registry = await (0, registry_js_1.getProviderRegistry)();
|
|
103
|
-
const provider = registry.tryGet(providerName);
|
|
104
|
-
if (!provider)
|
|
105
|
-
return null;
|
|
106
|
-
return provider.classifyResult(result);
|
|
107
|
-
}
|
|
108
|
-
/**
|
|
109
|
-
* Check for non-retryable provider failures that should fail the task immediately.
|
|
110
|
-
*/
|
|
111
|
-
async function checkNonRetryableProviderFailure(result, role, projectPath, reviewerConfig, classification) {
|
|
112
|
-
if (result.success)
|
|
113
|
-
return null;
|
|
114
|
-
const config = (0, loader_js_1.loadConfig)(projectPath);
|
|
115
|
-
const roleConfig = reviewerConfig || config.ai?.[role];
|
|
116
|
-
const providerName = roleConfig?.provider;
|
|
117
|
-
const modelName = roleConfig?.model;
|
|
118
|
-
if (!providerName || !modelName)
|
|
119
|
-
return null;
|
|
120
|
-
const registry = await (0, registry_js_1.getProviderRegistry)();
|
|
121
|
-
const provider = registry.tryGet(providerName);
|
|
122
|
-
if (!provider)
|
|
123
|
-
return null;
|
|
124
|
-
const resolvedClassification = classification === undefined ? await classifyProviderResult(result, role, projectPath, reviewerConfig) : classification;
|
|
125
|
-
if (!resolvedClassification)
|
|
126
|
-
return null;
|
|
127
|
-
if (resolvedClassification.type === 'model_not_found' ||
|
|
128
|
-
resolvedClassification.type === 'context_exceeded') {
|
|
129
|
-
return {
|
|
130
|
-
type: resolvedClassification.type,
|
|
131
|
-
provider: providerName,
|
|
132
|
-
model: modelName,
|
|
133
|
-
message: resolvedClassification.message,
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
return null;
|
|
137
|
-
}
|
|
138
|
-
/**
|
|
139
|
-
* Check for policy or safety violations
|
|
140
|
-
*/
|
|
141
|
-
async function checkSafetyViolation(result, role, projectPath, reviewerConfig, classification) {
|
|
142
|
-
if (result.success)
|
|
143
|
-
return null;
|
|
144
|
-
const config = (0, loader_js_1.loadConfig)(projectPath);
|
|
145
|
-
const roleConfig = reviewerConfig || config.ai?.[role];
|
|
146
|
-
const providerName = roleConfig?.provider;
|
|
147
|
-
const modelName = roleConfig?.model;
|
|
148
|
-
if (!providerName || !modelName)
|
|
149
|
-
return null;
|
|
150
|
-
const resolvedClassification = classification === undefined ? await classifyProviderResult(result, role, projectPath, reviewerConfig) : classification;
|
|
151
|
-
if (!resolvedClassification)
|
|
152
|
-
return null;
|
|
153
|
-
if (resolvedClassification.type === 'safety_violation' ||
|
|
154
|
-
resolvedClassification.type === 'policy_violation' ||
|
|
155
|
-
resolvedClassification.type === 'invalid_prompt') {
|
|
156
|
-
return {
|
|
157
|
-
type: resolvedClassification.type,
|
|
158
|
-
provider: providerName,
|
|
159
|
-
model: modelName,
|
|
160
|
-
message: resolvedClassification.message,
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
return null;
|
|
164
|
-
}
|
|
165
52
|
function summarizeErrorMessage(error) {
|
|
166
53
|
const raw = error instanceof Error ? error.message : String(error);
|
|
167
54
|
return raw.replace(/\s+/g, ' ').trim().slice(0, 220);
|
|
@@ -209,11 +96,34 @@ async function classifyOrchestratorFailure(error, projectPath) {
|
|
|
209
96
|
}
|
|
210
97
|
const MAX_ORCHESTRATOR_PARSE_RETRIES = 3;
|
|
211
98
|
const MAX_CONTRACT_VIOLATION_RETRIES = 3;
|
|
99
|
+
const MAX_PROVIDER_NONZERO_FAILURES = 3;
|
|
212
100
|
const CODER_PARSE_FALLBACK_MARKER = '[retry] FALLBACK: Orchestrator failed, defaulting to retry';
|
|
213
101
|
const REVIEWER_PARSE_FALLBACK_MARKER = '[unclear] FALLBACK: Orchestrator failed, retrying review';
|
|
214
102
|
const CONTRACT_CHECKLIST_MARKER = '[contract:checklist]';
|
|
215
103
|
const CONTRACT_REJECTION_RESPONSE_MARKER = '[contract:rejection_response]';
|
|
216
104
|
const MUST_IMPLEMENT_MARKER = '[must_implement]';
|
|
105
|
+
function formatProviderFailureMessage(taskId, context) {
|
|
106
|
+
const output = context.output || 'provider invocation failed with no output.';
|
|
107
|
+
return `Task ${taskId}: provider ${context.provider}/${context.model} exited with non-zero status ${context.exitCode} during ${context.role} phase: ${output}`;
|
|
108
|
+
}
|
|
109
|
+
async function handleProviderInvocationFailure(db, taskId, context, jsonMode) {
|
|
110
|
+
const failureCount = (0, queries_js_1.incrementTaskFailureCount)(db, taskId);
|
|
111
|
+
const providerMessage = formatProviderFailureMessage(taskId, context);
|
|
112
|
+
if (failureCount >= MAX_PROVIDER_NONZERO_FAILURES) {
|
|
113
|
+
const reason = `${providerMessage} (provider invocation failed ${failureCount} time(s). Task failed.)`;
|
|
114
|
+
(0, queries_js_1.updateTaskStatus)(db, taskId, 'failed', 'orchestrator', reason);
|
|
115
|
+
if (!jsonMode) {
|
|
116
|
+
console.log(`\n✗ Task failed (${reason})`);
|
|
117
|
+
}
|
|
118
|
+
return { shouldStopTask: true };
|
|
119
|
+
}
|
|
120
|
+
if (!jsonMode) {
|
|
121
|
+
const retriesLeft = MAX_PROVIDER_NONZERO_FAILURES - failureCount;
|
|
122
|
+
console.log(`\n⟳ Provider invocation failed (${failureCount}/${MAX_PROVIDER_NONZERO_FAILURES}) for task ${taskId}; retrying (${retriesLeft} attempt(s) left).`);
|
|
123
|
+
console.log(` ${providerMessage}`);
|
|
124
|
+
}
|
|
125
|
+
return { shouldStopTask: false };
|
|
126
|
+
}
|
|
217
127
|
function countConsecutiveOrchestratorFallbackEntries(db, taskId, marker) {
|
|
218
128
|
const audit = (0, queries_js_1.getTaskAudit)(db, taskId);
|
|
219
129
|
let count = 0;
|
|
@@ -408,51 +318,25 @@ async function runCoderPhase(db, task, projectPath, action, jsonMode = false, co
|
|
|
408
318
|
console.log('\n>>> Invoking CODER...\n');
|
|
409
319
|
}
|
|
410
320
|
const initialSha = (0, status_js_1.getCurrentCommitSha)(projectPath) || '';
|
|
411
|
-
const
|
|
321
|
+
const coderConfig = (0, loader_js_1.loadConfig)(projectPath).ai?.coder;
|
|
412
322
|
const coderResult = await (0, coder_js_1.invokeCoder)(task, projectPath, action, coordinatorGuidance, leaseFence?.runnerId);
|
|
413
|
-
if (coderResult.timedOut) {
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
(
|
|
425
|
-
return;
|
|
426
|
-
}
|
|
427
|
-
if (safetyViolation) {
|
|
428
|
-
// Check if we've exhausted our 2 attempts to resolve safety violations
|
|
429
|
-
const pastRejections = (0, queries_js_1.getTaskRejections)(db, task.id);
|
|
430
|
-
const safetyRejections = pastRejections.filter(r => r.notes?.includes('[safety_violation]')).length;
|
|
431
|
-
if (safetyRejections >= 2) {
|
|
432
|
-
const reason = `Task failed: Provider safety/policy violation could not be automatically resolved after 2 attempts. Error: ${safetyViolation.message}`;
|
|
433
|
-
(0, queries_js_1.updateTaskStatus)(db, task.id, 'disputed', 'orchestrator', reason);
|
|
434
|
-
if (!jsonMode) {
|
|
435
|
-
console.log(`\nTask disputed: Provider safety/policy violation could not be resolved.`);
|
|
436
|
-
}
|
|
323
|
+
if (coderResult.timedOut || !coderResult.success) {
|
|
324
|
+
const providerName = coderConfig?.provider ?? (0, loader_js_1.loadConfig)(projectPath).ai?.coder?.provider ?? 'unknown';
|
|
325
|
+
const modelName = coderConfig?.model ?? (0, loader_js_1.loadConfig)(projectPath).ai?.coder?.model ?? 'unknown';
|
|
326
|
+
const output = (coderResult.stderr || coderResult.stdout || '').trim();
|
|
327
|
+
const failed = await handleProviderInvocationFailure(db, task.id, {
|
|
328
|
+
role: 'coder',
|
|
329
|
+
provider: providerName,
|
|
330
|
+
model: modelName,
|
|
331
|
+
exitCode: coderResult.exitCode ?? 1,
|
|
332
|
+
output,
|
|
333
|
+
}, jsonMode);
|
|
334
|
+
if (failed.shouldStopTask) {
|
|
437
335
|
return;
|
|
438
336
|
}
|
|
439
|
-
// Inject the safety violation as a rejection so the orchestrator attempts to resolve it on the next loop
|
|
440
|
-
(0, queries_js_1.rejectTask)(db, task.id, `[safety_violation] The provider blocked the request due to a safety/policy violation: ${safetyViolation.message}\n\nPlease analyze the task description and previous outputs, and adjust your approach or prompt to comply with safety policies. You have ${2 - safetyRejections} attempt(s) remaining.`, 'orchestrator', 'coder');
|
|
441
|
-
if (!jsonMode) {
|
|
442
|
-
console.log(`\n⟳ Coder hit safety violation. Rejected back to orchestrator for automatic resolution (${safetyRejections + 1}/2 attempts).`);
|
|
443
|
-
}
|
|
444
|
-
return;
|
|
445
|
-
}
|
|
446
|
-
if (creditCheck) {
|
|
447
|
-
return creditCheck;
|
|
448
|
-
}
|
|
449
|
-
// Early bail: if coder returned empty output and failed, skip orchestrator and retry
|
|
450
|
-
if (!coderResult.success && coderResult.stdout.trim().length === 0) {
|
|
451
|
-
if (!jsonMode) {
|
|
452
|
-
console.log('\n⟳ Coder returned empty output (provider failure), will retry');
|
|
453
|
-
}
|
|
454
337
|
return;
|
|
455
338
|
}
|
|
339
|
+
(0, queries_js_1.clearTaskFailureCount)(db, task.id);
|
|
456
340
|
// STEP 2: Gather git state
|
|
457
341
|
const commits = (0, status_js_1.getRecentCommits)(projectPath, 5, initialSha);
|
|
458
342
|
const files_changed = (0, status_js_1.getChangedFiles)(projectPath);
|
|
@@ -820,7 +704,6 @@ async function runReviewerPhase(db, task, projectPath, jsonMode = false, coordin
|
|
|
820
704
|
const phaseConfig = (0, loader_js_1.loadConfig)(projectPath);
|
|
821
705
|
const multiReviewEnabled = (0, reviewer_js_1.isMultiReviewEnabled)(phaseConfig);
|
|
822
706
|
let effectiveMultiReviewEnabled = multiReviewEnabled;
|
|
823
|
-
const strict = phaseConfig.ai?.review?.strict ?? true;
|
|
824
707
|
let reviewerResult;
|
|
825
708
|
let reviewerResults = [];
|
|
826
709
|
// STEP 1: Invoke reviewer(s)
|
|
@@ -833,101 +716,34 @@ async function runReviewerPhase(db, task, projectPath, jsonMode = false, coordin
|
|
|
833
716
|
}
|
|
834
717
|
}
|
|
835
718
|
reviewerResults = await (0, reviewer_js_1.invokeReviewers)(task, projectPath, reviewerConfigs, coordinatorResult?.guidance, coordinatorResult?.decision, leaseFence?.runnerId);
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
const
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
(0, queries_js_1.updateTaskStatus)(db, task.id, 'disputed', 'orchestrator', reason);
|
|
858
|
-
if (!jsonMode) {
|
|
859
|
-
console.log(`\nTask disputed: Provider safety/policy violation could not be resolved.`);
|
|
860
|
-
}
|
|
861
|
-
return;
|
|
862
|
-
}
|
|
863
|
-
(0, queries_js_1.rejectTask)(db, task.id, `[safety_violation] The provider blocked the request due to a safety/policy violation: ${safetyViolation.message}\n\nPlease analyze the task description and previous outputs, and adjust your approach or prompt to comply with safety policies. You have ${2 - safetyRejections} attempt(s) remaining.`, 'orchestrator', 'reviewer');
|
|
864
|
-
if (!jsonMode) {
|
|
865
|
-
console.log(`\n⟳ Reviewer hit safety violation. Rejected back to orchestrator for automatic resolution (${safetyRejections + 1}/2 attempts).`);
|
|
866
|
-
}
|
|
867
|
-
return;
|
|
868
|
-
}
|
|
869
|
-
const creditCheck = await checkCreditExhaustion(res, 'reviewer', projectPath, reviewerConfigs[i], classification);
|
|
870
|
-
if (creditCheck) {
|
|
871
|
-
if (creditCheck.action === 'pause_credit_exhaustion' && !creditExhaustionSignal) {
|
|
872
|
-
creditExhaustionSignal = creditCheck;
|
|
873
|
-
}
|
|
874
|
-
if (creditCheck.action === 'rate_limit' && !rateLimitSignal) {
|
|
875
|
-
rateLimitSignal = creditCheck;
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
if (!res.success) {
|
|
879
|
-
if (classification?.type === 'rate_limit') {
|
|
880
|
-
failedRateLimitIndices.add(i);
|
|
881
|
-
}
|
|
882
|
-
else {
|
|
883
|
-
failedOtherIndices.add(i);
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
}
|
|
887
|
-
const successfulReviewers = reviewerResults.filter((res) => res.success);
|
|
888
|
-
const failedCount = reviewerResults.length - successfulReviewers.length;
|
|
889
|
-
const canDegradeToAvailableReviewers = successfulReviewers.length > 0 &&
|
|
890
|
-
failedCount > 0 &&
|
|
891
|
-
failedOtherIndices.size === 0 &&
|
|
892
|
-
failedRateLimitIndices.size === failedCount &&
|
|
893
|
-
!creditExhaustionSignal;
|
|
894
|
-
if (canDegradeToAvailableReviewers) {
|
|
895
|
-
const rateLimitedProviders = Array.from(failedRateLimitIndices)
|
|
896
|
-
.map((i) => reviewerConfigs[i])
|
|
897
|
-
.filter((cfg) => !!cfg)
|
|
898
|
-
.map((cfg) => `${cfg.provider}/${cfg.model}`)
|
|
899
|
-
.join(', ');
|
|
900
|
-
const strictLabel = strict ? ' (strict override)' : '';
|
|
901
|
-
const auditNote = `[reviewer_degraded] Falling back to ${successfulReviewers.length}/${reviewerResults.length} reviewer(s)` +
|
|
902
|
-
`${strictLabel}; rate-limited reviewers: ${rateLimitedProviders || 'unknown'}`;
|
|
903
|
-
(0, queries_js_1.addAuditEntry)(db, task.id, task.status, task.status, 'orchestrator', {
|
|
904
|
-
actorType: 'orchestrator',
|
|
905
|
-
notes: auditNote,
|
|
906
|
-
});
|
|
907
|
-
if (!jsonMode) {
|
|
908
|
-
console.warn(`! ${auditNote}`);
|
|
909
|
-
}
|
|
910
|
-
reviewerResults = successfulReviewers;
|
|
911
|
-
reviewerResult = reviewerResults[0];
|
|
912
|
-
effectiveMultiReviewEnabled = reviewerResults.length > 1;
|
|
913
|
-
}
|
|
914
|
-
else {
|
|
915
|
-
if (creditExhaustionSignal) {
|
|
916
|
-
return creditExhaustionSignal;
|
|
917
|
-
}
|
|
918
|
-
if (rateLimitSignal) {
|
|
919
|
-
return rateLimitSignal;
|
|
920
|
-
}
|
|
921
|
-
// Handle failures in strict mode or other unrecoverable failures
|
|
922
|
-
const failures = reviewerResults.filter(r => !r.success);
|
|
923
|
-
if (strict && failures.length > 0) {
|
|
924
|
-
if (!jsonMode) {
|
|
925
|
-
console.warn(`${failures.length} reviewer(s) failed in strict mode. Will retry.`);
|
|
926
|
-
}
|
|
719
|
+
const failedReviewerIndex = reviewerResults.findIndex((res) => !res.success || res.timedOut);
|
|
720
|
+
if (failedReviewerIndex !== -1) {
|
|
721
|
+
const failedReviewer = reviewerResults[failedReviewerIndex];
|
|
722
|
+
const failedConfig = reviewerConfigs[failedReviewerIndex];
|
|
723
|
+
const providerName = failedReviewer.provider ??
|
|
724
|
+
failedConfig?.provider ??
|
|
725
|
+
phaseConfig.ai?.reviewer?.provider ??
|
|
726
|
+
'unknown';
|
|
727
|
+
const modelName = failedReviewer.model ??
|
|
728
|
+
failedConfig?.model ??
|
|
729
|
+
phaseConfig.ai?.reviewer?.model ??
|
|
730
|
+
'unknown';
|
|
731
|
+
const output = (failedReviewer.stderr || failedReviewer.stdout || '').trim();
|
|
732
|
+
const failed = await handleProviderInvocationFailure(db, task.id, {
|
|
733
|
+
role: 'reviewer',
|
|
734
|
+
provider: providerName,
|
|
735
|
+
model: modelName,
|
|
736
|
+
exitCode: failedReviewer.exitCode ?? 1,
|
|
737
|
+
output,
|
|
738
|
+
}, jsonMode);
|
|
739
|
+
if (failed.shouldStopTask) {
|
|
927
740
|
return;
|
|
928
741
|
}
|
|
742
|
+
return;
|
|
929
743
|
}
|
|
930
|
-
|
|
744
|
+
(0, queries_js_1.clearTaskFailureCount)(db, task.id);
|
|
745
|
+
reviewerResult = reviewerResults[0];
|
|
746
|
+
effectiveMultiReviewEnabled = reviewerResults.length > 1;
|
|
931
747
|
}
|
|
932
748
|
else {
|
|
933
749
|
if (!jsonMode) {
|
|
@@ -937,49 +753,27 @@ async function runReviewerPhase(db, task, projectPath, jsonMode = false, coordin
|
|
|
937
753
|
}
|
|
938
754
|
}
|
|
939
755
|
reviewerResult = await (0, reviewer_js_1.invokeReviewer)(task, projectPath, coordinatorResult?.guidance, coordinatorResult?.decision, undefined, leaseFence?.runnerId);
|
|
940
|
-
if (reviewerResult.timedOut) {
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
const safetyRejections = pastRejections.filter(r => r.notes?.includes('[safety_violation]')).length;
|
|
957
|
-
if (safetyRejections >= 2) {
|
|
958
|
-
const reason = `Task failed: Provider safety/policy violation could not be automatically resolved after 2 attempts. Error: ${safetyViolation.message}`;
|
|
959
|
-
(0, queries_js_1.updateTaskStatus)(db, task.id, 'disputed', 'orchestrator', reason);
|
|
960
|
-
if (!jsonMode) {
|
|
961
|
-
console.log(`\nTask disputed: Provider safety/policy violation could not be resolved.`);
|
|
962
|
-
}
|
|
756
|
+
if (!reviewerResult.success || reviewerResult.timedOut) {
|
|
757
|
+
const providerName = reviewerResult.provider ??
|
|
758
|
+
phaseConfig.ai?.reviewer?.provider ??
|
|
759
|
+
'unknown';
|
|
760
|
+
const modelName = reviewerResult.model ??
|
|
761
|
+
phaseConfig.ai?.reviewer?.model ??
|
|
762
|
+
'unknown';
|
|
763
|
+
const output = (reviewerResult.stderr || reviewerResult.stdout || '').trim();
|
|
764
|
+
const failed = await handleProviderInvocationFailure(db, task.id, {
|
|
765
|
+
role: 'reviewer',
|
|
766
|
+
provider: providerName,
|
|
767
|
+
model: modelName,
|
|
768
|
+
exitCode: reviewerResult.exitCode ?? 1,
|
|
769
|
+
output,
|
|
770
|
+
}, jsonMode);
|
|
771
|
+
if (failed.shouldStopTask) {
|
|
963
772
|
return;
|
|
964
773
|
}
|
|
965
|
-
(0, queries_js_1.rejectTask)(db, task.id, `[safety_violation] The provider blocked the request due to a safety/policy violation: ${safetyViolation.message}\n\nPlease analyze the task description and previous outputs, and adjust your approach or prompt to comply with safety policies. You have ${2 - safetyRejections} attempt(s) remaining.`, 'orchestrator', 'reviewer');
|
|
966
|
-
if (!jsonMode) {
|
|
967
|
-
console.log(`\n⟳ Reviewer hit safety violation. Rejected back to orchestrator for automatic resolution (${safetyRejections + 1}/2 attempts).`);
|
|
968
|
-
}
|
|
969
|
-
return;
|
|
970
|
-
}
|
|
971
|
-
// Check for credit exhaustion
|
|
972
|
-
const creditCheck = await checkCreditExhaustion(reviewerResult, 'reviewer', projectPath, undefined, classification);
|
|
973
|
-
if (creditCheck) {
|
|
974
|
-
return creditCheck;
|
|
975
|
-
}
|
|
976
|
-
// Early bail: reviewer returned no output at all (provider crash/rate limit not caught above)
|
|
977
|
-
if (!reviewerResult.success && reviewerResult.stdout.trim().length === 0 && reviewerResult.stderr.trim().length === 0) {
|
|
978
|
-
if (!jsonMode) {
|
|
979
|
-
console.log('\n⟳ Reviewer returned no output (provider failure), will retry');
|
|
980
|
-
}
|
|
981
774
|
return;
|
|
982
775
|
}
|
|
776
|
+
(0, queries_js_1.clearTaskFailureCount)(db, task.id);
|
|
983
777
|
}
|
|
984
778
|
// STEP 2: Gather git context
|
|
985
779
|
const commit_sha = (0, status_js_1.getCurrentCommitSha)(projectPath) || '';
|