projection.spec.ts 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. /**
  2. * The `plan` projection unit (session-projection RFC's complete example): a
  3. * double-event fold over the session log. `command/run` records named `plan`
  4. * set the wanted target (`off` → false, anything else → true); `plan/mode`
  5. * commits and clears it; `view` derives `{ active, pending }` where pending
  6. * is true only while an outstanding selection differs from the logged state.
  7. * Pending is thereby a pure replay quantity — a cold fold answers it without
  8. * the service's in-memory intent. Composition without plan-mode has no `plan`
  9. * key; unloading the fiber removes it (HMR safety).
  10. */
  11. import { describe, expect, it } from 'vitest'
  12. import { Context } from 'cordis'
  13. import AgentRegistry from '@deepseek-ai/dsh-agent'
  14. import type { Agent } from '@deepseek-ai/dsh-agent'
  15. import SessionStore from '@deepseek-ai/dsh-session'
  16. import type { Session } from '@deepseek-ai/dsh-session'
  17. import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
  18. import ToolRegistry from '@deepseek-ai/dsh-tools'
  19. import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection'
  20. import UserInteractionService from '@deepseek-ai/dsh-user-interaction'
  21. import { CommandId } from '@deepseek-ai/dsh-commands/brand'
  22. import PlanModeService from '@deepseek-ai/dsh-plan-mode'
  23. interface Bench {
  24. ctx: Context
  25. session: Session
  26. values(): Record<string, unknown>
  27. }
  28. async function harness(withPlanMode: boolean): Promise<Bench> {
  29. const ctx = new Context()
  30. await ctx.plugin(SessionStore)
  31. await ctx.plugin(SystemPrompt, { persona: '' })
  32. await ctx.plugin(ToolRegistry)
  33. await ctx.plugin(UserInteractionService)
  34. await ctx.plugin(AgentRegistry)
  35. await ctx.plugin(SessionProjectionRegistry)
  36. if (withPlanMode) await ctx.plugin(PlanModeService, { section: 'plan policy' })
  37. const session = ctx.sessions.create()
  38. ctx.agents.register({ id: session.id, session, status: 'idle', ctx } as Agent)
  39. return {
  40. ctx,
  41. session,
  42. values: () => ctx.sessionProjections.snapshot(session).values,
  43. }
  44. }
  45. /** Append one logged /plan selection record (the executor's command/run shape). */
  46. function runPlanCommand(session: Session, args: string, index: number): void {
  47. session.append('command/run', {
  48. commandId: CommandId(`plan-proj-${String(index)}`),
  49. name: 'plan',
  50. args,
  51. source: { kind: 'user' },
  52. })
  53. }
  54. /** Commit one plan/mode flip inside an open turn (the invariant's turn-enclosure rule). */
  55. function commitPlanMode(session: Session, active: boolean, turn: number): void {
  56. session.append('turn/start', { turn })
  57. session.append('plan/mode', { active })
  58. session.append('turn/end', { turn, reason: { kind: 'completed' } })
  59. }
  60. describe('plan projection unit', () => {
  61. it('serves inactive/not-pending for the empty log', async () => {
  62. const bench = await harness(true)
  63. expect(bench.values()).toEqual({ plan: { active: false, pending: false } })
  64. })
  65. it('a logged /plan selection reads pending until the boundary commit resolves it', async () => {
  66. const bench = await harness(true)
  67. runPlanCommand(bench.session, '', 0)
  68. expect(bench.values().plan).toEqual({ active: false, pending: true })
  69. // A repeated identical selection returns the same state reference (no frame).
  70. runPlanCommand(bench.session, '', 1)
  71. expect(bench.values().plan).toEqual({ active: false, pending: true })
  72. commitPlanMode(bench.session, true, 0)
  73. expect(bench.values().plan).toEqual({ active: true, pending: false })
  74. })
  75. it('folds `off` args and non-plan commands correctly, and a matching selection is not pending', async () => {
  76. const bench = await harness(true)
  77. commitPlanMode(bench.session, true, 0)
  78. // Another command's record never touches plan state.
  79. bench.session.append('command/run', {
  80. commandId: CommandId('other-1'), name: 'compact', args: '', source: { kind: 'user' },
  81. })
  82. expect(bench.values().plan).toEqual({ active: true, pending: false })
  83. runPlanCommand(bench.session, ' off', 1)
  84. expect(bench.values().plan).toEqual({ active: true, pending: true })
  85. commitPlanMode(bench.session, false, 1)
  86. expect(bench.values().plan).toEqual({ active: false, pending: false })
  87. // Selecting the already-committed state folds to not-pending (net zero).
  88. runPlanCommand(bench.session, 'off', 2)
  89. expect(bench.values().plan).toEqual({ active: false, pending: false })
  90. })
  91. it('a /plan message-argument selection targets plan mode', async () => {
  92. const bench = await harness(true)
  93. runPlanCommand(bench.session, ' sketch the refactor first', 0)
  94. expect(bench.values().plan).toEqual({ active: false, pending: true })
  95. })
  96. it('has no plan key when plan-mode is not composed', async () => {
  97. const bench = await harness(false)
  98. expect('plan' in bench.values()).toBe(false)
  99. })
  100. it('drops the key when the plan-mode fiber unloads (HMR safety)', async () => {
  101. const bench = await harness(false)
  102. const fiber = await bench.ctx.plugin(PlanModeService, { section: 'plan policy' })
  103. expect(bench.values().plan).toEqual({ active: false, pending: false })
  104. await fiber.dispose()
  105. expect('plan' in bench.values()).toBe(false)
  106. })
  107. it('cold replay recovers pending from the log alone (a fresh registry refolds it)', async () => {
  108. const bench = await harness(true)
  109. runPlanCommand(bench.session, '', 0)
  110. // A second registry over the same log (the cold-read shape): no service
  111. // memory involved, the fold alone answers {active:false, pending:true}.
  112. const cold = await harness(true)
  113. for (const event of bench.session.events) {
  114. if (event.type === 'command/run' || event.type === 'plan/mode') {
  115. cold.session.append(event.type, event.data)
  116. }
  117. }
  118. expect(cold.values().plan).toEqual({ active: false, pending: true })
  119. })
  120. })