event-manifest.test.ts 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. import { describe, expect, test } from "bun:test"
  2. import {
  3. Agent,
  4. Config,
  5. FileSystem,
  6. Form,
  7. Integration,
  8. Permission,
  9. Project,
  10. Reference,
  11. Session,
  12. Workspace,
  13. } from "../src/index.js"
  14. import { EventManifest } from "../src/event-manifest.js"
  15. import { FileSystemV1 } from "../src/filesystem-v1.js"
  16. import { IdeEvent } from "../src/ide-event.js"
  17. import { McpEvent } from "../src/mcp-event.js"
  18. import { Plugin } from "../src/plugin.js"
  19. import { SessionEvent } from "../src/session-event.js"
  20. import { SessionID } from "../src/session-id.js"
  21. import { SessionMessage } from "../src/session-message.js"
  22. import { SessionV1 } from "../src/session-v1.js"
  23. import { WorkspaceEvent } from "../src/workspace-event.js"
  24. describe("public event manifest", () => {
  25. test("owns the complete public event surface", () => {
  26. expect(EventManifest.ServerDefinitions).toContain(Agent.Event.Updated)
  27. expect(EventManifest.ServerDefinitions.filter((definition) => definition.type === "agent.updated")).toEqual([
  28. Agent.Event.Updated,
  29. ])
  30. expect(EventManifest.Definitions).toContain(Agent.Event.Updated)
  31. expect(EventManifest.Definitions.filter((definition) => definition.type === "agent.updated")).toEqual([
  32. Agent.Event.Updated,
  33. ])
  34. expect(SessionV1.Event.Definitions).toEqual([
  35. SessionV1.Event.Created,
  36. SessionV1.Event.Updated,
  37. SessionV1.Event.Deleted,
  38. SessionV1.Event.MessageUpdated,
  39. SessionV1.Event.MessageRemoved,
  40. SessionV1.Event.PartUpdated,
  41. SessionV1.Event.PartRemoved,
  42. SessionV1.Event.PartDelta,
  43. SessionV1.Event.Diff,
  44. SessionV1.Event.Error,
  45. ])
  46. expect(Array.from(EventManifest.Latest.keys())).toEqual(
  47. Array.from(new Set(EventManifest.Definitions.map((definition) => definition.type))),
  48. )
  49. expect(EventManifest.Latest.get("agent.updated")).toBe(Agent.Event.Updated)
  50. expect(EventManifest.Latest.get("plugin.updated")).toBe(Plugin.Event.Updated)
  51. expect(EventManifest.Server.get("mcp.status.changed")).toBe(McpEvent.StatusChanged)
  52. expect(EventManifest.Server.get("mcp.resources.changed")).toBe(McpEvent.ResourcesChanged)
  53. expect(EventManifest.Server.get("session.deleted")).toBe(SessionEvent.Deleted)
  54. expect(EventManifest.Server.has("mcp.tools.changed")).toBe(false)
  55. expect(Agent.Event.Updated.durable).toBeUndefined()
  56. expect(EventManifest.Durable.has("agent.updated")).toBe(false)
  57. })
  58. test("uses canonical definitions for current public events", () => {
  59. expect(Session.Event).toBe(SessionEvent)
  60. expect(Session.Event.Definitions).toBe(SessionEvent.Definitions)
  61. expect(Workspace.Event).toBe(WorkspaceEvent)
  62. expect(Workspace.Event.Definitions).toBe(WorkspaceEvent.Definitions)
  63. expect(EventManifest.Latest.get("session.step.ended")).toBe(SessionEvent.Step.Ended)
  64. expect(EventManifest.Latest.get("agent.updated")).toBe(Agent.Event.Updated)
  65. expect(EventManifest.Latest.get("project.updated")).toBe(Project.Event.Updated)
  66. expect(Agent.Event.Definitions).toEqual([Agent.Event.Updated])
  67. expect(Project.Event.Definitions).toEqual([Project.Event.Updated])
  68. expect(Config.Event.Definitions).toEqual([Config.Event.Updated])
  69. expect(FileSystem.Event.Definitions).toEqual([FileSystem.Event.Changed])
  70. expect(FileSystemV1.Event.Definitions).toEqual([FileSystemV1.Event.Edited])
  71. expect(Integration.Event.Definitions).toEqual([Integration.Event.Updated, Integration.Event.ConnectionUpdated])
  72. expect(Permission.Event.Definitions).toEqual([Permission.Event.Asked, Permission.Event.Replied])
  73. expect(Form.Event.Definitions).toEqual([Form.Event.Created, Form.Event.Replied, Form.Event.Cancelled])
  74. expect(Reference.Event.Definitions).toEqual([Reference.Event.Updated])
  75. expect(Plugin.Event.Definitions).toEqual([Plugin.Event.Added, Plugin.Event.Updated])
  76. expect(McpEvent.Definitions).toEqual([McpEvent.ToolsChanged, McpEvent.ResourcesChanged, McpEvent.StatusChanged])
  77. expect(EventManifest.Latest.has("mcp.browser.open.failed")).toBe(false)
  78. expect(EventManifest.Latest.has("ide.installed")).toBe(false)
  79. expect(IdeEvent.Definitions).toEqual([IdeEvent.Installed])
  80. const sessionV1TailStart = EventManifest.Definitions.indexOf(SessionV1.Event.PartDelta)
  81. expect(EventManifest.Definitions.slice(sessionV1TailStart, sessionV1TailStart + 3)).toEqual([
  82. SessionV1.Event.PartDelta,
  83. SessionV1.Event.Diff,
  84. SessionV1.Event.Error,
  85. ])
  86. expect(EventManifest.Durable.get("session.step.ended.1")).toBe(SessionEvent.Step.Ended)
  87. expect(EventManifest.Durable.has("session.step.ended.2")).toBe(false)
  88. })
  89. test("derives durable definitions from explicit definition durability", () => {
  90. expect(Array.from(EventManifest.Durable.keys()).toSorted()).toEqual(
  91. [
  92. "session.created.1",
  93. "session.updated.1",
  94. "session.deleted.1",
  95. "session.deleted.2",
  96. "message.updated.1",
  97. "message.removed.1",
  98. "message.part.updated.1",
  99. "message.part.removed.1",
  100. "session.agent.selected.1",
  101. "session.model.selected.1",
  102. "session.moved.1",
  103. "session.renamed.1",
  104. "session.forked.2",
  105. "session.input.promoted.1",
  106. "session.input.admitted.1",
  107. "session.execution.started.1",
  108. "session.execution.succeeded.1",
  109. "session.execution.failed.1",
  110. "session.execution.interrupted.1",
  111. "session.instructions.updated.2",
  112. "session.synthetic.1",
  113. "session.skill.activated.1",
  114. "session.shell.started.1",
  115. "session.shell.ended.1",
  116. "session.step.started.1",
  117. "session.step.ended.1",
  118. "session.step.failed.1",
  119. "session.text.started.1",
  120. "session.text.ended.1",
  121. "session.tool.input.started.1",
  122. "session.tool.input.ended.1",
  123. "session.tool.called.1",
  124. "session.tool.progress.1",
  125. "session.tool.success.1",
  126. "session.tool.failed.1",
  127. "session.reasoning.started.1",
  128. "session.reasoning.ended.1",
  129. "session.retry.scheduled.1",
  130. "session.compaction.admitted.1",
  131. "session.compaction.started.1",
  132. "session.compaction.ended.1",
  133. "session.compaction.failed.1",
  134. "session.revert.staged.1",
  135. "session.revert.cleared.1",
  136. "session.revert.committed.1",
  137. ].toSorted(),
  138. )
  139. expect(SessionEvent.DurableDefinitions).toEqual(
  140. SessionEvent.Definitions.filter((definition) => definition.durability === "durable"),
  141. )
  142. expect(SessionEvent.UsageUpdated.durability).toBe("ephemeral")
  143. expect(SessionEvent.Compaction.Delta.durability).toBe("ephemeral")
  144. expect(EventManifest.Durable.has("session.compaction.delta.1")).toBe(false)
  145. expect(EventManifest.ServerDefinitions).toContain(SessionEvent.UsageUpdated)
  146. expect(EventManifest.Definitions.every((definition) => definition.durability !== undefined)).toBe(true)
  147. })
  148. test("uses the current Session skill event as durable version 1", () => {
  149. expect(EventManifest.Durable.get("session.skill.activated.1")).toBe(SessionEvent.Skill.Activated)
  150. expect(EventManifest.Latest.get("session.skill.activated")).toBe(SessionEvent.Skill.Activated)
  151. })
  152. test("keeps simplified session fragment and tool payloads on durable version 1", () => {
  153. const sessionID = SessionID.make("ses_test")
  154. const assistantMessageID = SessionMessage.ID.make("msg_test")
  155. const text = SessionEvent.Text.Started.data.make({ sessionID, assistantMessageID, ordinal: 0 })
  156. const reasoning = SessionEvent.Reasoning.Ended.data.make({
  157. sessionID,
  158. assistantMessageID,
  159. ordinal: 0,
  160. text: "thought",
  161. state: { signature: "sig" },
  162. })
  163. const tool = SessionEvent.Tool.Called.data.make({
  164. sessionID,
  165. assistantMessageID,
  166. callID: "call_test",
  167. input: {},
  168. executed: true,
  169. state: { itemId: "item_test" },
  170. })
  171. expect(text).not.toHaveProperty("textID")
  172. expect(reasoning).not.toHaveProperty("reasoningID")
  173. expect(reasoning).not.toHaveProperty("providerMetadata")
  174. expect(tool).not.toHaveProperty("tool")
  175. expect(tool).not.toHaveProperty("provider")
  176. expect(SessionEvent.Text.Started.durable?.version).toBe(1)
  177. expect(SessionEvent.Tool.Called.durable?.version).toBe(1)
  178. })
  179. test("keeps current session deletion minimal", () => {
  180. const sessionID = SessionID.make("ses_test")
  181. expect(SessionEvent.Deleted.data.make({ sessionID })).toEqual({ sessionID })
  182. expect(SessionEvent.Deleted.durable?.version).toBe(2)
  183. })
  184. })