steroids-cli 0.10.16 → 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.
@@ -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
- * Check for policy or safety violations
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,EAaR,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAU9D,OAAO,EAA8C,KAAK,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AAqBpH,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAEtE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAE9D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAE/D,OAAO,EAAE,KAAK,iBAAiB,EAAE,CAAC;AAElC,UAAU,iBAAiB;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AA2CD;;GAEG;AACH,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;AAoHD;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,YAAY,EACpB,IAAI,EAAE,OAAO,GAAG,UAAU,EAC1B,WAAW,EAAE,MAAM,EACnB,cAAc,CAAC,EAAE,cAAc,EAC/B,cAAc,CAAC,EAAE,aAAa,GAAG,IAAI,GACpC,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CA4BpF;AAwMD,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,sBAAsB,GAAG,IAAI,CAAC,CA4lBxC;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,sBAAsB,GAAG,IAAI,CAAC,CAwiBxC"}
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 coderConfigSnapshot = (0, loader_js_1.loadConfig)(projectPath).ai?.coder;
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
- console.warn('Coder timed out. Will retry next iteration.');
415
- return;
416
- }
417
- const classification = await classifyProviderResult(coderResult, 'coder', projectPath, coderConfigSnapshot);
418
- const hardFailureFromKnownTypes = await checkNonRetryableProviderFailure(coderResult, 'coder', projectPath, coderConfigSnapshot, classification);
419
- const safetyViolation = await checkSafetyViolation(coderResult, 'coder', projectPath, coderConfigSnapshot, classification);
420
- // Check for credit/rate-limit classification (once).
421
- const creditCheck = await checkCreditExhaustion(coderResult, 'coder', projectPath, coderConfigSnapshot, classification);
422
- if (hardFailureFromKnownTypes) {
423
- const reason = `Task failed: provider ${hardFailureFromKnownTypes.provider}/${hardFailureFromKnownTypes.model} returned non-recoverable error (${hardFailureFromKnownTypes.type}) during coder phase: ${hardFailureFromKnownTypes.message}`;
424
- (0, queries_js_1.updateTaskStatus)(db, task.id, 'failed', 'orchestrator', reason);
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
- // Check for hard failures and classify all results
837
- const classifications = await Promise.all(reviewerResults.map((res, i) => classifyProviderResult(res, 'reviewer', projectPath, reviewerConfigs[i])));
838
- let creditExhaustionSignal;
839
- let rateLimitSignal;
840
- const failedRateLimitIndices = new Set();
841
- const failedOtherIndices = new Set();
842
- for (let i = 0; i < reviewerResults.length; i++) {
843
- const res = reviewerResults[i];
844
- const classification = classifications[i];
845
- const hardFailure = await checkNonRetryableProviderFailure(res, 'reviewer', projectPath, reviewerConfigs[i], classification);
846
- if (hardFailure) {
847
- const reason = `Task failed: provider ${hardFailure.provider}/${hardFailure.model} returned non-recoverable error (${hardFailure.type}) during reviewer phase: ${hardFailure.message}`;
848
- (0, queries_js_1.updateTaskStatus)(db, task.id, 'failed', 'orchestrator', reason);
849
- return;
850
- }
851
- const safetyViolation = await checkSafetyViolation(res, 'reviewer', projectPath, reviewerConfigs[i], classification);
852
- if (safetyViolation) {
853
- const pastRejections = (0, queries_js_1.getTaskRejections)(db, task.id);
854
- const safetyRejections = pastRejections.filter(r => r.notes?.includes('[safety_violation]')).length;
855
- if (safetyRejections >= 2) {
856
- const reason = `Task failed: Provider safety/policy violation could not be automatically resolved after 2 attempts. Error: ${safetyViolation.message}`;
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
- // If not strict, we continue with the successful ones (resolveDecision handles empty/unclear)
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
- if (!jsonMode) {
942
- console.warn('Reviewer timed out. Will retry next iteration.');
943
- }
944
- return;
945
- }
946
- const classification = await classifyProviderResult(reviewerResult, 'reviewer', projectPath);
947
- const hardFailure = await checkNonRetryableProviderFailure(reviewerResult, 'reviewer', projectPath, undefined, classification);
948
- if (hardFailure) {
949
- const reason = `Task failed: provider ${hardFailure.provider}/${hardFailure.model} returned non-recoverable error (${hardFailure.type}) during reviewer phase: ${hardFailure.message}`;
950
- (0, queries_js_1.updateTaskStatus)(db, task.id, 'failed', 'orchestrator', reason);
951
- return;
952
- }
953
- const safetyViolation = await checkSafetyViolation(reviewerResult, 'reviewer', projectPath, undefined, classification);
954
- if (safetyViolation) {
955
- const pastRejections = (0, queries_js_1.getTaskRejections)(db, task.id);
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) || '';