fix: address ds-review-bot v7 findings on the merged image-input head

- gate model selection on steering-placement image carriers from enqueue
  until their steering/message event publishes; release the gate when an
  admission ends idle without publication (both behaviorally asserted)
- reject session.updateQueue edits carrying non-text blocks at the RPC
  boundary (queue edits cannot bypass image admission)
- extend the durable-directory walk past a first-created DSH_HOME to the
  deepest pre-existing ancestor
- strip Windows-style separators from attachment display names on POSIX
- verify attachment reads with a header-only probe (digest already proves
  the bytes decoded fully at admission); document the read path
- make SessionInputShell.addImages refusal observable and keep workspace
  transfers/composer intake from leaking refused drafts
- own ONE recursive image walk (dsh-llm contentHasImage) across apiproxy,
  pi-ai, compact-basic, and the DeepSeek text-only assertion
- drop the redundant canonical-base64 regex and the no-op role read
- move AttachmentId/AttachmentError out of types.ts (brand.ts/error.ts);
  document why AttachmentError does not extend HarnessError
- document the hard attachments inject in both consumer READMEs
This commit is contained in:
creatixchu
2026-07-30 14:34:08 +08:00
parent 97cf33b7e0
commit 0d1250f743
37 files changed
+335 -140

No files matched your search

@@ -5,7 +5,7 @@
*/
import type { Context } from 'cordis'
import { createUserMessage, BlockAssembler, LlmError } from '@deepseek-ai/dsh-llm'
import { contentHasImage, createUserMessage, BlockAssembler, LlmError } from '@deepseek-ai/dsh-llm'
import type {
ContentBlock, FinishReason, GenerateOptions, Message, TokenUsage, ToolSchema,
} from '@deepseek-ai/dsh-llm'
@@ -205,14 +205,8 @@ function finishError(finish: FinishReason): Error | undefined {
function summaryText(
blocks: readonly ContentBlock[],
): Array<Extract<ContentBlock, { type: 'text' }>> {
if (containsImage(blocks)) {
if (contentHasImage(blocks)) {
throw new LlmError('compaction summary cannot contain image output', 'UNSUPPORTED_CONTENT')
}
return blocks.filter((block): block is Extract<ContentBlock, { type: 'text' }> => block.type === 'text')
}
/** Detect images recursively so no structured result can hide a silent visual drop. */
function containsImage(blocks: readonly ContentBlock[]): boolean {
return blocks.some(block => block.type === 'image'
|| (block.type === 'tool-result' && containsImage(block.content)))
}