11import { monitorEventLoopDelay , performance } from "node:perf_hooks" ;
2+ import { resolveEmbeddedSessionLane } from "../agents/pi-embedded-runner/lanes.js" ;
23import { getRuntimeConfig } from "../config/config.js" ;
34import { resolveAllAgentSessionStoreTargetsSync } from "../config/sessions/targets.js" ;
45import type { OpenClawConfig } from "../config/types.openclaw.js" ;
@@ -9,6 +10,7 @@ import {
910 type DiagnosticPhaseSnapshot ,
1011 type DiagnosticLivenessWarningReason ,
1112} from "../infra/diagnostic-events.js" ;
13+ import { resetCommandLane } from "../process/command-queue.js" ;
1214import { emitDiagnosticMemorySample , resetDiagnosticMemoryForTest } from "./diagnostic-memory.js" ;
1315import {
1416 getCurrentDiagnosticPhase ,
@@ -737,6 +739,22 @@ export function logSessionStateChange(
737739 if ( params . state === "idle" ) {
738740 state . queueDepth = Math . max ( 0 , state . queueDepth - 1 ) ;
739741 state . activeQueuedTurn = false ;
742+ // Belt-and-suspenders: if the lane returns to idle but still has queued
743+ // items, force a pump. Normally `drainLane` re-fires recursively after
744+ // each task completes, but in production we have observed lanes that go
745+ // `idle` with `queueDepth > 0` and never dequeue (e.g., after an embedded
746+ // run ends with terminal progress). Calling `resetCommandLane` bumps the
747+ // lane generation, clears any stale `activeTaskIds`, and re-invokes
748+ // `drainLane` — a no-op when the lane queue is already empty.
749+ if ( state . queueDepth > 0 && state . sessionKey ) {
750+ try {
751+ resetCommandLane ( resolveEmbeddedSessionLane ( state . sessionKey ) ) ;
752+ } catch ( err ) {
753+ diag . warn (
754+ `lane-pump-on-idle failed: sessionKey=${ state . sessionKey } error="${ String ( err ) } "` ,
755+ ) ;
756+ }
757+ }
740758 }
741759 if ( ! isProbeSession && diag . isEnabled ( "debug" ) ) {
742760 diag . debug (
@@ -1116,6 +1134,14 @@ export function startDiagnosticHeartbeat(
11161134 thresholdMs : stuckSessionWarnMs ,
11171135 abortThresholdMs : stuckSessionAbortMs ,
11181136 } ) ;
1137+ const activeAbortRecoveryEligible =
1138+ classification !== undefined &&
1139+ isActiveAbortRecoveryEligible ( {
1140+ classification,
1141+ activity,
1142+ ageMs : attentionAgeMs ,
1143+ stuckSessionAbortMs,
1144+ } ) ;
11191145 if ( classification ?. recoveryEligible ) {
11201146 requestStuckSessionRecovery ( {
11211147 recover : opts ?. recoverStuckSession ?? recoverStuckSession ,
@@ -1125,19 +1151,12 @@ export function startDiagnosticHeartbeat(
11251151 sessionKey : state . sessionKey ,
11261152 ageMs : attentionAgeMs ,
11271153 queueDepth : state . queueDepth ,
1154+ ...( activeAbortRecoveryEligible ? { allowActiveAbort : true } : { } ) ,
11281155 expectedState : state . state ,
11291156 stateGeneration : state . generation ,
11301157 } ,
11311158 } ) ;
1132- } else if (
1133- classification &&
1134- isActiveAbortRecoveryEligible ( {
1135- classification,
1136- activity,
1137- ageMs : attentionAgeMs ,
1138- stuckSessionAbortMs,
1139- } )
1140- ) {
1159+ } else if ( classification && activeAbortRecoveryEligible ) {
11411160 requestStuckSessionRecovery ( {
11421161 recover : opts ?. recoverStuckSession ?? recoverStuckSession ,
11431162 classification,
0 commit comments