Merge remote-tracking branch 'github/master' into xtr/session-log-read-api

# Conflicts:
#	.agents/notes/implemented/architecture/2026-07-25-web-client-session-scope-and-provide-channel.i18n.yaml
#	.agents/notes/implemented/architecture/2026-07-25-web-client-session-scope-and-provide-channel.md
#	.agents/notes/implemented/architecture/2026-07-25-web-client-session-scope-and-provide-channel.zh.md
#	.agents/notes/implemented/feature/2026-08-05-context-form-vocabulary.i18n.yaml
#	.agents/notes/implemented/feature/2026-08-05-context-form-vocabulary.md
#	.agents/notes/implemented/feature/2026-08-05-context-form-vocabulary.zh.md
#	packages/examples/agent-spine-demo/tests/agent-core.spec.ts
#	packages/skill/tool-skill/tests/tool-skill.spec.ts
This commit is contained in:
_Kerman
2026-08-31 17:19:59 +08:00
704 changed files with 6884 additions and 10494 deletions
@@ -27,6 +27,8 @@ export type PendingSubmissionRetirement =
/** Input registering one local submission echo ahead of its prompt call. */
export interface BeginSubmissionInput {
/** Delivery mode used with the upcoming prompt. */
readonly mode: 'queue' | 'steer'
/** Prompt text exactly as the upcoming prompt will send it. */
readonly text: string
/** Ordered image previews matching the upcoming prompt's image parts. */
@@ -30,6 +30,9 @@ export interface PendingSubmissionImage {
readonly height?: number
}
/** Client surface selected when a local submission begins. */
export type PendingSubmissionPlacement = 'transcript' | 'queued' | 'steering'
/**
* One local prompt-submission echo: inserted synchronously when a submission
* begins, so the conversation can show the message before serialization,
@@ -39,6 +42,8 @@ export interface PendingSubmissionImage {
export interface PendingSubmission {
/** The prompt RPC identity; the durable `user/message` source echoes it as `rpcId`. */
readonly requestId: SessionRequestId
/** Expected surface until the Host reports the admitted queue or durable occurrence. */
readonly placement: PendingSubmissionPlacement
/** Client wall-clock ms when the submission began. */
readonly time: number
/** Prompt text exactly as it will be sent (one text block). */
@@ -60,6 +60,7 @@ export type {
OpenState,
PendingSubmission,
PendingSubmissionImage,
PendingSubmissionPlacement,
PromptError,
QueuedMessage,
SessionSnapshot,
@@ -5,8 +5,11 @@ import type { QueuedMessage } from '../contract/snapshot.ts'
const QUEUE_PREVIEW_CHARS = 200
// Image blocks are excluded: queue presentation renders them as thumbnails
// from `content`, so the text preview covers only what has no visual form.
function previewOf(content: readonly ContentBlock[]): string {
const flat = content
.filter(block => block.type !== 'image')
.map(block => (block.type === 'text' ? block.text : `[${block.type}]`))
.join(' ').replace(/\s+/g, ' ').trim()
const chars = Array.from(flat)
@@ -184,6 +184,9 @@ export class Session implements SessionFace {
const requestId = randomUUID() as SessionRequestId
this.pendingSubmissions = [...this.pendingSubmissions, {
requestId,
placement: this.running
? input.mode === 'steer' ? 'steering' : 'queued'
: 'transcript',
time: Date.now(),
text: input.text,
images: input.images,
@@ -4,12 +4,12 @@ import { randomUUID } from 'node:crypto'
import type { Context } from '@deepseek-ai/cordis'
import { brandString } from '@deepseek-ai/dsh-brand'
import type { Agent, ModelSelection as AgentModelSelection } from '@deepseek-ai/dsh-agent'
import { AttachmentError, admitEncodedImages } from '@deepseek-ai/dsh-attachment'
import { AttachmentError, admitPromptContent } from '@deepseek-ai/dsh-attachment'
import type { ImageAttachmentRef } from '@deepseek-ai/dsh-attachment'
import {
ReasoningEffortId, createUserMessage, freezeMessage,
} from '@deepseek-ai/dsh-llm'
import type { ContentBlock, MessageSource } from '@deepseek-ai/dsh-llm'
import type { MessageSource } from '@deepseek-ai/dsh-llm'
import type { SessionEvent, SessionHeader, SessionId, UserMessage } from '@deepseek-ai/dsh-session'
import { SessionQueryError, type SessionObservation } from '@deepseek-ai/dsh-session-query'
import { SessionTitleInvalidError } from '@deepseek-ai/dsh-session-title'
@@ -319,7 +319,7 @@ export class SessionCommandController {
)
}
}
const content = await durablePromptContent(this.ctx, request.content)
const content = await admitPromptContent(this.ctx.attachments, request.content)
const message: UserMessage = createUserMessage({ content, source })
if (request.mode === 'steer') agent.steer(message)
else agent.followup(message)
@@ -492,21 +492,6 @@ export class SessionCommandController {
}
}
async function durablePromptContent(
ctx: Context,
content: readonly SessionPromptRequest['content'][number][],
): Promise<ContentBlock[]> {
if (content.every(part => part.type === 'text')) {
return content.map(part => ({ type: 'text', text: part.text }))
}
const refs = await admitEncodedImages(ctx.attachments, content.filter(part => part.type === 'image'))
let next = 0
return content.map(part => part.type === 'text'
? { type: 'text', text: part.text }
// admitEncodedImages returns one reference per image part in order.
: { type: 'image', attachment: refs[next++] as ImageAttachmentRef })
}
function imageBlockIn(
content: unknown,
match: (ref: ImageAttachmentRef) => boolean,