Enforce 100% per-file test coverage on packages/*/src

vitest coverage (v8 provider) with per-file 100% thresholds for
statements, branches, functions, and lines. Scope: our runtime source
only — types-only files, vendor/ (upstream code), and examples/
(exercised by the demo smoke test) are excluded. yarn test:coverage
runs the gate.

59 tests added to close every gap: llm generate-waterfall and adapter
disposal; assembler edge protocol (duplicate block-start, stragglers
after block-end, id fallback, usage omission, invariant violation);
the whole Inbox surface incl. the wakeup-overwrite race; LoopAgent
disposed-state throws and double-stop idempotence; config-driven agent
creation; loop backstop catches (throwing turn-start/turn-end
listeners, non-Error throws, non-JSON tool arguments); system-prompt
dynamic sections and disposer paths; tools errorMessage fallbacks and
the full schema-DSL emission matrix. Genuinely unreachable defensive
guards carry /* v8 ignore */ comments with stated reasons rather than
deletion (132 tests total).
This commit is contained in:
Tianyi Cui
2026-06-11 14:58:36 +08:00
parent cb6bee3d03
commit bfb034830f
15 changed files with 1179 additions and 4 deletions

View File

@@ -114,6 +114,7 @@ async function runTurn(ctx: Context, agent: LoopAgent, handle: LoopHandle, turn:
// Drain queued messages into the session — they trigger this turn.
const queued = agent.inbox.drainQueued()
const first = queued[0]
/* v8 ignore next 3 -- invariant guard: runLoop only calls runTurn when hasQueued */
if (!first) throw new Error('runTurn invariant violated: no queued message at turn start')
const trigger: TurnTrigger = { kind: 'message', source: first.source }
for (const message of queued) {
@@ -158,6 +159,7 @@ async function runTurn(ctx: Context, agent: LoopAgent, handle: LoopHandle, turn:
if (handle.isDisposed()) {
reason = { kind: 'disposed' }
} else if (abort.signal.aborted) {
/* v8 ignore next -- abort.signal.reason always set by agent.abort() which provides a default */
reason = { kind: 'aborted', reason: String(abort.signal.reason ?? 'aborted') }
} else {
const coded = error as CodedError
@@ -196,6 +198,7 @@ async function runTurn(ctx: Context, agent: LoopAgent, handle: LoopHandle, turn:
if (!shouldContinue && agent.inbox.hasSteering) shouldContinue = true
if (!shouldContinue || handle.isDisposed()) {
/* v8 ignore next -- disposal during continuation-decision window is a narrow race; error-path disposal is covered elsewhere */
if (handle.isDisposed()) reason = { kind: 'disposed' }
break
}
@@ -256,6 +259,7 @@ async function runStep(
// --- Model call (streaming-first; raw chunks are the replay record) ---
const assembler = new BlockAssembler()
for await (const chunk of ctx.llm.stream(request)) {
/* v8 ignore next -- signal.reason always set by agent.abort() which provides a default */
if (signal.aborted) throw new Error(String(signal.reason ?? 'aborted'))
session.append('assistant/chunk', { turn, step, chunk })
ctx.emit('agent/stream-chunk', agent, turn, step, chunk)
@@ -278,6 +282,7 @@ async function runStep(
// isError results, so abort is re-checked around every call here.
const toolCalls = message.content.filter(block => block.type === 'tool-call')
for (const call of toolCalls) {
/* v8 ignore next -- signal.reason always set by agent.abort() which provides a default */
if (signal.aborted) throw new Error(String(signal.reason ?? 'aborted'))
session.append('tool/call', { turn, step, callId: call.id, name: call.name, arguments: call.arguments })
let parsedArguments: unknown
@@ -301,8 +306,12 @@ async function runStep(
})
// signal CAN flip during the await above (abort() inside a tool);
// the analyzer can't see through the await boundary.
// signal can flip during the await above (abort() inside a tool);
// the analyzer can't see through the await boundary.
/* v8 ignore start -- signal.reason default unreachable via agent.abort() */
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (signal.aborted) throw new Error(String(signal.reason ?? 'aborted'))
/* v8 ignore stop */
}
return { hadToolCalls: toolCalls.length > 0 }