diff --git a/packages/client/ui-conversation/src/client/chat/MessageItem.tsx b/packages/client/ui-conversation/src/client/chat/MessageItem.tsx
index 3c04422981..75e886f5ff 100644
--- a/packages/client/ui-conversation/src/client/chat/MessageItem.tsx
+++ b/packages/client/ui-conversation/src/client/chat/MessageItem.tsx
@@ -81,21 +81,38 @@ function projectUserText(text: string, sessionLabels: readonly string[] = []): R
return <>{parts}>
}
+function UserBubble({
+ text,
+ rest,
+ sessionLabels,
+ steering,
+}: {
+ text: string
+ rest: readonly unknown[]
+ sessionLabels: readonly string[]
+ steering?: boolean
+}): ReactNode {
+ return (
+
+
+ {steering === true ? 插话 : null}
+ {projectUserText(text, sessionLabels)}
+ {rest.map((block, i) => )}
+
+ {sessionLabels.length > 0
+ ?
引用会话 · {sessionLabels.join(', ')}
+ : null}
+
+ )
+}
+
export const MessageItem = memo(function MessageItem({ node, sessionLabels = [] }: MessageItemProps) {
switch (node.kind) {
case 'user': {
const { text, rest } = contentText(node.content)
return (
-
-
- {projectUserText(text, sessionLabels)}
- {rest.map((block, i) => )}
-
- {sessionLabels.length > 0
- ?
引用会话 · {sessionLabels.join(', ')}
- : null}
-
+
-
-
- 插话
- {projectUserText(text, sessionLabels)}
- {rest.map((block, i) => )}
-
- {sessionLabels.length > 0
- ?
引用会话 · {sessionLabels.join(', ')}
- : null}
-
+
)
}
diff --git a/packages/client/ui-conversation/src/client/input/facade.ts b/packages/client/ui-conversation/src/client/input/facade.ts
index 85dc945a77..f984f9ba89 100644
--- a/packages/client/ui-conversation/src/client/input/facade.ts
+++ b/packages/client/ui-conversation/src/client/input/facade.ts
@@ -348,7 +348,7 @@ export class SessionInputShell implements SessionInput {
private sinkSerialized(attempt: SubmitAttempt, draft: string, mode: 'queue' | 'steer'): void {
const occurrences = this.core.state.occurrences
if (occurrences.length === 0) {
- this.settleDefault(attempt, this.deps.defaultSink(draft.trim(), mode, attempt.signal))
+ this.settleSubmit(attempt, this.deps.defaultSink(draft.trim(), mode, attempt.signal))
return
}
const slash = this.deps.slash?.()
@@ -368,7 +368,7 @@ export class SessionInputShell implements SessionInput {
cursor = part.offset + 1
}
out += draft.slice(cursor)
- this.settleDefault(attempt, this.deps.defaultSink(out.trim(), mode, attempt.signal))
+ this.settleSubmit(attempt, this.deps.defaultSink(out.trim(), mode, attempt.signal))
},
(error: unknown) => {
controller.abort()
@@ -384,7 +384,7 @@ export class SessionInputShell implements SessionInput {
)
}
- private settleDefault(
+ private settleSubmit(
attempt: SubmitAttempt,
pending: Promise,
): void {
@@ -433,21 +433,10 @@ export class SessionInputShell implements SessionInput {
/** The submit transaction: claim.submit against the session scope; ok maps from the outcome kind. */
private beginSubmit(attempt: SubmitAttempt, claim: CommandClaim, args: string): void {
- Promise.resolve()
- .then(() => claim.submit(args, this.deps.actx))
- .then(
- (outcome) => {
- if (this.dead(attempt)) return
- this.run(this.core.dispatch({
- type: 'submit-settled', attempt, ok: outcome.kind === 'success', outcome,
- }))
- },
- (error: unknown) => {
- if (this.dead(attempt)) return
- const message = error instanceof Error ? error.message : String(error)
- this.run(this.core.dispatch({ type: 'submit-settled', attempt, ok: false, message }))
- },
- )
+ this.settleSubmit(
+ attempt,
+ Promise.resolve().then(() => claim.submit(args, this.deps.actx)),
+ )
}
/** Late-settlement guard: superseded attempts and disposed facades drop silently. */
diff --git a/packages/host/apiproxy/src/api-proxy.ts b/packages/host/apiproxy/src/api-proxy.ts
index 8329fae188..ee521e74ce 100644
--- a/packages/host/apiproxy/src/api-proxy.ts
+++ b/packages/host/apiproxy/src/api-proxy.ts
@@ -9,7 +9,7 @@ import { join } from 'node:path'
import type { Context } from 'cordis'
import { installAgentLlmTarget } from '@deepseek-ai/dsh-agent'
import type {
- Agent, AgentLlmTarget, AgentLlmTargetRef, AgentStatus, InboxPlacement,
+ Agent, AgentLlmTarget, AgentLlmTargetRef, AgentStatus, InboxPlacement, PromptDecision,
} from '@deepseek-ai/dsh-agent'
import { createUserMessage, ReasoningEffortId } from '@deepseek-ai/dsh-llm'
import { errorChain } from '@deepseek-ai/dsh-llm'
@@ -458,6 +458,22 @@ function changedWorkspaceView(workspaceId: string, value: unknown): WorkspaceVie
}
}
+async function appendPreparedPromptContext(
+ matchesPrompt: boolean,
+ additionalContext: UserMessage,
+ cleanup: () => void,
+ next: () => Promise,
+): Promise {
+ if (!matchesPrompt) return next()
+ cleanup()
+ const decision = await next()
+ if (decision.kind !== 'allow') return decision
+ return {
+ ...decision,
+ additionalContexts: [...decision.additionalContexts ?? [], additionalContext],
+ }
+}
+
/**
* Deliver a prepared browser prompt while preserving session-reference
* admission ownership under the context-injection contract.
@@ -490,16 +506,13 @@ function deliverPrompt(
detachSubmit()
detachDiscard()
}
- detachSubmit = ctx.on('agent/prompt-submit', async (subject, submitted, _signal, next) => {
- if (subject !== agent || submitted.id !== message.id) return next()
- cleanup()
- const decision = await next()
- if (decision.kind !== 'allow') return decision
- return {
- ...decision,
- additionalContexts: [...decision.additionalContexts ?? [], additionalContext],
- }
- }, { prepend: true })
+ detachSubmit = ctx.on('agent/prompt-submit', (subject, submitted, _signal, next) =>
+ appendPreparedPromptContext(
+ subject === agent && submitted.id === message.id,
+ additionalContext,
+ cleanup,
+ next,
+ ), { prepend: true })
detachDiscard = ctx.on('agent/inbox/discard', (subject, messages) => {
if (subject === agent && messages.some(discarded => discarded.id === message.id)) cleanup()
})