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() })