context-provenance.spec.ts 4.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. /**
  2. * Context provenance projection: the role and producer name a transcript row
  3. * shows for one logged non-user message, read from the durable source alone.
  4. */
  5. import { describe, expect, it } from 'vitest'
  6. import { contextForm, contextProvenance } from '../src/client/sessions/context-provenance.ts'
  7. describe('contextProvenance', () => {
  8. it('names a plugin producer by its logged plugin id', () => {
  9. expect(contextProvenance({ kind: 'plugin', plugin: 'dsh-tool-skill' }))
  10. .toEqual({ role: 'inject', label: 'dsh-tool-skill' })
  11. })
  12. it('names workspace instructions by the files they reconciled, deduplicated in first-seen order', () => {
  13. expect(contextProvenance({
  14. kind: 'workspace-instructions',
  15. baseline: true,
  16. changes: [
  17. { action: 'set', scope: '.�AGENTS.md', path: 'AGENTS.md' },
  18. { action: 'set', scope: 'sub�AGENTS.md', path: 'sub/AGENTS.md' },
  19. { action: 'replace', scope: '.�AGENTS.md', path: 'AGENTS.md' },
  20. ],
  21. })).toEqual({ role: 'inject', label: 'AGENTS.md, sub/AGENTS.md' })
  22. })
  23. it('marks a cross-session snapshot as recall and names the sessions it read', () => {
  24. expect(contextProvenance({
  25. kind: 'session-reference',
  26. version: 1,
  27. references: [{ sessionId: 's1', label: 'Refactor the loader' }, { sessionId: 's2', label: 'Fix CI' }],
  28. })).toEqual({ role: 'recall', label: 'Refactor the loader, Fix CI' })
  29. })
  30. it('identifies a producer this UI version does not know by its own durable kind', () => {
  31. expect(contextProvenance({ kind: 'subagent-report', senderSessionId: 'child' }))
  32. .toEqual({ role: 'inject', label: 'subagent-report' })
  33. })
  34. it('falls back to the source kind when the expected name field is unusable', () => {
  35. // Every arm keeps the kind as its last readable name: a missing, empty, or
  36. // wrongly-typed name field must not blank the row header.
  37. expect(contextProvenance({ kind: 'plugin' }).label).toBe('plugin')
  38. expect(contextProvenance({ kind: 'plugin', plugin: '' }).label).toBe('plugin')
  39. expect(contextProvenance({ kind: 'plugin', plugin: 7 }).label).toBe('plugin')
  40. expect(contextProvenance({ kind: 'workspace-instructions', changes: [] }).label)
  41. .toBe('workspace-instructions')
  42. expect(contextProvenance({ kind: 'workspace-instructions', changes: 'AGENTS.md' }).label)
  43. .toBe('workspace-instructions')
  44. expect(contextProvenance({ kind: 'workspace-instructions', changes: [{ action: 'set' }, null] }).label)
  45. .toBe('workspace-instructions')
  46. expect(contextProvenance({ kind: 'session-reference', references: [] }))
  47. .toEqual({ role: 'recall', label: 'session-reference' })
  48. })
  49. it('degrades to an unnamed injection for a source that carries no readable kind', () => {
  50. const unnamed = { role: 'inject', label: null }
  51. expect(contextProvenance(null)).toEqual(unnamed)
  52. expect(contextProvenance(undefined)).toEqual(unnamed)
  53. expect(contextProvenance('plugin')).toEqual(unnamed)
  54. expect(contextProvenance([{ kind: 'plugin' }])).toEqual(unnamed)
  55. expect(contextProvenance({ plugin: 'dsh-tool-skill' })).toEqual(unnamed)
  56. expect(contextProvenance({ kind: 42 })).toEqual(unnamed)
  57. })
  58. })
  59. describe('contextForm', () => {
  60. it('reads the form a producer declared', () => {
  61. expect(contextForm({ kind: 'workspace-instructions', form: 'instructions', changes: [] }))
  62. .toBe('instructions')
  63. expect(contextForm({ kind: 'skill-catalog', form: 'catalog', entries: [] })).toBe('catalog')
  64. expect(contextForm({ kind: 'plugin', form: 'snapshot', sections: [] })).toBe('snapshot')
  65. expect(contextForm({ kind: 'plugin', form: 'notice', summary: 'x' })).toBe('notice')
  66. expect(contextForm({ kind: 'subagent-report', form: 'relay' })).toBe('relay')
  67. expect(contextForm({ kind: 'session-reference', form: 'recall' })).toBe('recall')
  68. })
  69. it('degrades to the opaque presentation for anything this version does not present', () => {
  70. // The durable vocabulary may already be wider than this UI version, and a
  71. // source need not declare a form at all; neither may drop the row.
  72. expect(contextForm({ kind: 'plugin', plugin: 'dsh-tool-skill' })).toBeNull()
  73. // A durable vocabulary wider than this UI version still renders.
  74. expect(contextForm({ kind: 'plugin', form: 'a-later-form' })).toBeNull()
  75. expect(contextForm({ kind: 'plugin', form: '' })).toBeNull()
  76. expect(contextForm({ kind: 'plugin', form: 7 })).toBeNull()
  77. expect(contextForm(null)).toBeNull()
  78. expect(contextForm('instructions')).toBeNull()
  79. })
  80. })