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:
_Kerman
2026-07-26 14:57:24 +08:00
parent fc8d339bf0
commit 6a068ec28d
6 files changed
+216 -3

No files matched your search

+13
View File
@@ -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)
}
+10
View File
@@ -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 {