diff --git a/packages/context/agent-instructions/src/state.ts b/packages/context/agent-instructions/src/state.ts index efbdef6c81..73bac5a82a 100644 --- a/packages/context/agent-instructions/src/state.ts +++ b/packages/context/agent-instructions/src/state.ts @@ -188,7 +188,6 @@ function visibleInstructionChanges( const visibleSeqs = new Set(agent.session.surface.nodes) const visible = new Map() const folded = agent.ctx.get('sessionProjections')?.stateOf(agent.session, 'workspaceInstructions') - /* v8 ignore next -- agent-instructions registers its own workspaceInstructions unit, so the key is always present */ if (folded === undefined) throw new Error('workspaceInstructions projection is not registered') for (const [scope, history] of Object.entries(folded)) { // History is newest-first; the latest visible record restores the previous diff --git a/packages/context/time-context/src/index.ts b/packages/context/time-context/src/index.ts index 276b7fcfde..d41f9c27bb 100644 --- a/packages/context/time-context/src/index.ts +++ b/packages/context/time-context/src/index.ts @@ -188,14 +188,14 @@ export function apply(ctx: Context, config: Config): void { if (decision.kind === 'reject' || signal.aborted) return decision const now = Date.now() const state = ctx.sessionProjections.stateOf(agent.session, 'timeContext') - /* v8 ignore next -- time-context registers its own unit in apply, so the key is always present */ - if (state === undefined) throw new Error('timeContext projection is not registered') - if (refreshIntervalMs !== undefined && refreshIntervalMs > 0) { + if (state !== undefined && refreshIntervalMs !== undefined && refreshIntervalMs > 0) { const lastInjection = state.lastInjectionTime if (lastInjection != null && now >= lastInjection && now - lastInjection < refreshIntervalMs) return decision } + /* v8 ignore next 2 -- time-context registers its own unit in apply, so the key is always present */ + if (state === undefined) return decision /* v8 ignore next 6 -- an injection always records a time, so the lastInjectionTime nullish fallback is unreachable */ const previous = step === 1 ? state.lastMessageTime ?? undefined diff --git a/packages/interaction/user-approval/package.json b/packages/interaction/user-approval/package.json index ca34413924..3eb84ad80e 100644 --- a/packages/interaction/user-approval/package.json +++ b/packages/interaction/user-approval/package.json @@ -38,7 +38,6 @@ "license": "MIT", "peerDependencies": { "@deepseek-ai/dsh-agent": "workspace:^", - "@deepseek-ai/dsh-agent-loop": "workspace:^", "@deepseek-ai/dsh-brand": "workspace:^", "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", diff --git a/packages/interaction/user-approval/src/index.ts b/packages/interaction/user-approval/src/index.ts index c4132cbc59..51712ecc1c 100644 --- a/packages/interaction/user-approval/src/index.ts +++ b/packages/interaction/user-approval/src/index.ts @@ -282,14 +282,7 @@ export class ApprovalService extends Service { */ async request(req: ApprovalRequest): Promise { const session = req.agent.session - const boundary = this.ctx.sessionProjections.stateOf(session, 'turnBoundary') - if (boundary === undefined) { - throw new Error( - 'approval.request() requires the turnBoundary projection, which @deepseek-ai/dsh-agent-loop ' - + 'registers; mount the agent loop next to the projection registry', - ) - } - if (boundary.openTurn === null) { + if ((this.ctx.sessionProjections.stateOf(session, 'turnBoundary')?.openTurn ?? null) === null) { throw new Error( 'approval.request() outside an open turn: the approval/asked + approval/decided audit pair ' + 'must be turn-enclosed (a bare event between turns is crash-tail garbage on reload). ' diff --git a/packages/interaction/user-approval/tests/approval.spec.ts b/packages/interaction/user-approval/tests/approval.spec.ts index 618e7ee44c..906551f229 100644 --- a/packages/interaction/user-approval/tests/approval.spec.ts +++ b/packages/interaction/user-approval/tests/approval.spec.ts @@ -59,18 +59,6 @@ describe('ApprovalService.request', () => { expect(appended).toHaveLength(0) }) - it('names the missing agent loop when no turnBoundary unit is registered, even with turn events logged', async () => { - const ctx = new Context() - await ctx.plugin(SessionProjectionRegistry) - await ctx.plugin(ApprovalService) - const { agent, appended } = fakeAgent([ - { type: 'turn/start', seq: 0, data: { turn: 1 } }, - ]) - - await expect(ctx.approval.request(requestOf(agent))).rejects.toThrow(/requires the turnBoundary projection/) - expect(appended).toHaveLength(0) - }) - it('throws between turns — a closed turn does not satisfy the enclosure precondition', async () => { const ctx = await mounted() const { agent, appended } = fakeAgent([ diff --git a/packages/session/session-title/src/index.ts b/packages/session/session-title/src/index.ts index 4a6f8eb118..f7d6a553cb 100644 --- a/packages/session/session-title/src/index.ts +++ b/packages/session/session-title/src/index.ts @@ -712,10 +712,8 @@ export class SessionTitleService extends Service { } private titleInputOf(session: Session): TitleInputState { - const state = this.ctx.sessionProjections.stateOf(session, 'titleInput') /* v8 ignore next -- session-title registers its own titleInput unit, so the key is always present */ - if (state === undefined) throw new Error('titleInput projection is not registered') - return state + return this.ctx.sessionProjections.stateOf(session, 'titleInput') ?? EMPTY_TITLE_INPUT } /** Queue detached service work and retain it through service disposal. */