session-list.spec.ts 4.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. import { afterEach, beforeEach, describe, expect, it } from 'vitest'
  2. import { mkdtemp, rm } from 'node:fs/promises'
  3. import { tmpdir } from 'node:os'
  4. import { join } from 'node:path'
  5. import { PROTOCOL_VERSION } from '@agentclientprotocol/sdk'
  6. import { SessionId } from '@deepseek-ai/dsh-session'
  7. import type {} from '@deepseek-ai/dsh-session-title'
  8. import { encodeSessionReferenceUri } from '@deepseek-ai/dsh-session-reference'
  9. import { ACP_SESSION_REFERENCE_META_KEY } from '../src/index.ts'
  10. import { makeBridgeHarness, type BridgeHarness } from './harness.ts'
  11. describe('acp bridge — session/list', () => {
  12. let storageDir: string
  13. let harness: BridgeHarness | undefined
  14. beforeEach(async () => { storageDir = await mkdtemp(join(tmpdir(), 'acp-list-')) })
  15. afterEach(async () => {
  16. await harness?.dispose()
  17. harness = undefined
  18. await rm(storageDir, { recursive: true, force: true })
  19. })
  20. it('advertises title-aware listing and reference metadata for loadable sessions', async () => {
  21. harness = await makeBridgeHarness({ storageDir, withSessionReferences: true })
  22. const initialized = await harness.client.initialize({ protocolVersion: PROTOCOL_VERSION, clientCapabilities: {} })
  23. expect(initialized.agentCapabilities?.sessionCapabilities?.list).toEqual({})
  24. const cwd = process.cwd()
  25. const { sessionId } = await harness.client.newSession({ cwd, mcpServers: [] })
  26. const session = harness.ctx.agents.get(SessionId(sessionId))!.session
  27. await harness.ctx.sessions.appendOutOfBand(session, 'session/title', {
  28. title: 'Reference source title',
  29. messageSeqs: [],
  30. source: { kind: 'fallback' },
  31. }, { kind: 'session-title' })
  32. harness.ctx.sessions.create(SessionId('untitled'), { meta: { cwd: join(storageDir, 'other') } })
  33. harness.ctx.sessions.create(SessionId('missing-cwd'))
  34. const listed = await harness.client.listSessions({})
  35. expect(listed.nextCursor).toBeUndefined()
  36. expect(listed.sessions.map(item => item.sessionId)).toEqual(expect.arrayContaining([sessionId, 'untitled']))
  37. expect(listed.sessions.map(item => item.sessionId)).not.toContain('missing-cwd')
  38. const source = listed.sessions.find(item => item.sessionId === sessionId)
  39. expect(source).toMatchObject({ cwd, title: 'Reference source title' })
  40. expect(source?._meta?.[ACP_SESSION_REFERENCE_META_KEY]).toEqual({
  41. uri: encodeSessionReferenceUri(SessionId(sessionId)),
  42. })
  43. expect(listed.sessions.find(item => item.sessionId === 'untitled')).not.toHaveProperty('title')
  44. })
  45. it('filters by normalized cwd and omits reference metadata without the optional capability', async () => {
  46. harness = await makeBridgeHarness({ storageDir })
  47. await harness.client.initialize({ protocolVersion: PROTOCOL_VERSION, clientCapabilities: {} })
  48. const firstCwd = join(storageDir, 'first')
  49. const secondCwd = join(storageDir, 'second')
  50. const first = await harness.client.newSession({ cwd: firstCwd, mcpServers: [] })
  51. await harness.client.newSession({ cwd: secondCwd, mcpServers: [] })
  52. const listed = await harness.client.listSessions({ cursor: null, cwd: firstCwd })
  53. expect(listed.sessions).toHaveLength(1)
  54. expect(listed.sessions[0]).toMatchObject({ sessionId: first.sessionId, cwd: firstCwd })
  55. expect(listed.sessions[0]?._meta).toBeUndefined()
  56. await expect(harness.client.listSessions({ cwd: null })).resolves.toHaveProperty('sessions')
  57. })
  58. it('rejects unsupported cursors and relative cwd filters', async () => {
  59. harness = await makeBridgeHarness({ storageDir })
  60. await harness.client.initialize({ protocolVersion: PROTOCOL_VERSION, clientCapabilities: {} })
  61. await expect(harness.client.listSessions({ cursor: 'next' })).rejects.toThrow('session/list does not paginate')
  62. await expect(harness.client.listSessions({ cwd: 'relative' })).rejects.toThrow('session/list cwd must be absolute')
  63. })
  64. it('folds titles from persisted sessions in a fresh bridge', async () => {
  65. harness = await makeBridgeHarness({ storageDir, withSessionReferences: true })
  66. await harness.client.initialize({ protocolVersion: PROTOCOL_VERSION, clientCapabilities: {} })
  67. const cwd = process.cwd()
  68. const { sessionId } = await harness.client.newSession({ cwd, mcpServers: [] })
  69. const session = harness.ctx.agents.get(SessionId(sessionId))!.session
  70. await harness.ctx.sessions.appendOutOfBand(session, 'session/title', {
  71. title: 'Persisted reference title',
  72. messageSeqs: [],
  73. source: { kind: 'fallback' },
  74. }, { kind: 'session-title' })
  75. await harness.dispose()
  76. harness = await makeBridgeHarness({ storageDir, withSessionReferences: true })
  77. await harness.client.initialize({ protocolVersion: PROTOCOL_VERSION, clientCapabilities: {} })
  78. await expect(harness.client.listSessions({ cwd })).resolves.toMatchObject({
  79. sessions: [{ sessionId, cwd, title: 'Persisted reference title' }],
  80. })
  81. })
  82. })