@zhixuan92/multi-model-agent-core 3.10.6 → 3.11.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/README.md +3 -3
- package/dist/config/schema.d.ts +15 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +17 -2
- package/dist/config/schema.js.map +1 -1
- package/dist/diagnostics/types.d.ts +11 -0
- package/dist/diagnostics/types.d.ts.map +1 -1
- package/dist/escalation/fallback.d.ts +14 -0
- package/dist/escalation/fallback.d.ts.map +1 -1
- package/dist/escalation/fallback.js +254 -19
- package/dist/escalation/fallback.js.map +1 -1
- package/dist/executors/audit.d.ts.map +1 -1
- package/dist/executors/audit.js +6 -4
- package/dist/executors/audit.js.map +1 -1
- package/dist/executors/debug.d.ts.map +1 -1
- package/dist/executors/debug.js +5 -3
- package/dist/executors/debug.js.map +1 -1
- package/dist/executors/delegate.d.ts +12 -0
- package/dist/executors/delegate.d.ts.map +1 -1
- package/dist/executors/delegate.js +45 -11
- package/dist/executors/delegate.js.map +1 -1
- package/dist/executors/execute-plan.d.ts.map +1 -1
- package/dist/executors/execute-plan.js +6 -4
- package/dist/executors/execute-plan.js.map +1 -1
- package/dist/executors/retry.js +1 -1
- package/dist/executors/retry.js.map +1 -1
- package/dist/executors/review.js +1 -1
- package/dist/executors/review.js.map +1 -1
- package/dist/executors/shared-compute.js +4 -4
- package/dist/executors/shared-compute.js.map +1 -1
- package/dist/executors/types.d.ts +1 -1
- package/dist/executors/types.d.ts.map +1 -1
- package/dist/executors/verify.js +2 -2
- package/dist/executors/verify.js.map +1 -1
- package/dist/heartbeat.d.ts +5 -5
- package/dist/heartbeat.d.ts.map +1 -1
- package/dist/heartbeat.js +21 -17
- package/dist/heartbeat.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/intake/compilers/audit.d.ts.map +1 -1
- package/dist/intake/compilers/audit.js +5 -2
- package/dist/intake/compilers/audit.js.map +1 -1
- package/dist/intake/compilers/debug.d.ts.map +1 -1
- package/dist/intake/compilers/debug.js +4 -0
- package/dist/intake/compilers/debug.js.map +1 -1
- package/dist/intake/compilers/delegate.d.ts +3 -0
- package/dist/intake/compilers/delegate.d.ts.map +1 -1
- package/dist/intake/compilers/delegate.js +5 -1
- package/dist/intake/compilers/delegate.js.map +1 -1
- package/dist/intake/compilers/execute-plan.d.ts.map +1 -1
- package/dist/intake/compilers/execute-plan.js +5 -0
- package/dist/intake/compilers/execute-plan.js.map +1 -1
- package/dist/intake/compilers/review.d.ts.map +1 -1
- package/dist/intake/compilers/review.js +3 -0
- package/dist/intake/compilers/review.js.map +1 -1
- package/dist/intake/compilers/verify.d.ts.map +1 -1
- package/dist/intake/compilers/verify.js +7 -0
- package/dist/intake/compilers/verify.js.map +1 -1
- package/dist/intake/force-clarification.d.ts +5 -0
- package/dist/intake/force-clarification.d.ts.map +1 -0
- package/dist/intake/force-clarification.js +44 -0
- package/dist/intake/force-clarification.js.map +1 -0
- package/dist/intake/pipeline.d.ts +1 -1
- package/dist/intake/pipeline.d.ts.map +1 -1
- package/dist/intake/pipeline.js +32 -1
- package/dist/intake/pipeline.js.map +1 -1
- package/dist/intake/resolve.d.ts.map +1 -1
- package/dist/intake/resolve.js +0 -1
- package/dist/intake/resolve.js.map +1 -1
- package/dist/observability/bus.d.ts.map +1 -1
- package/dist/observability/bus.js +20 -0
- package/dist/observability/bus.js.map +1 -1
- package/dist/observability/events.d.ts +81 -4
- package/dist/observability/events.d.ts.map +1 -1
- package/dist/observability/events.js +77 -2
- package/dist/observability/events.js.map +1 -1
- package/dist/provider.d.ts +1 -0
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +8 -1
- package/dist/provider.js.map +1 -1
- package/dist/review/diff-review.d.ts +1 -0
- package/dist/review/diff-review.d.ts.map +1 -1
- package/dist/review/diff-review.js +1 -0
- package/dist/review/diff-review.js.map +1 -1
- package/dist/review/quality-reviewer.d.ts +1 -1
- package/dist/review/quality-reviewer.d.ts.map +1 -1
- package/dist/review/quality-reviewer.js +6 -6
- package/dist/review/quality-reviewer.js.map +1 -1
- package/dist/review/spec-reviewer.d.ts +1 -1
- package/dist/review/spec-reviewer.d.ts.map +1 -1
- package/dist/review/spec-reviewer.js +3 -1
- package/dist/review/spec-reviewer.js.map +1 -1
- package/dist/routing/canonical-model-identity.d.ts +9 -0
- package/dist/routing/canonical-model-identity.d.ts.map +1 -0
- package/dist/routing/canonical-model-identity.js +54 -0
- package/dist/routing/canonical-model-identity.js.map +1 -0
- package/dist/run-tasks/execute-task.js +1 -1
- package/dist/run-tasks/execute-task.js.map +1 -1
- package/dist/run-tasks/index.js +1 -1
- package/dist/run-tasks/index.js.map +1 -1
- package/dist/run-tasks/reviewed-lifecycle.d.ts.map +1 -1
- package/dist/run-tasks/reviewed-lifecycle.js +145 -31
- package/dist/run-tasks/reviewed-lifecycle.js.map +1 -1
- package/dist/runners/base/result-builders.d.ts +13 -2
- package/dist/runners/base/result-builders.d.ts.map +1 -1
- package/dist/runners/base/result-builders.js +25 -1
- package/dist/runners/base/result-builders.js.map +1 -1
- package/dist/runners/base/time-check.d.ts +9 -0
- package/dist/runners/base/time-check.d.ts.map +1 -0
- package/dist/runners/base/time-check.js +18 -0
- package/dist/runners/base/time-check.js.map +1 -0
- package/dist/runners/base/usage-accumulator.d.ts +9 -0
- package/dist/runners/base/usage-accumulator.d.ts.map +1 -0
- package/dist/runners/base/usage-accumulator.js +19 -0
- package/dist/runners/base/usage-accumulator.js.map +1 -0
- package/dist/runners/claude-runner.d.ts.map +1 -1
- package/dist/runners/claude-runner.js +129 -175
- package/dist/runners/claude-runner.js.map +1 -1
- package/dist/runners/codex-runner.d.ts.map +1 -1
- package/dist/runners/codex-runner.js +96 -128
- package/dist/runners/codex-runner.js.map +1 -1
- package/dist/runners/error-classification.d.ts +11 -0
- package/dist/runners/error-classification.d.ts.map +1 -1
- package/dist/runners/error-classification.js +51 -0
- package/dist/runners/error-classification.js.map +1 -1
- package/dist/runners/openai-runner.d.ts.map +1 -1
- package/dist/runners/openai-runner.js +80 -171
- package/dist/runners/openai-runner.js.map +1 -1
- package/dist/runners/supervision.d.ts +0 -49
- package/dist/runners/supervision.d.ts.map +1 -1
- package/dist/runners/supervision.js +0 -67
- package/dist/runners/supervision.js.map +1 -1
- package/dist/runners/types.d.ts +12 -5
- package/dist/runners/types.d.ts.map +1 -1
- package/dist/telemetry/concern-classifier.d.ts +1 -1
- package/dist/telemetry/concern-classifier.d.ts.map +1 -1
- package/dist/telemetry/concern-classifier.js +5 -0
- package/dist/telemetry/concern-classifier.js.map +1 -1
- package/dist/telemetry/event-builder.d.ts.map +1 -1
- package/dist/telemetry/event-builder.js +5 -5
- package/dist/telemetry/event-builder.js.map +1 -1
- package/dist/telemetry/field-coverage.js +2 -2
- package/dist/telemetry/field-coverage.js.map +1 -1
- package/dist/telemetry/types.d.ts +139 -91
- package/dist/telemetry/types.d.ts.map +1 -1
- package/dist/telemetry/types.js +23 -17
- package/dist/telemetry/types.js.map +1 -1
- package/dist/types.d.ts +2 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +5 -2
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { execFile } from 'node:child_process';
|
|
2
2
|
import { promisify } from 'node:util';
|
|
3
|
-
import { computeCostUSD
|
|
3
|
+
import { computeCostUSD } from '../types.js';
|
|
4
4
|
import { createProvider } from '../provider.js';
|
|
5
5
|
import { delegateWithEscalation } from '../delegate-with-escalation.js';
|
|
6
6
|
import { pickEscalation, pickReviewer, maxRowsFor, } from '../escalation/policy.js';
|
|
7
7
|
import { runWithFallback, makeSyntheticRunResult, TRANSPORT_FAILURES, isReviewTransportFailure, } from '../escalation/fallback.js';
|
|
8
8
|
import { findModelCapabilities, findModelProfile } from '../routing/model-profiles.js';
|
|
9
|
+
import { canonicalIdentity } from '../routing/canonical-model-identity.js';
|
|
9
10
|
import { HeartbeatTimer } from '../heartbeat.js';
|
|
10
11
|
import { newStageIdleTracker, snapshotIdle } from './stage-idle-tracker.js';
|
|
11
|
-
import { DEFAULT_TASK_TIMEOUT_MS, DEFAULT_STALL_TIMEOUT_MS } from '../config/schema.js';
|
|
12
|
+
import { DEFAULT_TASK_TIMEOUT_MS, DEFAULT_STALL_TIMEOUT_MS, MAX_TIME_PRESTOP_RATIO } from '../config/schema.js';
|
|
12
13
|
import { runSpecReview } from '../review/spec-reviewer.js';
|
|
13
14
|
import { makeSkippedReviewResult } from '../review/skipped-result.js';
|
|
14
15
|
import { runQualityReview } from '../review/quality-reviewer.js';
|
|
@@ -201,6 +202,17 @@ export async function executeReviewedLifecycle(task, resolved, config, taskIndex
|
|
|
201
202
|
function providerFor(tier) {
|
|
202
203
|
return providers[tier];
|
|
203
204
|
}
|
|
205
|
+
// Compute the implementer's canonical identity for reviewer separation (R3).
|
|
206
|
+
// Used as forbiddenIdentities on reviewer fallback calls so the reviewer
|
|
207
|
+
// never lands on the same effective backend as the implementer.
|
|
208
|
+
const implementerIdentity = (() => {
|
|
209
|
+
try {
|
|
210
|
+
return canonicalIdentity(resolved.provider.config);
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
return undefined;
|
|
214
|
+
}
|
|
215
|
+
})();
|
|
204
216
|
// Partition filePaths into output targets before the worker runs.
|
|
205
217
|
// Output targets are paths that do not yet exist on disk.
|
|
206
218
|
const { outputTargets } = partitionFilePaths(task.filePaths, task.cwd ?? process.cwd());
|
|
@@ -216,17 +228,54 @@ export async function executeReviewedLifecycle(task, resolved, config, taskIndex
|
|
|
216
228
|
'task_done_summary',
|
|
217
229
|
'fallback', 'fallback_unavailable',
|
|
218
230
|
'escalation', 'escalation_unavailable',
|
|
219
|
-
'stall_abort', 'cost_check',
|
|
231
|
+
'stall_abort', 'cost_check', 'time_check',
|
|
220
232
|
]);
|
|
221
233
|
const shortBatchEarly = verboseBatchIdEarly ? verboseBatchIdEarly.slice(0, 8) : '????????';
|
|
222
234
|
const emitTaskEvent = (event, fields) => {
|
|
223
235
|
if (bus && verboseBatchIdEarly !== undefined) {
|
|
236
|
+
const schemaEvent = event === 'heartbeat_timer' ? 'task_started' : event;
|
|
224
237
|
const cleaned = {};
|
|
225
238
|
for (const [key, value] of Object.entries(fields)) {
|
|
226
239
|
if (value !== undefined)
|
|
227
240
|
cleaned[key] = value;
|
|
228
241
|
}
|
|
229
|
-
|
|
242
|
+
// Keep verbose-line field names stable while emitting schema-declared
|
|
243
|
+
// telemetry envelopes in their authoritative persisted shape. EventSchemas
|
|
244
|
+
// validate the full envelope at EventBus.emit in dev/test, so production
|
|
245
|
+
// emission paths must construct schema-shaped keys before persistence.
|
|
246
|
+
if (schemaEvent === 'task_started') {
|
|
247
|
+
cleaned.route = routeKey || 'delegate';
|
|
248
|
+
cleaned.cwd = task.cwd ?? process.cwd();
|
|
249
|
+
for (const key of ['state', 'stage_count', 'tick_ms', 'reason'])
|
|
250
|
+
delete cleaned[key];
|
|
251
|
+
}
|
|
252
|
+
if (event === 'verify_step') {
|
|
253
|
+
if ('exit_code' in cleaned) {
|
|
254
|
+
cleaned.exitCode = cleaned.exit_code;
|
|
255
|
+
delete cleaned.exit_code;
|
|
256
|
+
}
|
|
257
|
+
if ('duration_ms' in cleaned) {
|
|
258
|
+
cleaned.durationMs = cleaned.duration_ms;
|
|
259
|
+
delete cleaned.duration_ms;
|
|
260
|
+
}
|
|
261
|
+
if ('error_message' in cleaned) {
|
|
262
|
+
cleaned.errorMessage = cleaned.error_message;
|
|
263
|
+
delete cleaned.error_message;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (event === 'task_completed') {
|
|
267
|
+
if ('stages_json' in cleaned) {
|
|
268
|
+
cleaned.stages = cleaned.stages_json;
|
|
269
|
+
delete cleaned.stages_json;
|
|
270
|
+
}
|
|
271
|
+
if (!('cachedTokens' in cleaned))
|
|
272
|
+
cleaned.cachedTokens = null;
|
|
273
|
+
if (!('reasoningTokens' in cleaned))
|
|
274
|
+
cleaned.reasoningTokens = null;
|
|
275
|
+
if (!('stages' in cleaned))
|
|
276
|
+
cleaned.stages = JSON.stringify(stats);
|
|
277
|
+
}
|
|
278
|
+
bus.emit({ event: schemaEvent, ts: new Date().toISOString(), batchId: verboseBatchIdEarly, taskIndex, ...cleaned });
|
|
230
279
|
}
|
|
231
280
|
if (verboseStreamRaw && (verbose || DEFAULT_MODE_EVENTS.has(event))) {
|
|
232
281
|
verboseStreamRaw(composeVerboseLine({ event, ts: new Date().toISOString(), batch: shortBatchEarly, task: taskIndex, ...toVerboseFields(fields) }));
|
|
@@ -398,9 +447,11 @@ export async function executeReviewedLifecycle(task, resolved, config, taskIndex
|
|
|
398
447
|
}
|
|
399
448
|
if (event.kind === 'turn_complete') {
|
|
400
449
|
heartbeat?.markEvent('llm');
|
|
401
|
-
const
|
|
402
|
-
const
|
|
403
|
-
|
|
450
|
+
const providerConfig = _activeRunnerProviderConfig ?? resolved.provider.config;
|
|
451
|
+
const costUSD = computeCostUSD(event.cumulativeInputTokens, event.cumulativeOutputTokens, providerConfig);
|
|
452
|
+
_currentRunnerCostUSD = costUSD ?? 0;
|
|
453
|
+
const cumulativeCostUSD = (_completedRunnerCostUSD ?? 0) + _currentRunnerCostUSD;
|
|
454
|
+
heartbeat?.updateCost(cumulativeCostUSD, null);
|
|
404
455
|
const nowTurn = Date.now();
|
|
405
456
|
const turnDurMs = nowTurn - prevEventAtMs;
|
|
406
457
|
prevEventAtMs = nowTurn;
|
|
@@ -410,7 +461,7 @@ export async function executeReviewedLifecycle(task, resolved, config, taskIndex
|
|
|
410
461
|
output_tokens: event.cumulativeOutputTokens,
|
|
411
462
|
cost: costUSD,
|
|
412
463
|
duration_ms: turnDurMs,
|
|
413
|
-
provider:
|
|
464
|
+
provider: providerConfig.model,
|
|
414
465
|
});
|
|
415
466
|
}
|
|
416
467
|
}
|
|
@@ -422,7 +473,7 @@ export async function executeReviewedLifecycle(task, resolved, config, taskIndex
|
|
|
422
473
|
// any in-flight call gets a per-call timeoutMs clamped to remaining
|
|
423
474
|
// budget so it returns its salvage promptly. The user gets *something*
|
|
424
475
|
// back instead of an open-ended retry storm.
|
|
425
|
-
const taskTimeoutMs = task.timeoutMs ?? config.defaults
|
|
476
|
+
const taskTimeoutMs = task.timeoutMs ?? config.defaults?.timeoutMs ?? DEFAULT_TASK_TIMEOUT_MS;
|
|
426
477
|
const taskDeadlineMs = taskStartMs + taskTimeoutMs;
|
|
427
478
|
// Stall watchdog: when no LLM / tool / text event has fired for this
|
|
428
479
|
// many ms, the in-flight runner is force-aborted via `stallController`.
|
|
@@ -484,7 +535,48 @@ export async function executeReviewedLifecycle(task, resolved, config, taskIndex
|
|
|
484
535
|
const model = provider?.config.model ?? config.agents[tier]?.model ?? resolvedModel;
|
|
485
536
|
return { tier, family: modelFamily(model), model };
|
|
486
537
|
};
|
|
487
|
-
|
|
538
|
+
// §3.9: runningCostUSD must be cumulative and monotonic across explicit
|
|
539
|
+
// runner boundaries. Runner progress reports per-runner cumulative token
|
|
540
|
+
// counts, so lifecycle cost is completed runners + current runner partial.
|
|
541
|
+
// Boundaries are closed from actual RunResult.usage.costUSD values rather
|
|
542
|
+
// than inferred from drops; this handles reviewer costs greater than the
|
|
543
|
+
// implementer and preserves reviewer-provider pricing.
|
|
544
|
+
let _completedRunnerCostUSD = null;
|
|
545
|
+
let _currentRunnerCostUSD = 0;
|
|
546
|
+
let _activeRunnerProviderConfig = null;
|
|
547
|
+
let _prevRunningCost = null;
|
|
548
|
+
const runningCostUSD = () => {
|
|
549
|
+
const current = _completedRunnerCostUSD !== null || _currentRunnerCostUSD !== 0
|
|
550
|
+
? (_completedRunnerCostUSD ?? 0) + _currentRunnerCostUSD
|
|
551
|
+
: null;
|
|
552
|
+
if (process.env.NODE_ENV === 'test' || process.env.NODE_ENV === 'development') {
|
|
553
|
+
if (_prevRunningCost !== null && current !== null && current < _prevRunningCost) {
|
|
554
|
+
throw new Error(`runningCostUSD non-monotonic: prev=${_prevRunningCost} now=${current}`);
|
|
555
|
+
}
|
|
556
|
+
_prevRunningCost = current;
|
|
557
|
+
}
|
|
558
|
+
return current;
|
|
559
|
+
};
|
|
560
|
+
const runAccounted = async (provider, call) => {
|
|
561
|
+
if (_activeRunnerProviderConfig !== null) {
|
|
562
|
+
throw new Error('lifecycle cost accounting runner overlap');
|
|
563
|
+
}
|
|
564
|
+
_activeRunnerProviderConfig = provider.config;
|
|
565
|
+
_currentRunnerCostUSD = 0;
|
|
566
|
+
try {
|
|
567
|
+
const result = await call();
|
|
568
|
+
const actualCost = result?.usage?.costUSD
|
|
569
|
+
?? result?.metrics?.costUSD
|
|
570
|
+
?? _currentRunnerCostUSD;
|
|
571
|
+
_completedRunnerCostUSD = (_completedRunnerCostUSD ?? 0) + actualCost;
|
|
572
|
+
_currentRunnerCostUSD = 0;
|
|
573
|
+
heartbeat?.updateCost(_completedRunnerCostUSD, null);
|
|
574
|
+
return result;
|
|
575
|
+
}
|
|
576
|
+
finally {
|
|
577
|
+
_activeRunnerProviderConfig = null;
|
|
578
|
+
}
|
|
579
|
+
};
|
|
488
580
|
const policyEscalated = { spec: false, quality: false, diff: false };
|
|
489
581
|
const emitFallback = (p) => {
|
|
490
582
|
emitTaskEvent('fallback', p);
|
|
@@ -541,11 +633,21 @@ export async function executeReviewedLifecycle(task, resolved, config, taskIndex
|
|
|
541
633
|
...(fallbackOverrides.length > 0 ? { fallbackOverrides } : {}),
|
|
542
634
|
};
|
|
543
635
|
};
|
|
544
|
-
const abortReviewLoop = (base, terminationReason, message, aborting) => ({
|
|
636
|
+
const abortReviewLoop = (base, terminationReason, message, aborting, wallClockMs) => ({
|
|
545
637
|
...base,
|
|
546
638
|
status: 'incomplete',
|
|
547
639
|
workerStatus: 'review_loop_aborted',
|
|
548
|
-
terminationReason
|
|
640
|
+
terminationReason: terminationReason === 'round_cap'
|
|
641
|
+
? 'round_cap'
|
|
642
|
+
: {
|
|
643
|
+
cause: terminationReason === 'cost_ceiling' ? 'cost_exceeded' : 'time_ceiling',
|
|
644
|
+
turnsUsed: base.turns,
|
|
645
|
+
hasFileArtifacts: (base.filesWritten ?? []).length > 0,
|
|
646
|
+
usedShell: (base.toolCalls ?? []).some(c => c.startsWith('shell') || c.startsWith('runShell')),
|
|
647
|
+
workerSelfAssessment: 'review_loop_aborted',
|
|
648
|
+
wasPromoted: false,
|
|
649
|
+
...(wallClockMs !== undefined ? { wallClockMs } : {}),
|
|
650
|
+
},
|
|
549
651
|
reviewRounds: reviewRounds(),
|
|
550
652
|
error: message,
|
|
551
653
|
specReviewStatus: aborting === 'spec' ? 'changes_required' : (base.specReviewStatus ?? 'approved'),
|
|
@@ -562,7 +664,7 @@ export async function executeReviewedLifecycle(task, resolved, config, taskIndex
|
|
|
562
664
|
const verification = await runVerifyStage({
|
|
563
665
|
cwd,
|
|
564
666
|
verifyCommand: task.verifyCommand,
|
|
565
|
-
taskTimeoutMs: task.timeoutMs ?? config.defaults
|
|
667
|
+
taskTimeoutMs: task.timeoutMs ?? config.defaults?.timeoutMs ?? DEFAULT_TASK_TIMEOUT_MS,
|
|
566
668
|
taskStartMs,
|
|
567
669
|
});
|
|
568
670
|
latestVerification = verification;
|
|
@@ -589,7 +691,7 @@ export async function executeReviewedLifecycle(task, resolved, config, taskIndex
|
|
|
589
691
|
const cause = typeof result.terminationReason === 'object' ? result.terminationReason.cause : result.terminationReason;
|
|
590
692
|
const capExhausted = result.capExhausted
|
|
591
693
|
?? (result.status === 'cost_exceeded' || cause === 'cost_exceeded' || cause === 'cost_ceiling' ? 'cost'
|
|
592
|
-
: result.status === 'timeout' || cause === 'timeout' ? 'wall_clock'
|
|
694
|
+
: result.status === 'timeout' || cause === 'timeout' || cause === 'time_ceiling' ? 'wall_clock'
|
|
593
695
|
: result.status === 'incomplete' && result.turns > 1 ? 'turn'
|
|
594
696
|
: undefined);
|
|
595
697
|
const lifecycleClarificationRequested = result.lifecycleClarificationRequested
|
|
@@ -605,7 +707,7 @@ export async function executeReviewedLifecycle(task, resolved, config, taskIndex
|
|
|
605
707
|
return signalize({
|
|
606
708
|
output: '',
|
|
607
709
|
status: 'error',
|
|
608
|
-
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0, costUSD: null },
|
|
710
|
+
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0, costUSD: null, costDeltaVsParentUSD: null, cachedTokens: null, reasoningTokens: null },
|
|
609
711
|
turns: 0,
|
|
610
712
|
filesRead: [],
|
|
611
713
|
filesWritten: [],
|
|
@@ -831,7 +933,7 @@ export async function executeReviewedLifecycle(task, resolved, config, taskIndex
|
|
|
831
933
|
return withVerification({
|
|
832
934
|
output: `Sub-agent error: task.cwd ${cwd} had pre-existing modifications`,
|
|
833
935
|
status: 'error',
|
|
834
|
-
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0, costUSD: null },
|
|
936
|
+
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0, costUSD: null, costDeltaVsParentUSD: null, cachedTokens: null, reasoningTokens: null },
|
|
835
937
|
turns: 0,
|
|
836
938
|
filesRead: [],
|
|
837
939
|
filesWritten: [],
|
|
@@ -858,7 +960,7 @@ export async function executeReviewedLifecycle(task, resolved, config, taskIndex
|
|
|
858
960
|
isTransportFailure: (r) => TRANSPORT_FAILURES.has(r.status) && r.capExhausted === undefined,
|
|
859
961
|
getStatus: (r) => r.status,
|
|
860
962
|
makeSyntheticFailure: (assigned) => makeSyntheticRunResult(assigned, 'all_tiers_unavailable'),
|
|
861
|
-
call: (provider) => delegateWithEscalation(withDoneCondition(task), [provider], { explicitlyPinned: false, onProgress: wrappedOnProgress, taskDeadlineMs, abortSignal: stallController.signal, assignedTier: initialDecision.impl }),
|
|
963
|
+
call: (provider) => runAccounted(provider, () => delegateWithEscalation(withDoneCondition(task), [provider], { explicitlyPinned: false, onProgress: wrappedOnProgress, taskDeadlineMs, abortSignal: stallController.signal, assignedTier: initialDecision.impl })),
|
|
862
964
|
});
|
|
863
965
|
if (initialImpl.fallbackFired || initialImpl.bothUnavailable) {
|
|
864
966
|
fallbackOverrides.push({
|
|
@@ -1047,10 +1149,11 @@ export async function executeReviewedLifecycle(task, resolved, config, taskIndex
|
|
|
1047
1149
|
isTransportFailure: (r) => isReviewTransportFailure(r),
|
|
1048
1150
|
getStatus: (r) => r.status,
|
|
1049
1151
|
makeSyntheticFailure: () => makeSkippedReviewResult('all_tiers_unavailable'),
|
|
1050
|
-
|
|
1152
|
+
forbiddenIdentities: implementerIdentity ? [implementerIdentity] : undefined,
|
|
1153
|
+
call: (provider) => runAccounted(provider, () => runDiffReview({ cwd, diff: evidence.fullDiff, diffTruncated: evidence.diffTruncated, verification, worker: { call: (prompt, opts) => provider.run(prompt, { cwd: opts?.cwd ?? cwd, abortSignal: opts?.abortSignal, timeoutMs: opts?.timeoutMs }) }, taskDeadlineMs, abortSignal: stallController.signal })),
|
|
1051
1154
|
});
|
|
1052
1155
|
if (diffCall.fallbackFired) {
|
|
1053
|
-
emitFallback({ batchId: heartbeatWiring?.batchId ?? '', taskIndex, loop: 'diff', attempt: 0, role: 'diffReviewer', assignedTier: diffReviewerTier, usedTier: diffCall.usedTier, reason: diffCall.fallbackReason, triggeringStatus: diffCall.fallbackTriggeringStatus, violatesSeparation: diffCall.usedTier === implementerHistory[implementerHistory.length - 1] });
|
|
1156
|
+
emitFallback({ batchId: heartbeatWiring?.batchId ?? '', taskIndex, loop: 'diff', attempt: 0, role: 'diffReviewer', assignedTier: diffReviewerTier, usedTier: diffCall.usedTier, reason: diffCall.fallbackReason, triggeringStatus: diffCall.fallbackTriggeringStatus, violatesSeparation: diffCall.usedTier === implementerHistory[implementerHistory.length - 1], fallbackSeparationRespected: diffCall.fallbackSeparationRespected, assignedIdentity: diffCall.assignedIdentity ?? null, usedIdentity: diffCall.usedIdentity ?? null });
|
|
1054
1157
|
fallbackOverrides.push({ role: 'diffReviewer', loop: 'diff', attempt: 0, assigned: diffReviewerTier, used: diffCall.usedTier, reason: diffCall.fallbackReason, triggeringStatus: diffCall.fallbackTriggeringStatus, bothUnavailable: diffCall.bothUnavailable });
|
|
1055
1158
|
}
|
|
1056
1159
|
if (diffCall.bothUnavailable) {
|
|
@@ -1118,7 +1221,8 @@ export async function executeReviewedLifecycle(task, resolved, config, taskIndex
|
|
|
1118
1221
|
isTransportFailure: (r) => isReviewTransportFailure(r),
|
|
1119
1222
|
getStatus: (r) => r.status,
|
|
1120
1223
|
makeSyntheticFailure: () => makeSkippedReviewResult('all_tiers_unavailable'),
|
|
1121
|
-
|
|
1224
|
+
forbiddenIdentities: implementerIdentity ? [implementerIdentity] : undefined,
|
|
1225
|
+
call: (provider) => runAccounted(provider, () => runSpecReview(provider, packet, effectiveImplReport, fileContents, implResult.toolCalls, task.planContext, evidence.block, taskDeadlineMs, stallController.signal, wrappedOnProgress, cwd)),
|
|
1122
1226
|
});
|
|
1123
1227
|
specReviewDurationMs += Date.now() - initialSpecReviewIterStart;
|
|
1124
1228
|
if (initialSpecReview.bothUnavailable) {
|
|
@@ -1129,7 +1233,7 @@ export async function executeReviewedLifecycle(task, resolved, config, taskIndex
|
|
|
1129
1233
|
else {
|
|
1130
1234
|
specReviewerHistory.push(initialSpecReview.usedTier);
|
|
1131
1235
|
if (initialSpecReview.fallbackFired) {
|
|
1132
|
-
emitFallback({ batchId: heartbeatWiring?.batchId ?? '', taskIndex, loop: 'spec', attempt: 0, role: 'specReviewer', assignedTier: initialReviewerTier, usedTier: initialSpecReview.usedTier, reason: initialSpecReview.fallbackReason, triggeringStatus: initialSpecReview.fallbackTriggeringStatus, violatesSeparation: initialSpecReview.usedTier === implementerHistory[implementerHistory.length - 1] });
|
|
1236
|
+
emitFallback({ batchId: heartbeatWiring?.batchId ?? '', taskIndex, loop: 'spec', attempt: 0, role: 'specReviewer', assignedTier: initialReviewerTier, usedTier: initialSpecReview.usedTier, reason: initialSpecReview.fallbackReason, triggeringStatus: initialSpecReview.fallbackTriggeringStatus, violatesSeparation: initialSpecReview.usedTier === implementerHistory[implementerHistory.length - 1], fallbackSeparationRespected: initialSpecReview.fallbackSeparationRespected, assignedIdentity: initialSpecReview.assignedIdentity ?? null, usedIdentity: initialSpecReview.usedIdentity ?? null });
|
|
1133
1237
|
fallbackOverrides.push({ role: 'specReviewer', loop: 'spec', attempt: 0, assigned: initialReviewerTier, used: initialSpecReview.usedTier, reason: initialSpecReview.fallbackReason, triggeringStatus: initialSpecReview.fallbackTriggeringStatus, bothUnavailable: false });
|
|
1134
1238
|
}
|
|
1135
1239
|
}
|
|
@@ -1149,6 +1253,11 @@ export async function executeReviewedLifecycle(task, resolved, config, taskIndex
|
|
|
1149
1253
|
emitTaskEvent('cost_check', { stage: 'spec_rework', tripped: true, cost_used_usd: currentCostUSD, cost_cap_usd: maxCostUSD, cost_available: true });
|
|
1150
1254
|
return abortReviewLoop(finalImplResult, 'cost_ceiling', 'cost ceiling reached before spec rework', 'spec');
|
|
1151
1255
|
}
|
|
1256
|
+
const wallClock = Date.now() - taskStartMs;
|
|
1257
|
+
if (wallClock >= MAX_TIME_PRESTOP_RATIO * taskTimeoutMs) {
|
|
1258
|
+
emitTaskEvent('time_check', { stage: 'spec_rework', tripped: true, wallClockMs: wallClock, timeoutMs: taskTimeoutMs });
|
|
1259
|
+
return abortReviewLoop(finalImplResult, 'time_ceiling', `time ceiling reached before spec rework (${wallClock}ms >= 0.8 × ${taskTimeoutMs}ms)`, 'spec', wallClock);
|
|
1260
|
+
}
|
|
1152
1261
|
const decision = pickEscalation({ loop: 'spec', attemptIndex: specAttemptIndex, baseTier: resolved.slot });
|
|
1153
1262
|
if (decision.isEscalated)
|
|
1154
1263
|
emitEscalationEvent('spec', specAttemptIndex, decision);
|
|
@@ -1156,7 +1265,7 @@ export async function executeReviewedLifecycle(task, resolved, config, taskIndex
|
|
|
1156
1265
|
transitionStage('spec_review', 'spec_rework', { stage: 'spec_rework', stageIndex: 3, reviewRound: specAttemptIndex, attemptCap: maxSpecRows }, { attempt: specAttemptIndex, attemptCap: maxSpecRows, implTier: decision.impl, reviewerTier: decision.reviewer, escalated: decision.isEscalated });
|
|
1157
1266
|
const feedback = specResult.findings.length > 0 ? `\n\n## Spec Review Feedback (round ${specAttemptIndex}):\n${specResult.findings.map(f => `- ${f}`).join('\n')}` : '';
|
|
1158
1267
|
const reworkTask = withDoneCondition({ ...task, prompt: `${task.prompt}${feedback}` });
|
|
1159
|
-
const reworkCall = await runWithFallback({ assigned: decision.impl, providerFor, unavailableTiers: specUnavailable, isTransportFailure: (r) => TRANSPORT_FAILURES.has(r.status) && r.capExhausted === undefined, getStatus: (r) => r.status, makeSyntheticFailure: (assigned) => makeSyntheticRunResult(assigned, 'all_tiers_unavailable'), call: (provider) => delegateWithEscalation(reworkTask, [provider], { explicitlyPinned: true, onProgress: wrappedOnProgress, taskDeadlineMs, abortSignal: stallController.signal, assignedTier: decision.impl }) });
|
|
1268
|
+
const reworkCall = await runWithFallback({ assigned: decision.impl, providerFor, unavailableTiers: specUnavailable, isTransportFailure: (r) => TRANSPORT_FAILURES.has(r.status) && r.capExhausted === undefined, getStatus: (r) => r.status, makeSyntheticFailure: (assigned) => makeSyntheticRunResult(assigned, 'all_tiers_unavailable'), call: (provider) => runAccounted(provider, () => delegateWithEscalation(reworkTask, [provider], { explicitlyPinned: true, onProgress: wrappedOnProgress, taskDeadlineMs, abortSignal: stallController.signal, assignedTier: decision.impl })) });
|
|
1160
1269
|
if (reworkCall.fallbackFired || reworkCall.bothUnavailable)
|
|
1161
1270
|
fallbackOverrides.push({ role: 'implementer', loop: 'spec', attempt: specAttemptIndex, assigned: decision.impl, used: reworkCall.usedTier, reason: (reworkCall.fallbackReason ?? reworkCall.unavailableReason), triggeringStatus: reworkCall.fallbackTriggeringStatus, bothUnavailable: reworkCall.bothUnavailable });
|
|
1162
1271
|
if (reworkCall.fallbackFired) {
|
|
@@ -1180,7 +1289,7 @@ export async function executeReviewedLifecycle(task, resolved, config, taskIndex
|
|
|
1180
1289
|
commitReworkStage(stats, 'spec_rework', specReworkAcc, implementerAgentInfo);
|
|
1181
1290
|
transitionStage('spec_rework', 'spec_review', { stage: 'spec_review', stageIndex: 2, reviewRound: specAttemptIndex + 1, attemptCap: maxSpecRows }, null);
|
|
1182
1291
|
const reReviewIterStart = Date.now();
|
|
1183
|
-
const reviewCall = await runWithFallback({ assigned: decision.reviewer, providerFor, unavailableTiers: specUnavailable, isTransportFailure: (r) => isReviewTransportFailure(r), getStatus: (r) => r.status, makeSyntheticFailure: () => makeSkippedReviewResult('all_tiers_unavailable'), call: (provider) => runSpecReview(provider, packet, finalImplReport, fileContents, finalImplResult.toolCalls, task.planContext, evidence.block, taskDeadlineMs, stallController.signal, wrappedOnProgress) });
|
|
1292
|
+
const reviewCall = await runWithFallback({ assigned: decision.reviewer, providerFor, unavailableTiers: specUnavailable, isTransportFailure: (r) => isReviewTransportFailure(r), getStatus: (r) => r.status, makeSyntheticFailure: () => makeSkippedReviewResult('all_tiers_unavailable'), forbiddenIdentities: implementerIdentity ? [implementerIdentity] : undefined, call: (provider) => runAccounted(provider, () => runSpecReview(provider, packet, finalImplReport, fileContents, finalImplResult.toolCalls, task.planContext, evidence.block, taskDeadlineMs, stallController.signal, wrappedOnProgress, cwd)) });
|
|
1184
1293
|
specReviewDurationMs += Date.now() - reReviewIterStart;
|
|
1185
1294
|
if (reviewCall.bothUnavailable) {
|
|
1186
1295
|
emitFallbackUnavailable({ batchId: heartbeatWiring?.batchId ?? '', taskIndex, loop: 'spec', attempt: specAttemptIndex, role: 'specReviewer', assignedTier: decision.reviewer, reason: reviewCall.unavailableReason });
|
|
@@ -1190,7 +1299,7 @@ export async function executeReviewedLifecycle(task, resolved, config, taskIndex
|
|
|
1190
1299
|
else {
|
|
1191
1300
|
specReviewerHistory.push(reviewCall.usedTier);
|
|
1192
1301
|
if (reviewCall.fallbackFired) {
|
|
1193
|
-
emitFallback({ batchId: heartbeatWiring?.batchId ?? '', taskIndex, loop: 'spec', attempt: specAttemptIndex, role: 'specReviewer', assignedTier: decision.reviewer, usedTier: reviewCall.usedTier, reason: reviewCall.fallbackReason, triggeringStatus: reviewCall.fallbackTriggeringStatus, violatesSeparation: reviewCall.usedTier === implementerHistory[implementerHistory.length - 1] });
|
|
1302
|
+
emitFallback({ batchId: heartbeatWiring?.batchId ?? '', taskIndex, loop: 'spec', attempt: specAttemptIndex, role: 'specReviewer', assignedTier: decision.reviewer, usedTier: reviewCall.usedTier, reason: reviewCall.fallbackReason, triggeringStatus: reviewCall.fallbackTriggeringStatus, violatesSeparation: reviewCall.usedTier === implementerHistory[implementerHistory.length - 1], fallbackSeparationRespected: reviewCall.fallbackSeparationRespected, assignedIdentity: reviewCall.assignedIdentity ?? null, usedIdentity: reviewCall.usedIdentity ?? null });
|
|
1194
1303
|
fallbackOverrides.push({ role: 'specReviewer', loop: 'spec', attempt: specAttemptIndex, assigned: decision.reviewer, used: reviewCall.usedTier, reason: reviewCall.fallbackReason, triggeringStatus: reviewCall.fallbackTriggeringStatus, bothUnavailable: false });
|
|
1195
1304
|
}
|
|
1196
1305
|
}
|
|
@@ -1234,7 +1343,7 @@ export async function executeReviewedLifecycle(task, resolved, config, taskIndex
|
|
|
1234
1343
|
qualityReviewT0 = Date.now();
|
|
1235
1344
|
qualityReviewC0 = runningCostUSD();
|
|
1236
1345
|
const initialQualityIterStart = Date.now();
|
|
1237
|
-
const initialQuality = await runWithFallback({ assigned: qualityReviewerTier, providerFor, unavailableTiers: qualityUnavailable, isTransportFailure: (r) => isReviewTransportFailure(r), getStatus: (r) => r.status, makeSyntheticFailure: () => makeSkippedReviewResult('all_tiers_unavailable'), call: (provider) => runQualityReview(provider, packet, specReport ?? finalImplReport, fileContents, finalImplResult.toolCalls, finalImplResult.filesWritten, evidence.block, qualityReviewPromptBuilder, finalImplResult.output, taskDeadlineMs, stallController.signal, wrappedOnProgress) });
|
|
1346
|
+
const initialQuality = await runWithFallback({ assigned: qualityReviewerTier, providerFor, unavailableTiers: qualityUnavailable, isTransportFailure: (r) => isReviewTransportFailure(r), getStatus: (r) => r.status, makeSyntheticFailure: () => makeSkippedReviewResult('all_tiers_unavailable'), forbiddenIdentities: implementerIdentity ? [implementerIdentity] : undefined, call: (provider) => runAccounted(provider, () => runQualityReview(provider, packet, specReport ?? finalImplReport, fileContents, finalImplResult.toolCalls, finalImplResult.filesWritten, evidence.block, qualityReviewPromptBuilder, finalImplResult.output, taskDeadlineMs, stallController.signal, wrappedOnProgress, cwd)) });
|
|
1238
1347
|
qualityReviewDurationMs += Date.now() - initialQualityIterStart;
|
|
1239
1348
|
if (initialQuality.bothUnavailable) {
|
|
1240
1349
|
emitFallbackUnavailable({ batchId: heartbeatWiring?.batchId ?? '', taskIndex, loop: 'quality', attempt: 0, role: 'qualityReviewer', assignedTier: qualityReviewerTier, reason: initialQuality.unavailableReason });
|
|
@@ -1244,7 +1353,7 @@ export async function executeReviewedLifecycle(task, resolved, config, taskIndex
|
|
|
1244
1353
|
else {
|
|
1245
1354
|
qualityReviewerHistory.push(initialQuality.usedTier);
|
|
1246
1355
|
if (initialQuality.fallbackFired) {
|
|
1247
|
-
emitFallback({ batchId: heartbeatWiring?.batchId ?? '', taskIndex, loop: 'quality', attempt: 0, role: 'qualityReviewer', assignedTier: qualityReviewerTier, usedTier: initialQuality.usedTier, reason: initialQuality.fallbackReason, triggeringStatus: initialQuality.fallbackTriggeringStatus, violatesSeparation: initialQuality.usedTier === implementerHistory[implementerHistory.length - 1] });
|
|
1356
|
+
emitFallback({ batchId: heartbeatWiring?.batchId ?? '', taskIndex, loop: 'quality', attempt: 0, role: 'qualityReviewer', assignedTier: qualityReviewerTier, usedTier: initialQuality.usedTier, reason: initialQuality.fallbackReason, triggeringStatus: initialQuality.fallbackTriggeringStatus, violatesSeparation: initialQuality.usedTier === implementerHistory[implementerHistory.length - 1], fallbackSeparationRespected: initialQuality.fallbackSeparationRespected, assignedIdentity: initialQuality.assignedIdentity ?? null, usedIdentity: initialQuality.usedIdentity ?? null });
|
|
1248
1357
|
fallbackOverrides.push({ role: 'qualityReviewer', loop: 'quality', attempt: 0, assigned: qualityReviewerTier, used: initialQuality.usedTier, reason: initialQuality.fallbackReason, triggeringStatus: initialQuality.fallbackTriggeringStatus, bothUnavailable: false });
|
|
1249
1358
|
}
|
|
1250
1359
|
}
|
|
@@ -1288,8 +1397,6 @@ export async function executeReviewedLifecycle(task, resolved, config, taskIndex
|
|
|
1288
1397
|
: 'error',
|
|
1289
1398
|
iterationIndex: 1,
|
|
1290
1399
|
findingsReviewed: annotated.length,
|
|
1291
|
-
findingsFlagged: 0, // legacy field — severity correction tracked elsewhere now
|
|
1292
|
-
severityCorrections: 0, // reviewerSeverity field removed in 3.10.5
|
|
1293
1400
|
meanConfidence,
|
|
1294
1401
|
durationMs: Date.now() - qualityReviewT0,
|
|
1295
1402
|
costUSD: runningCostUSD() !== null && qualityReviewC0 !== null ? runningCostUSD() - qualityReviewC0 : null,
|
|
@@ -1307,6 +1414,11 @@ export async function executeReviewedLifecycle(task, resolved, config, taskIndex
|
|
|
1307
1414
|
emitTaskEvent('cost_check', { stage: 'quality_rework', tripped: true, cost_used_usd: currentCostUSD, cost_cap_usd: maxCostUSD, cost_available: true });
|
|
1308
1415
|
return abortReviewLoop(finalImplResult, 'cost_ceiling', 'cost ceiling reached before quality rework', 'quality');
|
|
1309
1416
|
}
|
|
1417
|
+
const wallClock = Date.now() - taskStartMs;
|
|
1418
|
+
if (wallClock >= MAX_TIME_PRESTOP_RATIO * taskTimeoutMs) {
|
|
1419
|
+
emitTaskEvent('time_check', { stage: 'quality_rework', tripped: true, wallClockMs: wallClock, timeoutMs: taskTimeoutMs });
|
|
1420
|
+
return abortReviewLoop(finalImplResult, 'time_ceiling', `time ceiling reached before quality rework (${wallClock}ms >= 0.8 × ${taskTimeoutMs}ms)`, 'quality', wallClock);
|
|
1421
|
+
}
|
|
1310
1422
|
const decision = pickEscalation({ loop: 'quality', attemptIndex: qualityAttemptIndex, baseTier: resolved.slot });
|
|
1311
1423
|
if (decision.isEscalated)
|
|
1312
1424
|
emitEscalationEvent('quality', qualityAttemptIndex, decision);
|
|
@@ -1314,7 +1426,7 @@ export async function executeReviewedLifecycle(task, resolved, config, taskIndex
|
|
|
1314
1426
|
transitionStage('quality_review', 'quality_rework', { stage: 'quality_rework', stageIndex: 5, reviewRound: qualityAttemptIndex, attemptCap: maxQualityRows }, { attempt: qualityAttemptIndex, attemptCap: maxQualityRows, implTier: decision.impl, reviewerTier: decision.reviewer, escalated: decision.isEscalated });
|
|
1315
1427
|
const feedback = qualityResult.findings.length > 0 ? `\n\n## Quality Review Feedback (round ${qualityAttemptIndex}):\n${qualityResult.findings.map(f => `- ${f}`).join('\n')}` : '';
|
|
1316
1428
|
const reworkTask = withDoneCondition({ ...task, prompt: `${task.prompt}${feedback}` });
|
|
1317
|
-
const reworkCall = await runWithFallback({ assigned: decision.impl, providerFor, unavailableTiers: qualityUnavailable, isTransportFailure: (r) => TRANSPORT_FAILURES.has(r.status) && r.capExhausted === undefined, getStatus: (r) => r.status, makeSyntheticFailure: (assigned) => makeSyntheticRunResult(assigned, 'all_tiers_unavailable'), call: (provider) => delegateWithEscalation(reworkTask, [provider], { explicitlyPinned: true, onProgress: wrappedOnProgress, taskDeadlineMs, abortSignal: stallController.signal, assignedTier: decision.impl }) });
|
|
1429
|
+
const reworkCall = await runWithFallback({ assigned: decision.impl, providerFor, unavailableTiers: qualityUnavailable, isTransportFailure: (r) => TRANSPORT_FAILURES.has(r.status) && r.capExhausted === undefined, getStatus: (r) => r.status, makeSyntheticFailure: (assigned) => makeSyntheticRunResult(assigned, 'all_tiers_unavailable'), call: (provider) => runAccounted(provider, () => delegateWithEscalation(reworkTask, [provider], { explicitlyPinned: true, onProgress: wrappedOnProgress, taskDeadlineMs, abortSignal: stallController.signal, assignedTier: decision.impl })) });
|
|
1318
1430
|
if (reworkCall.fallbackFired || reworkCall.bothUnavailable)
|
|
1319
1431
|
fallbackOverrides.push({ role: 'implementer', loop: 'quality', attempt: qualityAttemptIndex, assigned: decision.impl, used: reworkCall.usedTier, reason: (reworkCall.fallbackReason ?? reworkCall.unavailableReason), triggeringStatus: reworkCall.fallbackTriggeringStatus, bothUnavailable: reworkCall.bothUnavailable });
|
|
1320
1432
|
if (reworkCall.fallbackFired)
|
|
@@ -1335,7 +1447,7 @@ export async function executeReviewedLifecycle(task, resolved, config, taskIndex
|
|
|
1335
1447
|
commitReworkStage(stats, 'quality_rework', qualityReworkAcc, implementerAgentInfo);
|
|
1336
1448
|
transitionStage('quality_rework', 'quality_review', { stage: 'quality_review', stageIndex: 4, reviewRound: qualityAttemptIndex + 1, attemptCap: maxQualityRows }, null);
|
|
1337
1449
|
const qReReviewIterStart = Date.now();
|
|
1338
|
-
const reviewCall = await runWithFallback({ assigned: decision.reviewer, providerFor, unavailableTiers: qualityUnavailable, isTransportFailure: (r) => isReviewTransportFailure(r), getStatus: (r) => r.status, makeSyntheticFailure: () => makeSkippedReviewResult('all_tiers_unavailable'), call: (provider) => runQualityReview(provider, packet, finalImplReport, fileContents, finalImplResult.toolCalls, finalImplResult.filesWritten, evidence.block, qualityReviewPromptBuilder, finalImplResult.output, taskDeadlineMs, stallController.signal, wrappedOnProgress) });
|
|
1450
|
+
const reviewCall = await runWithFallback({ assigned: decision.reviewer, providerFor, unavailableTiers: qualityUnavailable, isTransportFailure: (r) => isReviewTransportFailure(r), getStatus: (r) => r.status, makeSyntheticFailure: () => makeSkippedReviewResult('all_tiers_unavailable'), forbiddenIdentities: implementerIdentity ? [implementerIdentity] : undefined, call: (provider) => runAccounted(provider, () => runQualityReview(provider, packet, finalImplReport, fileContents, finalImplResult.toolCalls, finalImplResult.filesWritten, evidence.block, qualityReviewPromptBuilder, finalImplResult.output, taskDeadlineMs, stallController.signal, wrappedOnProgress, cwd)) });
|
|
1339
1451
|
qualityReviewDurationMs += Date.now() - qReReviewIterStart;
|
|
1340
1452
|
if (reviewCall.bothUnavailable) {
|
|
1341
1453
|
emitFallbackUnavailable({ batchId: heartbeatWiring?.batchId ?? '', taskIndex, loop: 'quality', attempt: qualityAttemptIndex, role: 'qualityReviewer', assignedTier: decision.reviewer, reason: reviewCall.unavailableReason });
|
|
@@ -1345,7 +1457,7 @@ export async function executeReviewedLifecycle(task, resolved, config, taskIndex
|
|
|
1345
1457
|
else {
|
|
1346
1458
|
qualityReviewerHistory.push(reviewCall.usedTier);
|
|
1347
1459
|
if (reviewCall.fallbackFired) {
|
|
1348
|
-
emitFallback({ batchId: heartbeatWiring?.batchId ?? '', taskIndex, loop: 'quality', attempt: qualityAttemptIndex, role: 'qualityReviewer', assignedTier: decision.reviewer, usedTier: reviewCall.usedTier, reason: reviewCall.fallbackReason, triggeringStatus: reviewCall.fallbackTriggeringStatus, violatesSeparation: reviewCall.usedTier === implementerHistory[implementerHistory.length - 1] });
|
|
1460
|
+
emitFallback({ batchId: heartbeatWiring?.batchId ?? '', taskIndex, loop: 'quality', attempt: qualityAttemptIndex, role: 'qualityReviewer', assignedTier: decision.reviewer, usedTier: reviewCall.usedTier, reason: reviewCall.fallbackReason, triggeringStatus: reviewCall.fallbackTriggeringStatus, violatesSeparation: reviewCall.usedTier === implementerHistory[implementerHistory.length - 1], fallbackSeparationRespected: reviewCall.fallbackSeparationRespected, assignedIdentity: reviewCall.assignedIdentity ?? null, usedIdentity: reviewCall.usedIdentity ?? null });
|
|
1349
1461
|
fallbackOverrides.push({ role: 'qualityReviewer', loop: 'quality', attempt: qualityAttemptIndex, assigned: decision.reviewer, used: reviewCall.usedTier, reason: reviewCall.fallbackReason, triggeringStatus: reviewCall.fallbackTriggeringStatus, bothUnavailable: false });
|
|
1350
1462
|
}
|
|
1351
1463
|
}
|
|
@@ -1515,6 +1627,8 @@ export async function executeReviewedLifecycle(task, resolved, config, taskIndex
|
|
|
1515
1627
|
toolCalls: r.toolCalls?.length ?? 0,
|
|
1516
1628
|
inputTokens: r.usage.inputTokens,
|
|
1517
1629
|
outputTokens: r.usage.outputTokens,
|
|
1630
|
+
cachedTokens: r.usage.cachedTokens ?? null,
|
|
1631
|
+
reasoningTokens: r.usage.reasoningTokens ?? null,
|
|
1518
1632
|
costUSD: r.usage.costUSD,
|
|
1519
1633
|
taskMaxIdleMs: r.taskMaxIdleMs ?? null,
|
|
1520
1634
|
stallTriggered: r.stallTriggered ?? false,
|