agentxchain 2.107.0 → 2.109.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/bin/agentxchain.js +2 -0
- package/dashboard/app.js +46 -5
- package/dashboard/components/blocked.js +55 -0
- package/dashboard/components/gate.js +49 -0
- package/dashboard/components/timeline.js +100 -1
- package/dashboard/components/timeouts.js +21 -3
- package/package.json +1 -1
- package/src/commands/approve-completion.js +40 -1
- package/src/commands/approve-transition.js +44 -1
- package/src/commands/events.js +33 -1
- package/src/commands/status.js +50 -4
- package/src/commands/step.js +2 -0
- package/src/lib/dashboard/bridge-server.js +49 -0
- package/src/lib/dashboard/gate-action-reader.js +58 -0
- package/src/lib/dashboard/timeout-status.js +47 -18
- package/src/lib/gate-actions.js +232 -0
- package/src/lib/governed-state.js +178 -3
- package/src/lib/normalized-config.js +6 -1
- package/src/lib/notification-runner.js +162 -6
- package/src/lib/report.js +131 -3
- package/src/lib/run-events.js +1 -0
- package/src/lib/run-loop.js +50 -2
package/src/lib/report.js
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
} from './coordinator-next-actions.js';
|
|
12
12
|
import { buildCoordinatorRepoStatusEntries } from './coordinator-repo-status-presentation.js';
|
|
13
13
|
import { summarizeCoordinatorEvent } from './coordinator-event-narrative.js';
|
|
14
|
+
import { extractGateActionDigest } from './gate-actions.js';
|
|
14
15
|
|
|
15
16
|
export const GOVERNANCE_REPORT_VERSION = '0.1';
|
|
16
17
|
|
|
@@ -119,6 +120,34 @@ function yesNo(value) {
|
|
|
119
120
|
return value ? 'yes' : 'no';
|
|
120
121
|
}
|
|
121
122
|
|
|
123
|
+
function normalizeConflictingFiles(conflict) {
|
|
124
|
+
if (!conflict || typeof conflict !== 'object' || Array.isArray(conflict)) return [];
|
|
125
|
+
if (Array.isArray(conflict.conflicting_files)) {
|
|
126
|
+
return conflict.conflicting_files.filter((entry) => typeof entry === 'string' && entry.length > 0);
|
|
127
|
+
}
|
|
128
|
+
if (Array.isArray(conflict.files)) {
|
|
129
|
+
return conflict.files.filter((entry) => typeof entry === 'string' && entry.length > 0);
|
|
130
|
+
}
|
|
131
|
+
return [];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function normalizeAcceptedSinceTurnIds(conflict) {
|
|
135
|
+
if (!conflict || typeof conflict !== 'object' || Array.isArray(conflict)) return [];
|
|
136
|
+
if (Array.isArray(conflict.accepted_since_turn_ids)) {
|
|
137
|
+
return conflict.accepted_since_turn_ids.filter((entry) => typeof entry === 'string' && entry.length > 0);
|
|
138
|
+
}
|
|
139
|
+
if (Array.isArray(conflict.accepted_since)) {
|
|
140
|
+
return conflict.accepted_since
|
|
141
|
+
.map((entry) => {
|
|
142
|
+
if (typeof entry === 'string') return entry;
|
|
143
|
+
if (entry && typeof entry === 'object' && typeof entry.turn_id === 'string') return entry.turn_id;
|
|
144
|
+
return null;
|
|
145
|
+
})
|
|
146
|
+
.filter(Boolean);
|
|
147
|
+
}
|
|
148
|
+
return [];
|
|
149
|
+
}
|
|
150
|
+
|
|
122
151
|
function summarizeBlockedOn(blockedOn) {
|
|
123
152
|
if (!blockedOn) return 'none';
|
|
124
153
|
if (typeof blockedOn === 'string') return blockedOn;
|
|
@@ -143,6 +172,9 @@ function renderGovernanceEventDetailText(lines, evt, indent) {
|
|
|
143
172
|
if (evt.conflicting_files?.length > 0) {
|
|
144
173
|
lines.push(`${indent}files: ${evt.conflicting_files.join(', ')}`);
|
|
145
174
|
}
|
|
175
|
+
if (evt.accepted_since_turn_ids?.length > 0) {
|
|
176
|
+
lines.push(`${indent}accepted since: ${evt.accepted_since_turn_ids.join(', ')}`);
|
|
177
|
+
}
|
|
146
178
|
if (evt.overlap_ratio != null) {
|
|
147
179
|
lines.push(`${indent}overlap: ${(evt.overlap_ratio * 100).toFixed(0)}%`);
|
|
148
180
|
}
|
|
@@ -151,8 +183,23 @@ function renderGovernanceEventDetailText(lines, evt, indent) {
|
|
|
151
183
|
if (evt.conflicting_files?.length > 0) {
|
|
152
184
|
lines.push(`${indent}files: ${evt.conflicting_files.join(', ')}`);
|
|
153
185
|
}
|
|
186
|
+
if (evt.accepted_since_turn_ids?.length > 0) {
|
|
187
|
+
lines.push(`${indent}accepted since: ${evt.accepted_since_turn_ids.join(', ')}`);
|
|
188
|
+
}
|
|
189
|
+
if (evt.operator_reason) {
|
|
190
|
+
lines.push(`${indent}operator reason: ${evt.operator_reason}`);
|
|
191
|
+
}
|
|
154
192
|
break;
|
|
155
193
|
case 'conflict_resolution_selected':
|
|
194
|
+
if (evt.conflicting_files?.length > 0) {
|
|
195
|
+
lines.push(`${indent}files: ${evt.conflicting_files.join(', ')}`);
|
|
196
|
+
}
|
|
197
|
+
if (evt.accepted_since_turn_ids?.length > 0) {
|
|
198
|
+
lines.push(`${indent}accepted since: ${evt.accepted_since_turn_ids.join(', ')}`);
|
|
199
|
+
}
|
|
200
|
+
if (evt.overlap_ratio != null) {
|
|
201
|
+
lines.push(`${indent}overlap: ${(evt.overlap_ratio * 100).toFixed(0)}%`);
|
|
202
|
+
}
|
|
156
203
|
if (evt.resolution_method) {
|
|
157
204
|
lines.push(`${indent}resolution: ${evt.resolution_method}`);
|
|
158
205
|
}
|
|
@@ -179,6 +226,9 @@ function renderGovernanceEventDetailMarkdown(lines, evt) {
|
|
|
179
226
|
if (evt.conflicting_files?.length > 0) {
|
|
180
227
|
lines.push(` - Files: ${evt.conflicting_files.map((f) => `\`${f}\``).join(', ')}`);
|
|
181
228
|
}
|
|
229
|
+
if (evt.accepted_since_turn_ids?.length > 0) {
|
|
230
|
+
lines.push(` - Accepted since: ${evt.accepted_since_turn_ids.map((turnId) => `\`${turnId}\``).join(', ')}`);
|
|
231
|
+
}
|
|
182
232
|
if (evt.overlap_ratio != null) {
|
|
183
233
|
lines.push(` - Overlap: ${(evt.overlap_ratio * 100).toFixed(0)}%`);
|
|
184
234
|
}
|
|
@@ -187,8 +237,21 @@ function renderGovernanceEventDetailMarkdown(lines, evt) {
|
|
|
187
237
|
if (evt.conflicting_files?.length > 0) {
|
|
188
238
|
lines.push(` - Files: ${evt.conflicting_files.map((f) => `\`${f}\``).join(', ')}`);
|
|
189
239
|
}
|
|
240
|
+
if (evt.accepted_since_turn_ids?.length > 0) {
|
|
241
|
+
lines.push(` - Accepted since: ${evt.accepted_since_turn_ids.map((turnId) => `\`${turnId}\``).join(', ')}`);
|
|
242
|
+
}
|
|
243
|
+
if (evt.operator_reason) lines.push(` - Operator reason: ${evt.operator_reason}`);
|
|
190
244
|
break;
|
|
191
245
|
case 'conflict_resolution_selected':
|
|
246
|
+
if (evt.conflicting_files?.length > 0) {
|
|
247
|
+
lines.push(` - Files: ${evt.conflicting_files.map((f) => `\`${f}\``).join(', ')}`);
|
|
248
|
+
}
|
|
249
|
+
if (evt.accepted_since_turn_ids?.length > 0) {
|
|
250
|
+
lines.push(` - Accepted since: ${evt.accepted_since_turn_ids.map((turnId) => `\`${turnId}\``).join(', ')}`);
|
|
251
|
+
}
|
|
252
|
+
if (evt.overlap_ratio != null) {
|
|
253
|
+
lines.push(` - Overlap: ${(evt.overlap_ratio * 100).toFixed(0)}%`);
|
|
254
|
+
}
|
|
192
255
|
if (evt.resolution_method) lines.push(` - Resolution: \`${evt.resolution_method}\``);
|
|
193
256
|
break;
|
|
194
257
|
case 'operator_escalated':
|
|
@@ -449,6 +512,11 @@ function extractGateFailureDigest(artifact) {
|
|
|
449
512
|
}));
|
|
450
513
|
}
|
|
451
514
|
|
|
515
|
+
function extractGateActionEventDigest(artifact) {
|
|
516
|
+
const data = extractFileData(artifact, '.agentxchain/decision-ledger.jsonl');
|
|
517
|
+
return extractGateActionDigest(data);
|
|
518
|
+
}
|
|
519
|
+
|
|
452
520
|
const GOVERNANCE_EVENT_TYPES = new Set([
|
|
453
521
|
'policy_escalation',
|
|
454
522
|
'conflict_detected',
|
|
@@ -480,14 +548,21 @@ function extractGovernanceEventDigest(artifact, relPath = '.agentxchain/decision
|
|
|
480
548
|
})) : [];
|
|
481
549
|
break;
|
|
482
550
|
case 'conflict_detected':
|
|
483
|
-
base.conflicting_files =
|
|
551
|
+
base.conflicting_files = normalizeConflictingFiles(d.conflict);
|
|
552
|
+
base.accepted_since_turn_ids = normalizeAcceptedSinceTurnIds(d.conflict);
|
|
484
553
|
base.overlap_ratio = typeof d.conflict?.overlap_ratio === 'number' ? d.conflict.overlap_ratio : null;
|
|
485
554
|
break;
|
|
486
555
|
case 'conflict_rejected':
|
|
487
|
-
base.conflicting_files =
|
|
556
|
+
base.conflicting_files = normalizeConflictingFiles(d.conflict);
|
|
557
|
+
base.accepted_since_turn_ids = normalizeAcceptedSinceTurnIds(d.conflict);
|
|
558
|
+
base.overlap_ratio = typeof d.conflict?.overlap_ratio === 'number' ? d.conflict.overlap_ratio : null;
|
|
559
|
+
base.operator_reason = d.operator_reason || null;
|
|
488
560
|
break;
|
|
489
561
|
case 'conflict_resolution_selected':
|
|
490
|
-
base.
|
|
562
|
+
base.conflicting_files = normalizeConflictingFiles(d.conflict);
|
|
563
|
+
base.accepted_since_turn_ids = normalizeAcceptedSinceTurnIds(d.conflict);
|
|
564
|
+
base.overlap_ratio = typeof d.conflict?.overlap_ratio === 'number' ? d.conflict.overlap_ratio : null;
|
|
565
|
+
base.resolution_method = d.resolution_chosen || d.conflict?.resolution || null;
|
|
491
566
|
break;
|
|
492
567
|
case 'operator_escalated':
|
|
493
568
|
base.blocked_on = d.blocked_on || null;
|
|
@@ -917,6 +992,7 @@ function buildRunSubject(artifact) {
|
|
|
917
992
|
const decisions = extractDecisionDigest(artifact);
|
|
918
993
|
const approvalPolicyEvents = extractApprovalPolicyDigest(artifact);
|
|
919
994
|
const gateFailures = extractGateFailureDigest(artifact);
|
|
995
|
+
const gateActions = extractGateActionEventDigest(artifact);
|
|
920
996
|
const timeoutEvents = extractTimeoutEventDigest(artifact);
|
|
921
997
|
const hookSummary = extractHookSummary(artifact);
|
|
922
998
|
const timing = computeTiming(artifact, turns);
|
|
@@ -965,6 +1041,7 @@ function buildRunSubject(artifact) {
|
|
|
965
1041
|
approval_policy_events: approvalPolicyEvents,
|
|
966
1042
|
governance_events: governanceEvents,
|
|
967
1043
|
gate_failures: gateFailures,
|
|
1044
|
+
gate_actions: gateActions,
|
|
968
1045
|
timeout_events: timeoutEvents,
|
|
969
1046
|
delegation_summary: delegationSummary,
|
|
970
1047
|
hook_summary: hookSummary,
|
|
@@ -1362,6 +1439,18 @@ export function formatGovernanceReportText(report) {
|
|
|
1362
1439
|
}
|
|
1363
1440
|
}
|
|
1364
1441
|
|
|
1442
|
+
if (run.gate_actions && run.gate_actions.length > 0) {
|
|
1443
|
+
lines.push('', 'Gate Actions:');
|
|
1444
|
+
for (const action of run.gate_actions) {
|
|
1445
|
+
const label = action.action_label || action.command || `action ${action.action_index || '?'}`;
|
|
1446
|
+
const exit = action.exit_code == null ? 'n/a' : String(action.exit_code);
|
|
1447
|
+
lines.push(` - ${action.gate_id || 'unknown'} | ${action.gate_type || 'unknown'} | action ${action.action_index || '?'} | ${action.status} | ${label} | exit: ${exit} | at: ${action.timestamp || 'n/a'}`);
|
|
1448
|
+
if (action.stderr_tail) {
|
|
1449
|
+
lines.push(` stderr: ${action.stderr_tail}`);
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1365
1454
|
if (run.approval_policy_events && run.approval_policy_events.length > 0) {
|
|
1366
1455
|
lines.push('', 'Approval Policy:');
|
|
1367
1456
|
for (const evt of run.approval_policy_events) {
|
|
@@ -1920,6 +2009,18 @@ export function formatGovernanceReportMarkdown(report) {
|
|
|
1920
2009
|
}
|
|
1921
2010
|
}
|
|
1922
2011
|
|
|
2012
|
+
if (run.gate_actions && run.gate_actions.length > 0) {
|
|
2013
|
+
lines.push('', '## Gate Actions', '');
|
|
2014
|
+
for (const action of run.gate_actions) {
|
|
2015
|
+
const label = action.action_label || action.command || `action ${action.action_index || '?'}`;
|
|
2016
|
+
const exit = action.exit_code == null ? 'n/a' : String(action.exit_code);
|
|
2017
|
+
lines.push(`- \`${action.gate_id || 'unknown'}\` (${action.gate_type || 'unknown'}) action ${action.action_index || '?'} — **${action.status}** at \`${action.timestamp || 'n/a'}\`: ${label} (exit \`${exit}\`)`);
|
|
2018
|
+
if (action.stderr_tail) {
|
|
2019
|
+
lines.push(` - stderr: ${action.stderr_tail}`);
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
|
|
1923
2024
|
if (run.approval_policy_events && run.approval_policy_events.length > 0) {
|
|
1924
2025
|
lines.push('', '## Approval Policy', '');
|
|
1925
2026
|
for (const evt of run.approval_policy_events) {
|
|
@@ -2420,7 +2521,19 @@ function renderHtmlGovEventDetail(evt) {
|
|
|
2420
2521
|
break;
|
|
2421
2522
|
case 'conflict_detected':
|
|
2422
2523
|
if (evt.conflicting_files?.length > 0) parts.push(`<li>Files: ${evt.conflicting_files.map((f) => `<code>${esc(f)}</code>`).join(', ')}</li>`);
|
|
2524
|
+
if (evt.accepted_since_turn_ids?.length > 0) parts.push(`<li>Accepted since: ${evt.accepted_since_turn_ids.map((turnId) => `<code>${esc(turnId)}</code>`).join(', ')}</li>`);
|
|
2525
|
+
if (evt.overlap_ratio != null) parts.push(`<li>Overlap: ${(evt.overlap_ratio * 100).toFixed(0)}%</li>`);
|
|
2526
|
+
break;
|
|
2527
|
+
case 'conflict_rejected':
|
|
2528
|
+
if (evt.conflicting_files?.length > 0) parts.push(`<li>Files: ${evt.conflicting_files.map((f) => `<code>${esc(f)}</code>`).join(', ')}</li>`);
|
|
2529
|
+
if (evt.accepted_since_turn_ids?.length > 0) parts.push(`<li>Accepted since: ${evt.accepted_since_turn_ids.map((turnId) => `<code>${esc(turnId)}</code>`).join(', ')}</li>`);
|
|
2530
|
+
if (evt.operator_reason) parts.push(`<li>Operator reason: ${esc(evt.operator_reason)}</li>`);
|
|
2531
|
+
break;
|
|
2532
|
+
case 'conflict_resolution_selected':
|
|
2533
|
+
if (evt.conflicting_files?.length > 0) parts.push(`<li>Files: ${evt.conflicting_files.map((f) => `<code>${esc(f)}</code>`).join(', ')}</li>`);
|
|
2534
|
+
if (evt.accepted_since_turn_ids?.length > 0) parts.push(`<li>Accepted since: ${evt.accepted_since_turn_ids.map((turnId) => `<code>${esc(turnId)}</code>`).join(', ')}</li>`);
|
|
2423
2535
|
if (evt.overlap_ratio != null) parts.push(`<li>Overlap: ${(evt.overlap_ratio * 100).toFixed(0)}%</li>`);
|
|
2536
|
+
if (evt.resolution_method) parts.push(`<li>Resolution: <code>${esc(evt.resolution_method)}</code></li>`);
|
|
2424
2537
|
break;
|
|
2425
2538
|
case 'operator_escalated':
|
|
2426
2539
|
if (evt.reason) parts.push(`<li>Reason: ${esc(evt.reason)}</li>`);
|
|
@@ -2605,6 +2718,21 @@ function renderRunHtml(report) {
|
|
|
2605
2718
|
sections.push(`<div class="section">${htmlSection('Gate Failures', gfHtml)}</div>`);
|
|
2606
2719
|
}
|
|
2607
2720
|
|
|
2721
|
+
if (run.gate_actions && run.gate_actions.length > 0) {
|
|
2722
|
+
let gaHtml = '<ul>';
|
|
2723
|
+
for (const action of run.gate_actions) {
|
|
2724
|
+
const label = action.action_label || action.command || `action ${action.action_index || '?'}`;
|
|
2725
|
+
const exit = action.exit_code == null ? 'n/a' : String(action.exit_code);
|
|
2726
|
+
gaHtml += `<li><code>${esc(action.gate_id || 'unknown')}</code> (${esc(action.gate_type || 'unknown')}) action ${esc(String(action.action_index || '?'))} — <strong>${esc(action.status)}</strong> at <code>${esc(action.timestamp || 'n/a')}</code>: ${esc(label)} (exit <code>${esc(exit)}</code>)`;
|
|
2727
|
+
if (action.stderr_tail) {
|
|
2728
|
+
gaHtml += `<br><code>${esc(action.stderr_tail)}</code>`;
|
|
2729
|
+
}
|
|
2730
|
+
gaHtml += '</li>';
|
|
2731
|
+
}
|
|
2732
|
+
gaHtml += '</ul>';
|
|
2733
|
+
sections.push(`<div class="section">${htmlSection('Gate Actions', gaHtml)}</div>`);
|
|
2734
|
+
}
|
|
2735
|
+
|
|
2608
2736
|
// Approval Policy
|
|
2609
2737
|
if (run.approval_policy_events && run.approval_policy_events.length > 0) {
|
|
2610
2738
|
let apHtml = '<ul>';
|
package/src/lib/run-events.js
CHANGED
package/src/lib/run-loop.js
CHANGED
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
import { runAdmissionControl } from './admission-control.js';
|
|
36
36
|
import { mkdirSync, writeFileSync } from 'fs';
|
|
37
37
|
import { join, dirname } from 'path';
|
|
38
|
+
import { evaluateApprovalSlaReminders } from './notification-runner.js';
|
|
38
39
|
|
|
39
40
|
const DEFAULT_MAX_TURNS = 50;
|
|
40
41
|
|
|
@@ -340,6 +341,26 @@ async function executeParallelTurns(root, config, state, maxConcurrent, callback
|
|
|
340
341
|
const acceptResult = acceptTurn(root, config, { turnId: turn.turn_id });
|
|
341
342
|
if (!acceptResult.ok) {
|
|
342
343
|
errors.push(`acceptTurn(${roleId}): ${acceptResult.error}`);
|
|
344
|
+
|
|
345
|
+
// Conflict-aware handling (DEC-RUN-LOOP-CONFLICT-001)
|
|
346
|
+
if (acceptResult.error_code === 'conflict') {
|
|
347
|
+
history.push({
|
|
348
|
+
role: roleId, turn_id: turn.turn_id, accepted: false,
|
|
349
|
+
error_code: 'conflict', accept_error: acceptResult.error,
|
|
350
|
+
conflict: acceptResult.conflict,
|
|
351
|
+
});
|
|
352
|
+
emit({
|
|
353
|
+
type: 'turn_conflicted', turn, role: roleId,
|
|
354
|
+
error_code: 'conflict', conflict: acceptResult.conflict,
|
|
355
|
+
state: acceptResult.state,
|
|
356
|
+
});
|
|
357
|
+
if (acceptResult.state?.status === 'blocked') {
|
|
358
|
+
emit({ type: 'blocked', state: acceptResult.state });
|
|
359
|
+
return { terminal: true, ok: false, stop_reason: 'conflict_loop', history, acceptedCount };
|
|
360
|
+
}
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
|
|
343
364
|
// Record failure but try other turns
|
|
344
365
|
history.push({ role: roleId, turn_id: turn.turn_id, accepted: false, accept_error: acceptResult.error });
|
|
345
366
|
continue;
|
|
@@ -372,8 +393,10 @@ async function executeParallelTurns(root, config, state, maxConcurrent, callback
|
|
|
372
393
|
if (acceptedCount === 0 && history.length > 0) {
|
|
373
394
|
const allFailed = history.every(h => !h.accepted);
|
|
374
395
|
if (allFailed) {
|
|
375
|
-
|
|
376
|
-
|
|
396
|
+
const allConflicts = history.every(h => h.error_code === 'conflict');
|
|
397
|
+
const stopReason = allConflicts ? 'conflict_stall' : 'blocked';
|
|
398
|
+
errors.push(`All parallel turns failed acceptance — ${stopReason}`);
|
|
399
|
+
return { terminal: true, ok: false, stop_reason: stopReason, history, acceptedCount };
|
|
377
400
|
}
|
|
378
401
|
}
|
|
379
402
|
|
|
@@ -419,6 +442,29 @@ async function dispatchAndProcess(root, config, turn, assignState, callbacks, em
|
|
|
419
442
|
const acceptResult = acceptTurn(root, config);
|
|
420
443
|
if (!acceptResult.ok) {
|
|
421
444
|
errors.push(`acceptTurn(${roleId}): ${acceptResult.error}`);
|
|
445
|
+
|
|
446
|
+
// Conflict-aware handling (DEC-RUN-LOOP-CONFLICT-001)
|
|
447
|
+
if (acceptResult.error_code === 'conflict') {
|
|
448
|
+
history.push({
|
|
449
|
+
role: roleId, turn_id: turn.turn_id, accepted: false,
|
|
450
|
+
error_code: 'conflict', accept_error: acceptResult.error,
|
|
451
|
+
conflict: acceptResult.conflict,
|
|
452
|
+
});
|
|
453
|
+
emit({
|
|
454
|
+
type: 'turn_conflicted', turn, role: roleId,
|
|
455
|
+
error_code: 'conflict', conflict: acceptResult.conflict,
|
|
456
|
+
state: acceptResult.state,
|
|
457
|
+
});
|
|
458
|
+
// If the resulting state is blocked (conflict_loop), terminate
|
|
459
|
+
if (acceptResult.state?.status === 'blocked') {
|
|
460
|
+
emit({ type: 'blocked', state: acceptResult.state });
|
|
461
|
+
return { terminal: true, ok: false, stop_reason: 'conflict_loop', history };
|
|
462
|
+
}
|
|
463
|
+
// Otherwise the turn is conflicted but the run is still active — let the
|
|
464
|
+
// main loop re-enter and try another role or handle the paused state
|
|
465
|
+
return { terminal: false, accepted: false, history };
|
|
466
|
+
}
|
|
467
|
+
|
|
422
468
|
return { terminal: true, ok: false, stop_reason: 'blocked', history };
|
|
423
469
|
}
|
|
424
470
|
|
|
@@ -450,6 +496,8 @@ async function dispatchAndProcess(root, config, turn, assignState, callbacks, em
|
|
|
450
496
|
* Handle a paused state by checking for pending gates and calling approveGate.
|
|
451
497
|
*/
|
|
452
498
|
async function handleGatePause(root, config, state, callbacks, emit) {
|
|
499
|
+
evaluateApprovalSlaReminders(root, config, state);
|
|
500
|
+
|
|
453
501
|
if (state.pending_phase_transition) {
|
|
454
502
|
emit({ type: 'gate_paused', gateType: 'phase_transition', state });
|
|
455
503
|
let approved;
|