fix(session-reference): bind snapshots to prompts
This commit is contained in:
@@ -56,7 +56,7 @@ import type {
|
||||
TokenUsage,
|
||||
} from '@deepseek-ai/dsh-llm'
|
||||
import type {} from '@deepseek-ai/dsh-llm-retry'
|
||||
import { SessionId, type Session, type SessionEvent, type TodoItem } from '@deepseek-ai/dsh-session'
|
||||
import { displayPromptContent, SessionId, type Session, type SessionEvent, type TodoItem } from '@deepseek-ai/dsh-session'
|
||||
import {
|
||||
formatSessionReferenceMention,
|
||||
parseSessionReferenceText,
|
||||
@@ -1113,6 +1113,13 @@ function sessionReferenceCard(meta: unknown): string[] | undefined {
|
||||
return labels
|
||||
}
|
||||
|
||||
function promptReferenceCards(event: Extract<SessionEvent, { type: 'user/message' | 'steering/message' }>): string[][] {
|
||||
return event.data.envelope?.prefixContexts.flatMap((context) => {
|
||||
const card = sessionReferenceCard(context.meta)
|
||||
return card === undefined ? [] : [card]
|
||||
}) ?? []
|
||||
}
|
||||
|
||||
function activeToolCallIds(session: Session, active: ReadonlySet<number>): Set<string> {
|
||||
const ids = new Set<string>()
|
||||
for (const event of session.events) {
|
||||
@@ -1372,20 +1379,28 @@ export function createTuiChat(
|
||||
const renderEvent = (event: SessionEvent, options: { addHistory: boolean; renderChunks: boolean }): void => {
|
||||
switch (event.type) {
|
||||
case 'user/message': {
|
||||
const text = displayText(contentText(event.data.content).trim())
|
||||
const text = displayText(contentText(displayPromptContent(event.data)).trim())
|
||||
if (text) {
|
||||
chat.addChild(new Spacer(1))
|
||||
chat.addChild(new UserMessageComponent(text, palette, mdTheme))
|
||||
if (options.addHistory) editor.addToHistory(text)
|
||||
}
|
||||
for (const references of promptReferenceCards(event)) {
|
||||
chat.addChild(new Spacer(1))
|
||||
chat.addChild(new Text(palette.dim(`Referenced sessions · ${references.map(displayText).join(', ')}`), 1, 0))
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'steering/message': {
|
||||
const text = displayText(contentText(event.data.content).trim())
|
||||
const text = displayText(contentText(displayPromptContent(event.data)).trim())
|
||||
if (text) {
|
||||
chat.addChild(new Spacer(1))
|
||||
chat.addChild(new UserMessageComponent(text, palette, mdTheme, 'Steering'))
|
||||
}
|
||||
for (const references of promptReferenceCards(event)) {
|
||||
chat.addChild(new Spacer(1))
|
||||
chat.addChild(new Text(palette.dim(`Referenced sessions · ${references.map(displayText).join(', ')}`), 1, 0))
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'context/message': {
|
||||
|
||||
@@ -24,9 +24,14 @@ class SnapshotAdapter extends LlmAdapter {
|
||||
|
||||
async * stream(options: GenerateOptions): AsyncIterable<StreamChunk> {
|
||||
this.requests.push(options)
|
||||
const prompt = options.messages.at(-1)
|
||||
if (prompt?.role !== 'user' || prompt.content.length !== 3
|
||||
|| prompt.content[1]?.type !== 'text' || prompt.content[1].text !== '\n\n## My request:\n') {
|
||||
throw new Error('session reference did not reach the model as one prefixed user message')
|
||||
}
|
||||
yield { type: 'block-start', index: 0, blockType: 'text' }
|
||||
yield { type: 'text-delta', index: 0, text: 'Snapshot reference accepted.' }
|
||||
yield { type: 'block-end', index: 0, block: { type: 'text', text: 'Snapshot reference accepted.' } }
|
||||
yield { type: 'text-delta', index: 0, text: 'Combined reference request accepted.' }
|
||||
yield { type: 'block-end', index: 0, block: { type: 'text', text: 'Combined reference request accepted.' } }
|
||||
yield { type: 'finish', reason: { kind: 'stop' } }
|
||||
}
|
||||
}
|
||||
@@ -108,11 +113,22 @@ describe('TUI session-reference snapshot', () => {
|
||||
expect(request).toContain('Recent retained question.')
|
||||
expect(request).not.toContain('SHADOWED OLD USER')
|
||||
expect(request).not.toContain('SHADOWED OLD ASSISTANT')
|
||||
const context = target.session.events.find(event => event.type === 'context/message')
|
||||
expect(context?.type === 'context/message' && context.data.meta).toMatchObject({
|
||||
kind: 'session-reference',
|
||||
references: [{ sessionId: 'source-session', compacted: true }],
|
||||
const user = target.session.events.find(event => event.type === 'user/message')
|
||||
expect(user?.type === 'user/message' && user.data.envelope).toMatchObject({
|
||||
displayContent: [{ type: 'text', text: 'Use @Source session' }],
|
||||
prefixContexts: [{
|
||||
source: { kind: 'plugin', plugin: 'session-reference' },
|
||||
meta: {
|
||||
kind: 'session-reference',
|
||||
references: [{ sessionId: 'source-session', compacted: true }],
|
||||
},
|
||||
}],
|
||||
})
|
||||
expect(user?.type === 'user/message' && user.data.content[1]).toEqual({
|
||||
type: 'text',
|
||||
text: '\n\n## My request:\n',
|
||||
})
|
||||
expect(target.session.events.some(event => event.type === 'context/message')).toBe(false)
|
||||
|
||||
const snapshot = await terminal.snapshot({ includeScrollback: true })
|
||||
if (REFRESHING) {
|
||||
|
||||
@@ -36,7 +36,7 @@ buffer
|
||||
12| <blank>
|
||||
13| " Assistant "
|
||||
style 1-9 fg=bright-magenta bold
|
||||
14| " Snapshot reference accepted. "
|
||||
14| " Combined reference request accepted. "
|
||||
15| "────────────────────────────────────────────────────────────────────────────────────────────────"
|
||||
style 0-95 dim
|
||||
16| " "
|
||||
|
||||
@@ -750,6 +750,56 @@ describe('pi-tui chat lifecycle and transcript', () => {
|
||||
expect(result.terminal.output).toContain('Session reference failed')
|
||||
expect(result.terminal.output).toContain('keep @[')
|
||||
|
||||
result.session.append('user/message', {
|
||||
content: [
|
||||
{ type: 'text', text: 'hidden baked snapshot payload' },
|
||||
{ type: 'text', text: '\n\n## My request:\n' },
|
||||
{ type: 'text', text: 'visible referenced question' },
|
||||
],
|
||||
source: { kind: 'user' },
|
||||
envelope: {
|
||||
displayContent: [{ type: 'text', text: 'visible referenced question' }],
|
||||
prefixContexts: [{
|
||||
source: { kind: 'plugin', plugin: 'session-reference' },
|
||||
meta: {
|
||||
kind: 'session-reference',
|
||||
references: [{ sessionId: 'prefixed', label: 'Prefixed source' }],
|
||||
},
|
||||
}],
|
||||
},
|
||||
}, { surfaceOp: 'append' })
|
||||
await tick()
|
||||
expect(result.terminal.output).toContain('visible referenced question')
|
||||
expect(result.terminal.output).toContain('Referenced sessions · Prefixed source (prefixed)')
|
||||
expect(result.terminal.output).not.toContain('hidden baked snapshot payload')
|
||||
|
||||
result.session.append('steering/message', {
|
||||
turn: 1,
|
||||
content: [
|
||||
{ type: 'text', text: 'hidden non-reference prefix' },
|
||||
{ type: 'text', text: '\n\n## My request:\n' },
|
||||
{ type: 'text', text: 'visible steering prompt' },
|
||||
],
|
||||
source: { kind: 'user' },
|
||||
envelope: {
|
||||
displayContent: [{ type: 'text', text: 'visible steering prompt' }],
|
||||
prefixContexts: [
|
||||
{ source: { kind: 'plugin', plugin: 'other' }, meta: { kind: 'other' } },
|
||||
{
|
||||
source: { kind: 'plugin', plugin: 'session-reference' },
|
||||
meta: {
|
||||
kind: 'session-reference',
|
||||
references: [{ sessionId: 'steering-source', label: 'Steering source' }],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}, { surfaceOp: 'append' })
|
||||
await tick()
|
||||
expect(result.terminal.output).toContain('visible steering prompt')
|
||||
expect(result.terminal.output).toContain('Referenced sessions · Steering source (steering-source)')
|
||||
expect(result.terminal.output).not.toContain('hidden non-reference prefix')
|
||||
|
||||
result.session.append('context/message', {
|
||||
content: [{ type: 'text', text: 'secret full snapshot payload' }],
|
||||
source: { kind: 'plugin', plugin: 'session-reference' },
|
||||
|
||||
Reference in New Issue
Block a user