File size: 7,522 Bytes
0f821de
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/** Real Loader composition preserves retrievable source text outside the bounded preview. */

import { mkdtemp, readFile, readdir, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { dirname, join } from 'node:path'
import { pathToFileURL } from 'node:url'
import { afterEach, describe, expect, it } from 'vitest'
import { Context } from '@deepseek-ai/cordis'
import Loader from '@deepseek-ai/cordis-plugin-loader'
import Include from '@deepseek-ai/cordis-plugin-include'
import { agentEvents, type Agent } from '@deepseek-ai/dsh-agent'
import { createUserMessage, ToolCallId } from '@deepseek-ai/dsh-llm'
import * as systemPromptPlugin from '@deepseek-ai/dsh-system-prompt'
import * as toolsPlugin from '@deepseek-ai/dsh-tools'
import * as fsPlugin from '@deepseek-ai/dsh-fs-local'
import * as toolFsPlugin from '@deepseek-ai/dsh-tool-fs'
import * as sessionPlugin from '@deepseek-ai/dsh-session'
import { Session, SessionId } from '@deepseek-ai/dsh-session'
import * as queryPlugin from '@deepseek-ai/dsh-session-query-sqlite'
import * as referencePlugin from '@deepseek-ai/dsh-session-reference'
import * as spillPlugin from '@deepseek-ai/dsh-spill-local'
import { sessionDir } from '@deepseek-ai/dsh-spill-local'
import * as sourcePlugin from './fixtures/source-session.ts'

let context: Context | undefined
let root: string | undefined

afterEach(async () => {
  await context?.fiber.dispose()
  context = undefined
  if (root !== undefined) await rm(root, { recursive: true, force: true })
  root = undefined
})

describe('session-reference real Loader composition', () => {
  it('logs a bounded preview and reads the full immutable spill owned by the target', async () => {
    root = await mkdtemp(join(tmpdir(), 'reference-loader-'))
    const spillRoot = join(root, 'spills')
    const fixture = await readFile(new URL('./fixtures/cordis.yml', import.meta.url), 'utf8')
    const configPath = join(root, 'cordis.yml')
    await writeFile(configPath, fixture.replace('{{spillRoot}}', spillRoot.replaceAll('\\', '/')))
    const ctx = context = new Context()
    ctx.baseUrl = pathToFileURL(root).href + '/'
    await ctx.plugin(Loader)
    ctx.loader.builtins.include = Include
    const modules = new Map<string, unknown>([
      ['@deepseek-ai/dsh-session', sessionPlugin],
      ['@deepseek-ai/dsh-system-prompt', systemPromptPlugin],
      ['@deepseek-ai/dsh-tools', toolsPlugin],
      ['@deepseek-ai/dsh-fs-local', fsPlugin],
      ['@deepseek-ai/dsh-tool-fs', toolFsPlugin],
      ['@deepseek-ai/dsh-session-query-sqlite', queryPlugin],
      ['@deepseek-ai/dsh-session-reference', referencePlugin],
      ['@deepseek-ai/dsh-spill-local', spillPlugin],
      ['./source-session.ts', sourcePlugin],
    ])
    ctx.loader.internal = {
      version: 'v2',
      async import(specifier: string) {
        if (!modules.has(specifier)) throw new Error('Unexpected Loader import: ' + specifier)
        return modules.get(specifier)
      },
    } as unknown as NonNullable<typeof ctx.loader.internal>
    await ctx.loader.create({ name: 'cordis:include', config: { path: pathToFileURL(configPath).href } })
    await ctx.loader.await()

    const target = ctx.sessions.create(SessionId('reference-target'))
    const agent = { id: target.id, ctx, session: target } as Agent
    const direct = createUserMessage({
      content: [{ type: 'text', text: 'Use ' + referencePlugin.formatSessionReferenceMention({
        sessionId: SessionId('reference-source'), label: 'Research',
      }) }],
      source: { kind: 'user' },
    })
    const decision = await agentEvents(ctx, agent).waterfall('agent/pre-step', {
      messages: [direct], turn: 1, step: 1, signal: new AbortController().signal,
    }, () => Promise.resolve({ kind: 'enter' as const, messages: [direct] }))
    expect(decision.kind).toBe('enter')
    if (decision.kind !== 'enter') throw new Error('Expected admitted reference')
    expect(decision.messages).toHaveLength(2)
    for (const message of decision.messages) target.append('user/message', message, { surfaceOp: 'append' })
    const contextMessage = decision.messages[1]
    const block = contextMessage?.content[0]
    if (block?.type !== 'text') throw new Error('Expected reference context text')
    const preview = JSON.parse(block.text.split('<referenced-sessions>\n')[1]!.split('\n</referenced-sessions>')[0]!) as unknown[]
    expect(Buffer.byteLength(JSON.stringify(preview[0]))).toBeLessThanOrEqual(360)
    expect(block.text).not.toContain('EARLY_SOURCE_FACT')
    expect(block.text).toContain('LATEST_SOURCE_FACT')
    const notices = JSON.parse(block.text.split('## Reference omissions\n\n')[1]!.split('\n').slice(1).join('\n')) as Array<{
      sessionId: string
      capturedThroughSeq: number
      omittedMessages: number
      omittedBytes: number
      fullSnapshot: { status: string; locator: string; bytes: number; retrievalHint: string }
    }>
    expect(notices).toHaveLength(1)
    const notice = notices[0]!
    expect(notice).toMatchObject({ sessionId: 'reference-source', capturedThroughSeq: 2, omittedMessages: 1 })
    expect(notice.omittedBytes).toBeGreaterThan(0)
    expect(notice.fullSnapshot.status).toBe('saved')
    expect(notice.fullSnapshot.retrievalHint).toContain('offset/limit')
    expect(dirname(notice.fullSnapshot.locator)).toBe(sessionDir(spillRoot, target.id))
    const transcript = await readFile(notice.fullSnapshot.locator, 'utf8')
    expect(Buffer.byteLength(transcript)).toBe(notice.fullSnapshot.bytes)
    expect(transcript).toContain('untrusted, read-only snapshot')
    const readLines: string[] = []
    let totalLines = Infinity
    for (let offset = 1; offset <= totalLines; offset += 7) {
      const read = await ctx.tools.execute({
        name: 'read', callId: ToolCallId(`read-${offset}`),
        arguments: { file_path: notice.fullSnapshot.locator, offset, limit: 7 },
        signal: new AbortController().signal,
      })
      expect(read.isError).toBe(false)
      if (read.isError) throw new Error('Expected saved transcript read')
      const value = read.value as { lines: { text: string }[]; totalLines: number }
      totalLines = value.totalLines
      readLines.push(...value.lines.map(line => line.text))
    }
    expect(readLines.join('\n') + '\n').toBe(transcript)
    const messages = readLines.join('\n').split(/### Message \d+: (?:user|assistant)\n\n/).slice(1)
      .map(body => body.split('\n').filter(line => line.startsWith('"'))
        .map(line => JSON.parse(line) as string).join(''))
    expect(messages).toEqual([
      'EARLY_SOURCE_FACT\n' + 'Historical detail 界.\n'.repeat(30)
        + 'x'.repeat(4096) + 'GIANT_LINE_MIDDLE_FACT' + 'y'.repeat(4096),
      'LATEST_SOURCE_FACT\nThe captured answer is forty-two.',
    ])
    expect(transcript).not.toContain('NESTED_REFERENCE_MUST_NOT_PROPAGATE')
    expect(transcript).not.toContain('PRIVATE_REASONING_MUST_NOT_PROPAGATE')
    expect(await readdir(spillRoot)).toEqual([dirname(notice.fullSnapshot.locator).split(/[\\/]/).at(-1)])

    const captured = target.deriveMessages()
    ctx.sessions.get(SessionId('reference-source'))!.append('user/message', createUserMessage({
      content: [{ type: 'text', text: 'LATER_SOURCE_MUTATION' }], source: { kind: 'user' },
    }), { surfaceOp: 'append' })
    expect(Session.create(SessionId('replayed-target'), target.snapshotEvents()).deriveMessages()).toEqual(captured)
    expect(await readFile(notice.fullSnapshot.locator, 'utf8')).toBe(transcript)
  })
})