fix(session-reference): bind snapshots to prompts

This commit is contained in:
Yichen Jiang
2026-07-22 17:34:31 +08:00
parent 3fa368854c
commit da8c0ab092
102 changed files with 838 additions and 248 deletions

View File

@@ -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': {

View File

@@ -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) {

View File

@@ -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| " "

View File

@@ -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' },