fix: address ds-review-bot v8 findings

- llm-deepseek: the uncatalogued resolveModel fallback declares text-only
  modalities — the wire route is text-only regardless of catalog
  membership, so "unknown" must not let the host persist-then-fail images.
- session.selectModel also consults the pending-inbox mirror: a queued
  image prompt enters the log only when claimed, after a switch would land.
- attachment store: ensureDurableDirectory syncs every ancestor entry up to
  a caller-vouched boundary regardless of what mkdir reports — a raced
  "already existed" is not "already durable".
- One image walker (imageBlockIn/imageInEvent) now serves both attachment
  authorization and the selection gate; referencedImage therefore also
  authorizes references inside wrapped message content.
- InputHub: the scope disposer resolves the conversation service optionally
  (teardown/HMR must reach quiescence), and a send failing after its scope
  died releases the in-flight drafts instead of restoring them onto a
  disposed shell.
- http-bridge destroys declared-oversize requests with connection: close
  instead of draining a body the client can trickle indefinitely.
- LlmService validates AND detaches modality arrays identically on the
  advisory and exact routes; READMEs record the fourth INVALID_MODEL_INFO
  rejection reason.
- CLI provider docs (JSDoc, README pair, Agent Note pair) describe the
  reuse behavior; llm-route.spec now parses the SHIPPED cordis.yml through
  the production extraction, pinning the row coupling.
- image-display lane pins gallery/rail shape in inline snapshots and the
  object-URL scheme this environment must take; stale host.schema comment
  dropped.
This commit is contained in:
creatixchu
2026-07-29 19:40:17 +08:00
parent adce3b833d
commit f73bf425ec
27 changed files with 337 additions and 100 deletions

View File

@@ -69,6 +69,34 @@ describe('ConversationService', () => {
await b.runtime.dispose()
})
it('releases in-flight send images when the scope dies before the failure lands', async () => {
const b = await bench()
const created = vi.spyOn(URL, 'createObjectURL').mockReturnValue('blob:inflight-1')
const revoked = vi.spyOn(URL, 'revokeObjectURL').mockReturnValue(undefined)
try {
const [attachment] = b.root.createDraftImages([new File([new Uint8Array(4)], 'b.png', { type: 'image/png' })])
if (attachment === undefined) throw new Error('draft attachment missing')
const shell = b.hub.shell(b.runtime.sessions.behavior('s1').sessionId)
shell.addImages([attachment.id])
let reject!: (error: Error) => void
b.prompt.mockReturnValueOnce(new Promise((_resolve, rej) => { reject = rej }) as never)
shell.setDraft('x')
shell.submit('queue')
// commitSend already removed the ids from the shell; kill the scope
// while the RPC is still pending, then land the failure.
await b.runtime.sessions.remove('s1')
reject(new Error('transport died'))
await vi.waitFor(() => {
expect(revoked).toHaveBeenCalledWith('blob:inflight-1')
})
expect(b.root.draftImages([attachment.id])).toEqual([])
} finally {
created.mockRestore()
revoked.mockRestore()
}
await b.runtime.dispose()
})
it('fails loudly from the root scope, on an unbound session, or without SessionsService', async () => {
const b = await bench()
await expect(b.root.send('x', 'queue')).rejects.toThrow(/requires a session scope/)