test: restore 100% per-file coverage for agent-loop and the acp bridge
agent-loop: behavior tests for retry-while-busy, cancelled recovery windows, no-facts stream failures, idle-listener preemption, rejected driver promises under whenIdle, finish-chunk failures after step close, presentationMeta persistence, pre-aborted and torn-down create/resume signals, and configured-start failures over existing artifacts or after teardown. The remaining guards that no public path can reach carry justified v8 ignore annotations naming the invariant that starves them. acp bridge: cover the retry-adoption path (a retry turn resolves the prompt the failed turn deferred), the no-retry quiescence rejection, and the admission-blocked cancelled settlement; the synchronous send-throw catch is annotated as a future-proofing guard since the machine's send() contains listener failures.
This commit is contained in:
6 files changed
+216
-3
No files matched your search
@@ -295,6 +295,9 @@ export class ReactLoopAgent implements Agent {
|
||||
if (outcome.continueTurn || this.outbox.some(item => 'id' in item)) continue
|
||||
break
|
||||
case 'request-failed': {
|
||||
// step() reports request failures only after step/start commits
|
||||
// and before its own step/end, so the step is always open here.
|
||||
/* v8 ignore next -- unreachable false arm, see above */
|
||||
if (this.stepOpen) {
|
||||
this.stepOpen = false
|
||||
this.session.append('step/end', { turn, step })
|
||||
@@ -315,6 +318,9 @@ export class ReactLoopAgent implements Agent {
|
||||
`agent "${this.id}": request recovery failed at turn ${turn}, step ${step}: ${errorChain(recoveryError)}`,
|
||||
)
|
||||
} finally {
|
||||
// Nothing else writes the window while the waterfall runs:
|
||||
// cancel() only flips `requested` and a second run cannot start.
|
||||
/* v8 ignore next -- unreachable false arm, see above */
|
||||
if (this.retryWindow === retryWindow) this.retryWindow = undefined
|
||||
}
|
||||
retry = recoveryCompleted
|
||||
@@ -342,6 +348,10 @@ export class ReactLoopAgent implements Agent {
|
||||
({ reason, idle } = this.settle(turn, step, caught, signal))
|
||||
} finally {
|
||||
try {
|
||||
// Every step-close before this point clears the flag on both success
|
||||
// and failure paths (step(), the request-failed branch, the catch),
|
||||
// so the finally never finds a step still open.
|
||||
/* v8 ignore next 4 -- unreachable last-resort step close, see above */
|
||||
if (this.stepOpen) {
|
||||
this.stepOpen = false
|
||||
this.session.append('step/end', { turn, step })
|
||||
@@ -357,6 +367,9 @@ export class ReactLoopAgent implements Agent {
|
||||
emitAgentEvent(this.loopCtx, this, 'agent/error', turn, step, error)
|
||||
}
|
||||
this.retryWindow = undefined
|
||||
// cancel() aborts but never clears the slot, and no second run can
|
||||
// install a controller while this one is still unwinding.
|
||||
/* v8 ignore next -- unreachable false arm, see above */
|
||||
if (this.abort === controller) this.abort = undefined
|
||||
signal.removeEventListener('abort', cancelRetry)
|
||||
}
|
||||
|
||||
@@ -328,6 +328,10 @@ export class AgentLoop extends Service implements AgentFactory {
|
||||
*/
|
||||
private prepare(ownerCtx: Context, id: SessionId, options: AgentOptions, session: Session, callerSignal?: AbortSignal): PreparedAgent {
|
||||
ownerCtx.fiber.assertActive()
|
||||
// Every caller reaches prepare() synchronously from a service method
|
||||
// whose Cordis dispatch already requires the live factory fiber, or
|
||||
// re-checks ownership itself after its awaits (resume's load barrier).
|
||||
/* v8 ignore next -- unreachable backstop, see above */
|
||||
if (!this.ownership.isActive()) throw new Error('agent loop is not active')
|
||||
if (callerSignal?.aborted) {
|
||||
throw callerSignal.reason instanceof Error
|
||||
@@ -393,15 +397,21 @@ export class AgentLoop extends Service implements AgentFactory {
|
||||
abort.abort(new Error(`agent "${id}" setup aborted: owner disposed during setup`))
|
||||
return dispose(true)
|
||||
}, `agentLoop.lifecycle(${id})`)
|
||||
/* v8 ignore start -- ctx.effect throws only on an inactive fiber, which assertActive() above already rejected */
|
||||
} catch (error: unknown) {
|
||||
untrack()
|
||||
callerSignal?.removeEventListener('abort', onCallerAbort)
|
||||
this.ownership.signal.removeEventListener('abort', onFactoryTeardown)
|
||||
throw error
|
||||
}
|
||||
/* v8 ignore stop */
|
||||
|
||||
const assertLive = (): void => {
|
||||
if (!abort.signal.aborted) return
|
||||
// Every fused abort source carries an Error reason: onCallerAbort and
|
||||
// raceAbort wrap non-Error caller reasons, and the factory/lifecycle
|
||||
// owners abort with constructed Errors.
|
||||
/* v8 ignore next -- unreachable String() arm, see above */
|
||||
throw abort.signal.reason instanceof Error ? abort.signal.reason : new Error(String(abort.signal.reason))
|
||||
}
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user