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
@@ -87,11 +87,17 @@ export class InputHub implements InputService {
for (const off of offs) off()
// Draft attachments die with the scope: the shell only holds ids, so
// the service-owned File objects and object URLs must be released
// here or they leak for the page lifetime.
// here or they leak for the page lifetime. The lookup is optional —
// during application teardown or HMR of this plugin the root
// `conversation` service can already be unregistered while session
// scopes are still alive; a throwing disposer would abort teardown
// quiescence, and the service's own disposal effect revokes every
// remaining URL in that case anyway.
const drafts = shell.snapshot.imageIds
shell.dispose()
this.shells.delete(id)
for (const imageId of drafts) this.conversation().releaseDraftImage(imageId)
const conversation = this.rootCtx.get('conversation') as ConversationAttachmentFace | undefined
for (const imageId of drafts) conversation?.releaseDraftImage(imageId)
}
}, 'conversation.input: session shell')
return shell
@@ -139,8 +145,18 @@ export class InputHub implements InputService {
// Commit, not an editable clear: undo must not resurrect sent content.
shell?.commitSend(imageIds)
void this.conversation().sendSession(session, text, mode, imageIds).catch(() => {
shell?.restoreImages(imageIds)
if (shell?.snapshot.draft === '') shell.setDraft(text)
// Restore only into the shell that still owns the session: if the scope
// died while the send was in flight, `commitSend` already removed the
// ids from the (now disposed) shell, so the teardown release could not
// see them — release the drafts here instead of resurrecting them onto
// a dead instance where they would leak for the page lifetime.
if (this.shells.get(session.sessionId) === shell) {
shell?.restoreImages(imageIds)
if (shell?.snapshot.draft === '') shell.setDraft(text)
return
}
const conversation = this.rootCtx.get('conversation') as ConversationAttachmentFace | undefined
for (const id of imageIds) conversation?.releaseDraftImage(id)
})
}