refactor(agent): unify sourced message delivery
This commit is contained in:
@@ -2781,11 +2781,10 @@ export function createTuiChat(
|
||||
if (disposed) {
|
||||
appendNotice(`Agent "${agent.id}" is disposed.`, 'error')
|
||||
} else if (agent.status === 'running') {
|
||||
const source = { kind: 'user' } as const
|
||||
pendingSteering.add(agent.steer(content, { source }))
|
||||
pendingSteering.add(agent.steer({ content, source: { kind: 'user' } }))
|
||||
refreshStatus()
|
||||
} else {
|
||||
agent.followup(content, { source: { kind: 'user' } })
|
||||
agent.followup({ content, source: { kind: 'user' } })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3073,7 +3072,7 @@ export function createTuiChat(
|
||||
editor.addToHistory(text)
|
||||
if (editor.getText() === value) editor.setText('')
|
||||
if (prepared.additionalContext !== undefined) {
|
||||
agent.inject(prepared.additionalContext.content, { source: prepared.additionalContext.source })
|
||||
agent.inject({ content: prepared.additionalContext.content, source: prepared.additionalContext.source })
|
||||
}
|
||||
dispatchMessage(prepared.content)
|
||||
}, (error: unknown) => {
|
||||
|
||||
@@ -4,14 +4,13 @@ import AgentRegistry, {
|
||||
AgentMessageId,
|
||||
type Agent,
|
||||
type AgentCancelCause,
|
||||
type AliasSendOptions,
|
||||
type AgentOptions,
|
||||
type AgentStatus,
|
||||
type SendOptions,
|
||||
} from '@deepseek-ai/dsh-agent'
|
||||
import type { ContentBlock, LlmModelContext, LlmModelInfo, LlmProviderInfo } from '@deepseek-ai/dsh-llm'
|
||||
import CommandService from '@deepseek-ai/dsh-commands'
|
||||
import SessionStore, { SessionId, type Session, type SessionHeader } from '@deepseek-ai/dsh-session'
|
||||
import SessionStore, { SessionId, type Session, type SessionHeader, type UserMessageData } from '@deepseek-ai/dsh-session'
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import type { ToolDefinition } from '@deepseek-ai/dsh-tools'
|
||||
import UserInteractionService from '@deepseek-ai/dsh-user-interaction'
|
||||
@@ -21,12 +20,12 @@ import { TestSessionQueryService } from './session-query.ts'
|
||||
interface FakeAgent extends Agent {
|
||||
status: AgentStatus
|
||||
sent: ContentBlock[][]
|
||||
sentOptions: (SendOptions | AliasSendOptions | undefined)[]
|
||||
sentOptions: (SendOptions | undefined)[]
|
||||
steered: ContentBlock[][]
|
||||
steeredIds: AgentMessageId[]
|
||||
steeredOptions: (AliasSendOptions | undefined)[]
|
||||
steeredOptions: UserMessageData[]
|
||||
injected: ContentBlock[][]
|
||||
injectedOptions: (AliasSendOptions | undefined)[]
|
||||
injectedOptions: UserMessageData[]
|
||||
cancelled: AgentCancelCause[]
|
||||
}
|
||||
|
||||
@@ -163,10 +162,10 @@ export async function createTuiTestHarness<TerminalType extends Terminal, Exit e
|
||||
const sent: ContentBlock[][] = []
|
||||
const steered: ContentBlock[][] = []
|
||||
const steeredIds: AgentMessageId[] = []
|
||||
const sentOptions: (SendOptions | AliasSendOptions | undefined)[] = []
|
||||
const steeredOptions: (AliasSendOptions | undefined)[] = []
|
||||
const sentOptions: (SendOptions | undefined)[] = []
|
||||
const steeredOptions: UserMessageData[] = []
|
||||
const injected: ContentBlock[][] = []
|
||||
const injectedOptions: (AliasSendOptions | undefined)[] = []
|
||||
const injectedOptions: UserMessageData[] = []
|
||||
const cancelled: AgentCancelCause[] = []
|
||||
const agent: FakeAgent = {
|
||||
id: sessionId,
|
||||
@@ -182,26 +181,26 @@ export async function createTuiTestHarness<TerminalType extends Terminal, Exit e
|
||||
injected,
|
||||
injectedOptions,
|
||||
cancelled,
|
||||
send(content, options) {
|
||||
sent.push(content)
|
||||
send(input, options) {
|
||||
sent.push(input.content)
|
||||
sentOptions.push(options)
|
||||
return AgentMessageId('stub')
|
||||
},
|
||||
followup(content, options) {
|
||||
sent.push(content)
|
||||
sentOptions.push(options)
|
||||
followup(input) {
|
||||
sent.push(input.content)
|
||||
sentOptions.push(undefined)
|
||||
return AgentMessageId('stub')
|
||||
},
|
||||
steer(content, options) {
|
||||
steered.push(content)
|
||||
steeredOptions.push(options)
|
||||
steer(input) {
|
||||
steered.push(input.content)
|
||||
steeredOptions.push(input)
|
||||
const id = AgentMessageId(`steering-${steeredIds.length + 1}`)
|
||||
steeredIds.push(id)
|
||||
return id
|
||||
},
|
||||
inject(content, options) {
|
||||
injected.push(content)
|
||||
injectedOptions.push(options)
|
||||
inject(input) {
|
||||
injected.push(input.content)
|
||||
injectedOptions.push(input)
|
||||
return AgentMessageId('stub')
|
||||
},
|
||||
cancel(cause) {
|
||||
|
||||
@@ -11,18 +11,18 @@ buffer
|
||||
2| " mock • target-session"
|
||||
style 1-23 dim
|
||||
3| <blank>
|
||||
4| "▌ "
|
||||
4| " Referenced sessions · Source session (source-session) "
|
||||
style 1-53 dim
|
||||
5| <blank>
|
||||
6| "▌ "
|
||||
style 0-0 fg=bright-blue
|
||||
5| "▌ You "
|
||||
7| "▌ You "
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-4 fg=bright-blue bold
|
||||
6| "▌ Use @Source session "
|
||||
8| "▌ Use @Source session "
|
||||
style 0-0 fg=bright-blue
|
||||
7| "▌ "
|
||||
9| "▌ "
|
||||
style 0-0 fg=bright-blue
|
||||
8| <blank>
|
||||
9| " Referenced sessions · Source session (source-session) "
|
||||
style 1-53 dim
|
||||
10| <blank>
|
||||
11| " Assistant "
|
||||
style 1-9 fg=bright-magenta bold
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
terminal 100x34 buffer=normal length=38 base=4 viewport=4
|
||||
terminal 100x34 buffer=normal length=36 base=2 viewport=2
|
||||
lifecycle started=1 stopped=0 progress=inactive
|
||||
title "Unsafe terminal title \\x1b]2;snapshot-controlled\\x07\\x09\\x7f\\x9b31m"
|
||||
cursor hidden column=100 viewportRow=33 bufferRow=37
|
||||
cursor hidden column=100 viewportRow=33 bufferRow=35
|
||||
buffer
|
||||
0| " DEEPSEEK HARNESS"
|
||||
style 1-8 fg=bright-blue bold
|
||||
@@ -55,23 +55,20 @@ buffer
|
||||
24| " Unsafe context \\x1b]2;snapshot-controlled\\x07\\x09\\x7f\\x9b31m "
|
||||
style 1-60 fg=bright-black
|
||||
25| <blank>
|
||||
26| " Prompt blocked: Unsafe policy \\x1b]2;snapshot-controlled\\x07\\x09\\x7f\\x9b31m "
|
||||
style 1-75 fg=yellow
|
||||
27| <blank>
|
||||
28| " Unsafe turn error \\x1b]2;snapshot-controlled\\x07\\x09\\x7f\\x9b31m "
|
||||
26| " Unsafe turn error \\x1b]2;snapshot-controlled\\x07\\x09\\x7f\\x9b31m "
|
||||
style 1-63 fg=red
|
||||
29| <blank>
|
||||
30| " "
|
||||
31| " Question 1/1 (1 unanswered) · Unsafe header \\x1b]2;snapshot-controlled\\x07\\x09\\x7f\\x9b31m "
|
||||
27| <blank>
|
||||
28| " "
|
||||
29| " Question 1/1 (1 unanswered) · Unsafe header \\x1b]2;snapshot-controlled\\x07\\x09\\x7f\\x9b31m "
|
||||
style 2-90 fg=bright-black
|
||||
32| " Unsafe question \\x1b]2;snapshot-controlled\\x07\\x09\\x7f\\x9b31m "
|
||||
33| " "
|
||||
34| " › 1. Unsafe option \\x1b]2;snapshot-controlled\\x07\\x09\\x7f\\x9b31m Unsafe detail \\x1b]2;snapshot-c "
|
||||
30| " Unsafe question \\x1b]2;snapshot-controlled\\x07\\x09\\x7f\\x9b31m "
|
||||
31| " "
|
||||
32| " › 1. Unsafe option \\x1b]2;snapshot-controlled\\x07\\x09\\x7f\\x9b31m Unsafe detail \\x1b]2;snapshot-c "
|
||||
style 2-65 fg=bright-blue bold
|
||||
style 67-97 fg=bright-black
|
||||
35| " Tab custom answer • ↑/↓ navigate • Enter submit • Esc interrupt "
|
||||
33| " Tab custom answer • ↑/↓ navigate • Enter submit • Esc interrupt "
|
||||
style 2-64 dim
|
||||
36| " "
|
||||
37| "deepseek-v4-flash /workspace/project ↑0 ↓0 0% context tools:collapsed"
|
||||
34| " "
|
||||
35| "deepseek-v4-flash /workspace/project ↑0 ↓0 0% context tools:collapsed"
|
||||
style 0-43 dim
|
||||
style 73-99 dim
|
||||
|
||||
@@ -475,11 +475,6 @@ describe('TUI terminal-state snapshots', () => {
|
||||
content: [{ type: 'text', text: `Unsafe context ${CONTROL_PROBE}` }],
|
||||
source: { kind: 'plugin', plugin: `unsafe-${CONTROL_PROBE}` },
|
||||
}, { surfaceOp: 'append' })
|
||||
session.append('prompt/blocked', {
|
||||
content: [{ type: 'text', text: 'blocked' }],
|
||||
source: { kind: 'user' },
|
||||
reason: `Unsafe policy ${CONTROL_PROBE}`,
|
||||
})
|
||||
session.append('step/end', { turn: 1, step: 1 })
|
||||
session.append('turn/end', {
|
||||
turn: 1,
|
||||
|
||||
@@ -417,7 +417,6 @@ describe('resume command and /resume', () => {
|
||||
[{ kind: 'error', step: 1, message: 'failed' }, 'error'],
|
||||
[{ kind: 'disposed' }, 'disposed'],
|
||||
[{ kind: 'max-tokens' }, 'max tokens'],
|
||||
[{ kind: 'rejected', reason: 'policy' }, 'rejected'],
|
||||
[{ kind: 'interrupted' }, 'interrupted'],
|
||||
[{ kind: 'future-result' } as unknown as TurnEndReason, 'unknown result'],
|
||||
] as const)('renders the last turn result %s', async (reason, label) => {
|
||||
@@ -1182,7 +1181,6 @@ describe('pi-tui chat lifecycle and transcript', () => {
|
||||
// A non-plugin injected source (goal) has no `plugin` field, so its context
|
||||
// card label falls back to the source kind.
|
||||
result.session.append('user/message', { content: [{ type: 'text', text: 'goal context' }], source: { kind: 'goal', goalId: 'g1', revision: 1, round: 0 } as never }, { surfaceOp: 'append' })
|
||||
result.session.append('prompt/blocked', { content: [{ type: 'text', text: 'blocked' }], source: { kind: 'user' }, reason: 'test policy' })
|
||||
appendAssistant(result.session, [])
|
||||
result.session.append('step/end', { turn: 1, step: 1 })
|
||||
result.session.append('turn/end', { turn: 1, reason: { kind: 'aborted' } })
|
||||
@@ -1263,7 +1261,6 @@ describe('pi-tui chat lifecycle and transcript', () => {
|
||||
expect(result.terminal.output).toContain('Steering')
|
||||
expect(result.terminal.output).toContain('user context')
|
||||
expect(result.terminal.output).toContain('Context · goal') // goal-sourced injected context labels by kind
|
||||
expect(result.terminal.output).toContain('Prompt blocked')
|
||||
expect(result.terminal.output).toContain('Turn cancelled')
|
||||
expect(result.terminal.progress).toContain(true)
|
||||
|
||||
@@ -2610,12 +2607,10 @@ describe('pi-tui chat lifecycle and transcript', () => {
|
||||
events.session.append('turn/start', { turn: 4, trigger: { kind: 'message', source: { kind: 'user' } } })
|
||||
events.session.append('turn/end', { turn: 4, reason: { kind: 'max-tokens' } })
|
||||
events.session.append('turn/start', { turn: 5, trigger: { kind: 'message', source: { kind: 'user' } } })
|
||||
events.session.append('turn/end', { turn: 5, reason: { kind: 'rejected', reason: 'policy' } })
|
||||
events.session.append('turn/end', { turn: 5, reason: { kind: 'interrupted' } })
|
||||
events.session.append('turn/start', { turn: 6, trigger: { kind: 'message', source: { kind: 'user' } } })
|
||||
events.session.append('turn/end', { turn: 6, reason: { kind: 'interrupted' } })
|
||||
events.session.append('turn/start', { turn: 7, trigger: { kind: 'message', source: { kind: 'user' } } })
|
||||
events.session.append('turn/end', {
|
||||
turn: 7,
|
||||
turn: 6,
|
||||
reason: { kind: 'error', step: 1, failure: { message: 'structured provider failure', code: 'SERVER' } },
|
||||
})
|
||||
agentEvents(events.ctx, events.agent).emit('agent/disposed')
|
||||
@@ -2625,7 +2620,6 @@ describe('pi-tui chat lifecycle and transcript', () => {
|
||||
expect(events.terminal.output).toContain('Turn cancelled')
|
||||
expect(events.terminal.output).toContain('structured provider failure')
|
||||
expect(events.terminal.output).toContain('output-token limit')
|
||||
expect(events.terminal.output).toContain('Turn rejected')
|
||||
expect(events.terminal.output).toContain('previous process ended')
|
||||
expect(events.terminal.output).toContain('was disposed')
|
||||
await dispose(events)
|
||||
|
||||
Reference in New Issue
Block a user