@grackle-ai/web-components 0.107.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (279) hide show
  1. package/.rush/temp/3ae72563f781afd72723475938136f113846603e.untar.log +10 -0
  2. package/.rush/temp/bc1d5bf9201ce71abeaeaddd096deb9b0805d703.untar.log +10 -0
  3. package/.rush/temp/operation/_phase_build/all.log +18 -0
  4. package/.rush/temp/operation/_phase_build/log-chunks.jsonl +18 -0
  5. package/.rush/temp/operation/_phase_build/state.json +3 -0
  6. package/.rush/temp/operation/_phase_test/all.log +121 -0
  7. package/.rush/temp/operation/_phase_test/log-chunks.jsonl +121 -0
  8. package/.rush/temp/operation/_phase_test/state.json +3 -0
  9. package/.rush/temp/shrinkwrap-deps.json +938 -0
  10. package/.storybook/main.ts +22 -0
  11. package/.storybook/preview.tsx +30 -0
  12. package/config/rig.json +4 -0
  13. package/config/rush-project.json +12 -0
  14. package/dist/index.css +1 -0
  15. package/dist/index.js +39221 -0
  16. package/eslint.config.cjs +5 -0
  17. package/package.json +83 -0
  18. package/rush-logs/web-components._phase_build.cache.log +4 -0
  19. package/rush-logs/web-components._phase_test.cache.log +4 -0
  20. package/src/components/chat/ChatInput.module.scss +81 -0
  21. package/src/components/chat/ChatInput.stories.tsx +91 -0
  22. package/src/components/chat/ChatInput.tsx +168 -0
  23. package/src/components/chat/index.ts +6 -0
  24. package/src/components/dag/DagView.module.scss +149 -0
  25. package/src/components/dag/DagView.stories.tsx +125 -0
  26. package/src/components/dag/DagView.tsx +109 -0
  27. package/src/components/dag/TaskNode.stories.tsx +133 -0
  28. package/src/components/dag/TaskNode.tsx +40 -0
  29. package/src/components/dag/useDagLayout.ts +139 -0
  30. package/src/components/display/Breadcrumbs.module.scss +71 -0
  31. package/src/components/display/Breadcrumbs.stories.tsx +80 -0
  32. package/src/components/display/Breadcrumbs.tsx +46 -0
  33. package/src/components/display/Button.module.scss +110 -0
  34. package/src/components/display/Button.stories.tsx +88 -0
  35. package/src/components/display/Button.tsx +40 -0
  36. package/src/components/display/ConfirmDialog.module.scss +67 -0
  37. package/src/components/display/ConfirmDialog.stories.tsx +81 -0
  38. package/src/components/display/ConfirmDialog.tsx +88 -0
  39. package/src/components/display/CopyButton.module.scss +41 -0
  40. package/src/components/display/CopyButton.stories.tsx +78 -0
  41. package/src/components/display/CopyButton.tsx +64 -0
  42. package/src/components/display/DemoBanner.module.scss +37 -0
  43. package/src/components/display/DemoBanner.stories.tsx +40 -0
  44. package/src/components/display/DemoBanner.tsx +23 -0
  45. package/src/components/display/EventHoverRow.module.scss +102 -0
  46. package/src/components/display/EventHoverRow.stories.tsx +99 -0
  47. package/src/components/display/EventHoverRow.tsx +154 -0
  48. package/src/components/display/EventRenderer.module.scss +272 -0
  49. package/src/components/display/EventRenderer.stories.tsx +186 -0
  50. package/src/components/display/EventRenderer.tsx +271 -0
  51. package/src/components/display/EventStream.module.scss +93 -0
  52. package/src/components/display/EventStream.stories.tsx +249 -0
  53. package/src/components/display/EventStream.tsx +369 -0
  54. package/src/components/display/FloatingActionBar.module.scss +107 -0
  55. package/src/components/display/FloatingActionBar.stories.tsx +122 -0
  56. package/src/components/display/FloatingActionBar.tsx +119 -0
  57. package/src/components/display/SessionAttemptSelector.module.scss +50 -0
  58. package/src/components/display/SessionAttemptSelector.stories.tsx +78 -0
  59. package/src/components/display/SessionAttemptSelector.tsx +49 -0
  60. package/src/components/display/SessionPicker.module.scss +200 -0
  61. package/src/components/display/SessionPicker.stories.tsx +169 -0
  62. package/src/components/display/SessionPicker.tsx +214 -0
  63. package/src/components/display/Skeleton.module.scss +58 -0
  64. package/src/components/display/Skeleton.stories.tsx +94 -0
  65. package/src/components/display/Skeleton.tsx +127 -0
  66. package/src/components/display/Spinner.module.scss +41 -0
  67. package/src/components/display/Spinner.stories.tsx +66 -0
  68. package/src/components/display/Spinner.tsx +32 -0
  69. package/src/components/display/SplashScreen.module.scss +20 -0
  70. package/src/components/display/SplashScreen.stories.tsx +26 -0
  71. package/src/components/display/SplashScreen.tsx +16 -0
  72. package/src/components/display/SplitButton.module.scss +166 -0
  73. package/src/components/display/SplitButton.stories.tsx +95 -0
  74. package/src/components/display/SplitButton.tsx +128 -0
  75. package/src/components/display/Tooltip.module.scss +84 -0
  76. package/src/components/display/Tooltip.stories.tsx +240 -0
  77. package/src/components/display/Tooltip.tsx +184 -0
  78. package/src/components/display/extractText.test.tsx +48 -0
  79. package/src/components/display/index.ts +20 -0
  80. package/src/components/editable/EditableCheckbox.stories.tsx +54 -0
  81. package/src/components/editable/EditableCheckbox.tsx +39 -0
  82. package/src/components/editable/EditableField.module.scss +135 -0
  83. package/src/components/editable/EditableSelect.tsx +164 -0
  84. package/src/components/editable/EditableTextArea.stories.tsx +50 -0
  85. package/src/components/editable/EditableTextArea.tsx +148 -0
  86. package/src/components/editable/EditableTextField.stories.tsx +62 -0
  87. package/src/components/editable/EditableTextField.tsx +153 -0
  88. package/src/components/editable/EnvironmentSelect.module.scss +17 -0
  89. package/src/components/editable/EnvironmentSelect.stories.tsx +61 -0
  90. package/src/components/editable/EnvironmentSelect.tsx +87 -0
  91. package/src/components/editable/index.ts +13 -0
  92. package/src/components/editable/useEditableField.test.tsx +233 -0
  93. package/src/components/editable/useEditableField.ts +173 -0
  94. package/src/components/index.ts +20 -0
  95. package/src/components/knowledge/KnowledgeDetailPanel.module.scss +162 -0
  96. package/src/components/knowledge/KnowledgeDetailPanel.stories.tsx +208 -0
  97. package/src/components/knowledge/KnowledgeDetailPanel.tsx +122 -0
  98. package/src/components/knowledge/KnowledgeGraph.module.scss +110 -0
  99. package/src/components/knowledge/KnowledgeGraph.stories.tsx +180 -0
  100. package/src/components/knowledge/KnowledgeGraph.tsx +455 -0
  101. package/src/components/knowledge/KnowledgeNav.module.scss +130 -0
  102. package/src/components/knowledge/KnowledgeNav.stories.tsx +108 -0
  103. package/src/components/knowledge/KnowledgeNav.tsx +138 -0
  104. package/src/components/knowledge/index.ts +3 -0
  105. package/src/components/layout/AppNav.module.scss +82 -0
  106. package/src/components/layout/AppNav.stories.tsx +115 -0
  107. package/src/components/layout/AppNav.tsx +133 -0
  108. package/src/components/layout/BottomStatusBar.module.scss +58 -0
  109. package/src/components/layout/BottomStatusBar.stories.tsx +35 -0
  110. package/src/components/layout/BottomStatusBar.tsx +206 -0
  111. package/src/components/layout/Sidebar.module.scss +60 -0
  112. package/src/components/layout/Sidebar.stories.tsx +46 -0
  113. package/src/components/layout/Sidebar.tsx +84 -0
  114. package/src/components/layout/StatusBar.module.scss +108 -0
  115. package/src/components/layout/StatusBar.stories.tsx +119 -0
  116. package/src/components/layout/StatusBar.tsx +70 -0
  117. package/src/components/layout/index.ts +9 -0
  118. package/src/components/lists/EnvironmentNav.module.scss +118 -0
  119. package/src/components/lists/EnvironmentNav.stories.tsx +121 -0
  120. package/src/components/lists/EnvironmentNav.tsx +133 -0
  121. package/src/components/lists/FindingsNav.module.scss +126 -0
  122. package/src/components/lists/FindingsNav.tsx +146 -0
  123. package/src/components/lists/TaskList.module.scss +206 -0
  124. package/src/components/lists/TaskList.stories.tsx +401 -0
  125. package/src/components/lists/TaskList.tsx +509 -0
  126. package/src/components/lists/index.ts +6 -0
  127. package/src/components/lists/listHelpers.tsx +130 -0
  128. package/src/components/notifications/Callout.module.scss +83 -0
  129. package/src/components/notifications/Callout.stories.tsx +81 -0
  130. package/src/components/notifications/Callout.tsx +64 -0
  131. package/src/components/notifications/Toast.module.scss +86 -0
  132. package/src/components/notifications/Toast.stories.tsx +71 -0
  133. package/src/components/notifications/Toast.tsx +51 -0
  134. package/src/components/notifications/ToastContainer.module.scss +23 -0
  135. package/src/components/notifications/ToastContainer.stories.tsx +66 -0
  136. package/src/components/notifications/ToastContainer.tsx +29 -0
  137. package/src/components/notifications/UpdateBanner.stories.tsx +77 -0
  138. package/src/components/notifications/UpdateBanner.test.tsx +64 -0
  139. package/src/components/notifications/UpdateBanner.tsx +44 -0
  140. package/src/components/notifications/index.ts +8 -0
  141. package/src/components/panels/AboutPanel.stories.tsx +70 -0
  142. package/src/components/panels/AboutPanel.tsx +66 -0
  143. package/src/components/panels/AppearancePanel.stories.tsx +45 -0
  144. package/src/components/panels/AppearancePanel.tsx +97 -0
  145. package/src/components/panels/CredentialProvidersPanel.stories.tsx +62 -0
  146. package/src/components/panels/CredentialProvidersPanel.tsx +111 -0
  147. package/src/components/panels/EnvironmentEditPanel.module.scss +170 -0
  148. package/src/components/panels/EnvironmentEditPanel.stories.tsx +206 -0
  149. package/src/components/panels/EnvironmentEditPanel.tsx +785 -0
  150. package/src/components/panels/FindingsPanel.module.scss +94 -0
  151. package/src/components/panels/FindingsPanel.stories.tsx +109 -0
  152. package/src/components/panels/FindingsPanel.tsx +76 -0
  153. package/src/components/panels/KeyboardShortcutsPanel.module.scss +65 -0
  154. package/src/components/panels/KeyboardShortcutsPanel.stories.tsx +40 -0
  155. package/src/components/panels/KeyboardShortcutsPanel.tsx +104 -0
  156. package/src/components/panels/PluginsPanel.tsx +77 -0
  157. package/src/components/panels/SettingsPanel.module.scss +336 -0
  158. package/src/components/panels/TaskActionButtons.module.scss +22 -0
  159. package/src/components/panels/TaskActionButtons.stories.tsx +125 -0
  160. package/src/components/panels/TaskActionButtons.tsx +87 -0
  161. package/src/components/panels/TaskEditPanel.module.scss +202 -0
  162. package/src/components/panels/TaskEditPanel.stories.tsx +75 -0
  163. package/src/components/panels/TaskEditPanel.tsx +328 -0
  164. package/src/components/panels/TaskOverviewPanel.module.scss +236 -0
  165. package/src/components/panels/TaskOverviewPanel.stories.tsx +219 -0
  166. package/src/components/panels/TaskOverviewPanel.tsx +270 -0
  167. package/src/components/panels/TokensPanel.stories.tsx +131 -0
  168. package/src/components/panels/TokensPanel.tsx +143 -0
  169. package/src/components/panels/WorkpadPanel.module.scss +39 -0
  170. package/src/components/panels/WorkpadPanel.stories.tsx +56 -0
  171. package/src/components/panels/WorkpadPanel.tsx +63 -0
  172. package/src/components/panels/index.ts +13 -0
  173. package/src/components/personas/McpToolSelector.module.scss +109 -0
  174. package/src/components/personas/McpToolSelector.stories.tsx +129 -0
  175. package/src/components/personas/McpToolSelector.tsx +180 -0
  176. package/src/components/personas/PersonaManager.module.scss +233 -0
  177. package/src/components/personas/PersonaManager.stories.tsx +139 -0
  178. package/src/components/personas/PersonaManager.tsx +122 -0
  179. package/src/components/schedules/ScheduleManager.module.scss +98 -0
  180. package/src/components/schedules/ScheduleManager.stories.tsx +78 -0
  181. package/src/components/schedules/ScheduleManager.tsx +160 -0
  182. package/src/components/settings/SettingsNav.module.scss +82 -0
  183. package/src/components/settings/SettingsNav.stories.tsx +83 -0
  184. package/src/components/settings/SettingsNav.tsx +104 -0
  185. package/src/components/streams/StreamDetailPanel.module.scss +206 -0
  186. package/src/components/streams/StreamDetailPanel.stories.tsx +132 -0
  187. package/src/components/streams/StreamDetailPanel.tsx +119 -0
  188. package/src/components/streams/StreamList.module.scss +92 -0
  189. package/src/components/streams/StreamList.stories.tsx +99 -0
  190. package/src/components/streams/StreamList.tsx +114 -0
  191. package/src/components/streams/index.ts +10 -0
  192. package/src/components/tools/AgentToolCard.module.scss +118 -0
  193. package/src/components/tools/AgentToolCard.stories.tsx +304 -0
  194. package/src/components/tools/AgentToolCard.tsx +247 -0
  195. package/src/components/tools/FileEditCard.stories.tsx +138 -0
  196. package/src/components/tools/FileEditCard.tsx +160 -0
  197. package/src/components/tools/FileReadCard.stories.tsx +120 -0
  198. package/src/components/tools/FileReadCard.tsx +106 -0
  199. package/src/components/tools/FindingCard.stories.tsx +124 -0
  200. package/src/components/tools/FindingCard.tsx +178 -0
  201. package/src/components/tools/GenericToolCard.stories.tsx +80 -0
  202. package/src/components/tools/GenericToolCard.tsx +111 -0
  203. package/src/components/tools/IpcCard.stories.tsx +129 -0
  204. package/src/components/tools/IpcCard.tsx +178 -0
  205. package/src/components/tools/KnowledgeCard.stories.tsx +112 -0
  206. package/src/components/tools/KnowledgeCard.tsx +165 -0
  207. package/src/components/tools/MetadataCard.stories.tsx +32 -0
  208. package/src/components/tools/MetadataCard.tsx +39 -0
  209. package/src/components/tools/SearchCard.stories.tsx +74 -0
  210. package/src/components/tools/SearchCard.tsx +86 -0
  211. package/src/components/tools/ShellCard.stories.tsx +112 -0
  212. package/src/components/tools/ShellCard.tsx +106 -0
  213. package/src/components/tools/TaskCard.stories.tsx +123 -0
  214. package/src/components/tools/TaskCard.tsx +203 -0
  215. package/src/components/tools/TodoCard.module.scss +131 -0
  216. package/src/components/tools/TodoCard.stories.tsx +202 -0
  217. package/src/components/tools/TodoCard.tsx +200 -0
  218. package/src/components/tools/ToolCard.stories.tsx +177 -0
  219. package/src/components/tools/ToolCard.tsx +60 -0
  220. package/src/components/tools/ToolCardProps.ts +20 -0
  221. package/src/components/tools/ToolSearchCard.stories.tsx +81 -0
  222. package/src/components/tools/ToolSearchCard.tsx +86 -0
  223. package/src/components/tools/WorkpadCard.stories.tsx +106 -0
  224. package/src/components/tools/WorkpadCard.tsx +125 -0
  225. package/src/components/tools/classifyTool.test.ts +44 -0
  226. package/src/components/tools/classifyTool.ts +134 -0
  227. package/src/components/tools/parseDiff.ts +95 -0
  228. package/src/components/tools/parseShellOutput.ts +28 -0
  229. package/src/components/tools/toolCardHelpers.test.ts +53 -0
  230. package/src/components/tools/toolCards.module.scss +234 -0
  231. package/src/components/workspace/WorkspaceBoard.module.scss +238 -0
  232. package/src/components/workspace/WorkspaceBoard.stories.tsx +240 -0
  233. package/src/components/workspace/WorkspaceBoard.tsx +232 -0
  234. package/src/components/workspace/WorkspaceFormFields.module.scss +79 -0
  235. package/src/components/workspace/WorkspaceFormFields.stories.tsx +133 -0
  236. package/src/components/workspace/WorkspaceFormFields.tsx +185 -0
  237. package/src/context/GrackleContext.ts +28 -0
  238. package/src/context/GrackleContextTypes.ts +64 -0
  239. package/src/context/SidebarContext.tsx +53 -0
  240. package/src/context/ThemeContext.tsx +21 -0
  241. package/src/context/ToastContext.tsx +56 -0
  242. package/src/hooks/types.ts +864 -0
  243. package/src/hooks/useEventSelection.test.ts +204 -0
  244. package/src/hooks/useEventSelection.ts +158 -0
  245. package/src/hooks/useSmartScroll.ts +151 -0
  246. package/src/hooks/useTheme.ts +228 -0
  247. package/src/index.ts +210 -0
  248. package/src/mocks/MockGrackleProvider.tsx +1397 -0
  249. package/src/mocks/mockData.ts +1966 -0
  250. package/src/mocks/mockKnowledgeData.ts +294 -0
  251. package/src/scss.d.ts +12 -0
  252. package/src/styles/global.scss +244 -0
  253. package/src/styles/mixins.scss +278 -0
  254. package/src/styles/prism-theme.scss +148 -0
  255. package/src/styles/theme.scss +1102 -0
  256. package/src/test-utils/storybook-decorators.tsx +50 -0
  257. package/src/test-utils/storybook-helpers.ts +262 -0
  258. package/src/themes.ts +142 -0
  259. package/src/utils/boardColumns.ts +141 -0
  260. package/src/utils/breadcrumbs.test.ts +285 -0
  261. package/src/utils/breadcrumbs.ts +222 -0
  262. package/src/utils/dashboard.test.ts +156 -0
  263. package/src/utils/dashboard.ts +195 -0
  264. package/src/utils/eventContent.test.ts +353 -0
  265. package/src/utils/eventContent.ts +209 -0
  266. package/src/utils/findingCategory.ts +33 -0
  267. package/src/utils/format.ts +27 -0
  268. package/src/utils/iconSize.ts +18 -0
  269. package/src/utils/navigation.ts +205 -0
  270. package/src/utils/route-config.test.ts +128 -0
  271. package/src/utils/scrollUtils.test.ts +65 -0
  272. package/src/utils/scrollUtils.ts +49 -0
  273. package/src/utils/sessionEvents.test.ts +302 -0
  274. package/src/utils/sessionEvents.ts +233 -0
  275. package/src/utils/taskStatus.tsx +137 -0
  276. package/src/utils/time.ts +92 -0
  277. package/tsconfig.json +8 -0
  278. package/vite.config.ts +20 -0
  279. package/vitest.config.ts +10 -0
@@ -0,0 +1,1966 @@
1
+ /**
2
+ * Static mock data for visual testing (`?mock` mode).
3
+ *
4
+ * Provides realistic sample entities that exercise every UI state:
5
+ * multiple environments, sessions in various statuses, workspaces with
6
+ * tasks at different lifecycle stages, and findings across all categories.
7
+ */
8
+
9
+ import type {
10
+ Environment,
11
+ Session,
12
+ SessionEvent,
13
+ Workspace,
14
+ TaskData,
15
+ FindingData,
16
+ TokenInfo,
17
+ PersonaData,
18
+ } from "../hooks/types.js";
19
+ export { MOCK_KNOWLEDGE_NODES, MOCK_KNOWLEDGE_LINKS, MOCK_KNOWLEDGE_DETAILS } from "./mockKnowledgeData.js";
20
+
21
+ // ─── Environments ───────────────────────────────────
22
+
23
+ /** Sample environments covering different adapter types and statuses. */
24
+ export const MOCK_ENVIRONMENTS: Environment[] = [
25
+ {
26
+ id: "env-local-01",
27
+ displayName: "Local Dev",
28
+ adapterType: "local",
29
+ adapterConfig: "{}",
30
+ status: "connected",
31
+ bootstrapped: true,
32
+ },
33
+ {
34
+ id: "env-docker-01",
35
+ displayName: "Docker Sandbox",
36
+ adapterType: "docker",
37
+ adapterConfig: '{"image":"node:20"}',
38
+ status: "connected",
39
+ bootstrapped: true,
40
+ },
41
+ {
42
+ id: "env-cs-01",
43
+ displayName: "GitHub Codespace",
44
+ adapterType: "codespace",
45
+ adapterConfig: '{"codespaceName":"my-codespace"}',
46
+ status: "connected",
47
+ bootstrapped: true,
48
+ },
49
+ {
50
+ id: "env-remote-01",
51
+ displayName: "Staging (SSH)",
52
+ adapterType: "ssh",
53
+ adapterConfig: '{"host":"192.168.1.10","user":"deploy","sshPort":22}',
54
+ status: "disconnected",
55
+ bootstrapped: false,
56
+ },
57
+ {
58
+ id: "error-env",
59
+ displayName: "Error Test",
60
+ adapterType: "local",
61
+ adapterConfig: "{}",
62
+ status: "connected",
63
+ bootstrapped: true,
64
+ },
65
+ ];
66
+
67
+ // ─── Sessions ───────────────────────────────────────
68
+
69
+ /** Sample sessions spanning active, completed, and failed states. */
70
+ export const MOCK_SESSIONS: Session[] = [
71
+ {
72
+ id: "sess-001-prev",
73
+ environmentId: "env-local-01",
74
+ runtime: "claude-code",
75
+ status: "stopped",
76
+ endReason: "interrupted",
77
+ prompt: "Implement auth middleware",
78
+ startedAt: "2026-02-26T14:00:00Z",
79
+ endedAt: "2026-02-26T15:12:00Z",
80
+ error: "Context window exceeded before completing implementation",
81
+ inputTokens: 84_200,
82
+ outputTokens: 12_750,
83
+ costMillicents: 41_000,
84
+ },
85
+ {
86
+ id: "sess-001",
87
+ environmentId: "env-local-01",
88
+ runtime: "claude-code",
89
+ status: "running",
90
+ prompt: "Refactor the authentication middleware to use JWT tokens",
91
+ startedAt: "2026-02-27T08:15:00Z",
92
+ inputTokens: 42_600,
93
+ outputTokens: 8_100,
94
+ costMillicents: 22_000,
95
+ },
96
+ {
97
+ id: "sess-002",
98
+ environmentId: "env-docker-01",
99
+ runtime: "copilot",
100
+ status: "stopped",
101
+ endReason: "completed",
102
+ prompt: "Write unit tests for the user registration endpoint",
103
+ startedAt: "2026-02-27T07:30:00Z",
104
+ endedAt: "2026-02-27T07:33:00Z",
105
+ inputTokens: 31_400,
106
+ outputTokens: 9_800,
107
+ costMillicents: 18_000,
108
+ },
109
+ {
110
+ id: "sess-003",
111
+ environmentId: "env-local-01",
112
+ runtime: "claude-code",
113
+ status: "stopped",
114
+ endReason: "interrupted",
115
+ prompt: "Migrate database schema to add audit_log table",
116
+ startedAt: "2026-02-26T22:45:00Z",
117
+ endedAt: "2026-02-26T23:10:00Z",
118
+ inputTokens: 56_300,
119
+ outputTokens: 14_200,
120
+ costMillicents: 31_000,
121
+ },
122
+ {
123
+ id: "sess-004",
124
+ environmentId: "env-docker-01",
125
+ runtime: "codex",
126
+ status: "running",
127
+ prompt: "Implement rate limiting for the public API",
128
+ startedAt: "2026-02-27T09:00:00Z",
129
+ inputTokens: 18_900,
130
+ outputTokens: 4_500,
131
+ costMillicents: 10_000,
132
+ },
133
+ {
134
+ id: "sess-005",
135
+ environmentId: "env-cs-01",
136
+ runtime: "claude-code",
137
+ status: "suspended",
138
+ prompt: "Refactor database connection pooling for multi-tenant isolation",
139
+ startedAt: "2026-02-27T06:00:00Z",
140
+ inputTokens: 67_800,
141
+ outputTokens: 19_300,
142
+ costMillicents: 38_000,
143
+ },
144
+ ];
145
+
146
+ // ─── Session Events ─────────────────────────────────
147
+
148
+ /**
149
+ * Rich event streams for all sessions. Each session has realistic agent
150
+ * activity: system prompts, markdown text, tool calls with results, usage
151
+ * badges, user input, and status transitions.
152
+ */
153
+ export const MOCK_EVENTS: SessionEvent[] = [
154
+ // ── sess-001-prev: failed JWT attempt (interrupted by context window) ──
155
+
156
+ {
157
+ sessionId: "sess-001-prev",
158
+ eventType: "status",
159
+ timestamp: "2026-02-26T14:00:01Z",
160
+ content: "running",
161
+ },
162
+ {
163
+ sessionId: "sess-001-prev",
164
+ eventType: "system",
165
+ timestamp: "2026-02-26T14:00:02Z",
166
+ content: "You are a senior backend engineer. Focus on security best practices and thorough testing.\nProject: acme/alpha — Node.js + Express REST API\nTask: Implement auth middleware using JWT tokens",
167
+ raw: JSON.stringify({ systemContext: true }),
168
+ },
169
+ {
170
+ sessionId: "sess-001-prev",
171
+ eventType: "text",
172
+ timestamp: "2026-02-26T14:00:05Z",
173
+ content: "I'll start by reading the current authentication middleware to understand what needs to change.",
174
+ },
175
+ {
176
+ sessionId: "sess-001-prev",
177
+ eventType: "tool_use",
178
+ timestamp: "2026-02-26T14:00:06Z",
179
+ content: JSON.stringify({ tool: "Read", args: { file_path: "src/middleware/auth.ts" } }),
180
+ raw: JSON.stringify({ id: "tu-prev-001", type: "tool_use" }),
181
+ },
182
+ {
183
+ sessionId: "sess-001-prev",
184
+ eventType: "tool_result",
185
+ timestamp: "2026-02-26T14:00:07Z",
186
+ content: 'import session from "express-session";\nimport type { Request, Response, NextFunction } from "express";\n\nexport function requireAuth(req: Request, res: Response, next: NextFunction): void {\n if (!req.session?.userId) {\n res.status(401).json({ error: "Not authenticated" });\n return;\n }\n next();\n}',
187
+ raw: JSON.stringify({ tool_use_id: "tu-prev-001" }),
188
+ },
189
+ {
190
+ sessionId: "sess-001-prev",
191
+ eventType: "text",
192
+ timestamp: "2026-02-26T14:00:10Z",
193
+ content: "The current auth uses express-session with server-side session storage. Let me find all files that depend on this pattern.",
194
+ },
195
+ {
196
+ sessionId: "sess-001-prev",
197
+ eventType: "tool_use",
198
+ timestamp: "2026-02-26T14:00:11Z",
199
+ content: JSON.stringify({ tool: "Grep", args: { pattern: "req\\.session", path: "src/" } }),
200
+ raw: JSON.stringify({ id: "tu-prev-002", type: "tool_use" }),
201
+ },
202
+ {
203
+ sessionId: "sess-001-prev",
204
+ eventType: "tool_result",
205
+ timestamp: "2026-02-26T14:00:12Z",
206
+ content: "src/middleware/auth.ts:4: if (!req.session?.userId) {\nsrc/routes/login.ts:18: req.session.userId = user.id;\nsrc/routes/login.ts:19: req.session.role = user.role;\nsrc/routes/protected.ts:7: const userId = req.session.userId;",
207
+ raw: JSON.stringify({ tool_use_id: "tu-prev-002" }),
208
+ },
209
+ {
210
+ sessionId: "sess-001-prev",
211
+ eventType: "usage",
212
+ timestamp: "2026-02-26T14:30:00Z",
213
+ content: JSON.stringify({ input_tokens: 42100, output_tokens: 6350, cost_millicents: 21000 }),
214
+ },
215
+ {
216
+ sessionId: "sess-001-prev",
217
+ eventType: "text",
218
+ timestamp: "2026-02-26T14:30:02Z",
219
+ content: "Found 3 files using session-based auth. Now let me read the login route and the protected route to understand the full flow before making changes.",
220
+ },
221
+ {
222
+ sessionId: "sess-001-prev",
223
+ eventType: "tool_use",
224
+ timestamp: "2026-02-26T14:30:03Z",
225
+ content: JSON.stringify({ tool: "Read", args: { file_path: "src/routes/login.ts" } }),
226
+ raw: JSON.stringify({ id: "tu-prev-003", type: "tool_use" }),
227
+ },
228
+ {
229
+ sessionId: "sess-001-prev",
230
+ eventType: "tool_result",
231
+ timestamp: "2026-02-26T14:30:04Z",
232
+ content: 'import { Router } from "express";\nimport bcrypt from "bcrypt";\nimport { db } from "../db.js";\n\nconst router = Router();\n\nrouter.post("/login", async (req, res) => {\n const { email, password } = req.body;\n const user = await db.users.findByEmail(email);\n if (!user || !await bcrypt.compare(password, user.passwordHash)) {\n return res.status(401).json({ error: "Invalid credentials" });\n }\n req.session.userId = user.id;\n req.session.role = user.role;\n res.json({ ok: true });\n});\n\nexport default router;',
233
+ raw: JSON.stringify({ tool_use_id: "tu-prev-003" }),
234
+ },
235
+ {
236
+ sessionId: "sess-001-prev",
237
+ eventType: "error",
238
+ timestamp: "2026-02-26T15:12:00Z",
239
+ content: "Context window exceeded before completing implementation",
240
+ },
241
+ {
242
+ sessionId: "sess-001-prev",
243
+ eventType: "status",
244
+ timestamp: "2026-02-26T15:12:01Z",
245
+ content: "interrupted",
246
+ },
247
+
248
+ // ── sess-001: active JWT refactor (currently running) ──
249
+
250
+ {
251
+ sessionId: "sess-001",
252
+ eventType: "status",
253
+ timestamp: "2026-02-27T08:15:01Z",
254
+ content: "running",
255
+ },
256
+ {
257
+ sessionId: "sess-001",
258
+ eventType: "system",
259
+ timestamp: "2026-02-27T08:15:02Z",
260
+ content: "You are a senior backend engineer. Focus on security best practices and thorough testing.\nProject: acme/alpha — Node.js + Express REST API\nTask: Implement auth middleware using JWT tokens\n\nNote: A previous session attempted this task but was interrupted. Pick up where it left off.",
261
+ raw: JSON.stringify({ systemContext: true }),
262
+ },
263
+ {
264
+ sessionId: "sess-001",
265
+ eventType: "text",
266
+ timestamp: "2026-02-27T08:15:04Z",
267
+ content: "I'll pick up the JWT auth migration. The previous session identified 3 files that need updating. Let me verify the current state and start implementing.",
268
+ },
269
+ {
270
+ sessionId: "sess-001",
271
+ eventType: "tool_use",
272
+ timestamp: "2026-02-27T08:15:04.5Z",
273
+ content: JSON.stringify({
274
+ tool: "TodoWrite",
275
+ args: {
276
+ todos: [
277
+ { content: "Install jsonwebtoken package", activeForm: "Installing jsonwebtoken", status: "pending" },
278
+ { content: "Rewrite auth middleware for JWT", activeForm: "Rewriting auth middleware", status: "pending" },
279
+ { content: "Update login route to issue tokens", activeForm: "Updating login route", status: "pending" },
280
+ { content: "Update protected routes", activeForm: "Updating protected routes", status: "pending" },
281
+ { content: "Fix and update tests", activeForm: "Fixing tests", status: "pending" },
282
+ { content: "Run full test suite", activeForm: "Running tests", status: "pending" },
283
+ ],
284
+ },
285
+ }),
286
+ },
287
+ {
288
+ sessionId: "sess-001",
289
+ eventType: "tool_use",
290
+ timestamp: "2026-02-27T08:15:05Z",
291
+ content: JSON.stringify({ tool: "Grep", args: { pattern: "express-session|req\\.session", path: "src/", output_mode: "files_with_matches" } }),
292
+ raw: JSON.stringify({ id: "tu-001-01", type: "tool_use" }),
293
+ },
294
+ {
295
+ sessionId: "sess-001",
296
+ eventType: "tool_result",
297
+ timestamp: "2026-02-27T08:15:06Z",
298
+ content: "src/middleware/auth.ts\nsrc/routes/login.ts\nsrc/routes/protected.ts",
299
+ raw: JSON.stringify({ tool_use_id: "tu-001-01" }),
300
+ },
301
+ {
302
+ sessionId: "sess-001",
303
+ eventType: "text",
304
+ timestamp: "2026-02-27T08:15:08Z",
305
+ content: "Good, same 3 files. Let me install the JWT library first, then rewrite the middleware.",
306
+ },
307
+ {
308
+ sessionId: "sess-001",
309
+ eventType: "tool_use",
310
+ timestamp: "2026-02-27T08:15:09Z",
311
+ content: JSON.stringify({ tool: "Bash", args: { command: "npm install jsonwebtoken && npm install -D @types/jsonwebtoken" } }),
312
+ raw: JSON.stringify({ id: "tu-001-02", type: "tool_use" }),
313
+ },
314
+ {
315
+ sessionId: "sess-001",
316
+ eventType: "tool_result",
317
+ timestamp: "2026-02-27T08:15:11Z",
318
+ content: "added 12 packages in 3.2s\n\nadded 1 package in 0.8s",
319
+ raw: JSON.stringify({ tool_use_id: "tu-001-02" }),
320
+ },
321
+ {
322
+ sessionId: "sess-001",
323
+ eventType: "tool_use",
324
+ timestamp: "2026-02-27T08:15:11.5Z",
325
+ content: JSON.stringify({
326
+ tool: "TodoWrite",
327
+ args: {
328
+ todos: [
329
+ { content: "Install jsonwebtoken package", activeForm: "Installing jsonwebtoken", status: "completed" },
330
+ { content: "Rewrite auth middleware for JWT", activeForm: "Rewriting auth middleware", status: "in_progress" },
331
+ { content: "Update login route to issue tokens", activeForm: "Updating login route", status: "pending" },
332
+ { content: "Update protected routes", activeForm: "Updating protected routes", status: "pending" },
333
+ { content: "Fix and update tests", activeForm: "Fixing tests", status: "pending" },
334
+ { content: "Run full test suite", activeForm: "Running tests", status: "pending" },
335
+ ],
336
+ },
337
+ }),
338
+ },
339
+ {
340
+ sessionId: "sess-001",
341
+ eventType: "text",
342
+ timestamp: "2026-02-27T08:15:12Z",
343
+ content: "Now I'll rewrite the auth middleware to use JWT verification instead of session checks.",
344
+ },
345
+ {
346
+ sessionId: "sess-001",
347
+ eventType: "tool_use",
348
+ timestamp: "2026-02-27T08:15:13Z",
349
+ content: JSON.stringify({
350
+ tool: "Write",
351
+ args: {
352
+ file_path: "src/middleware/auth.ts",
353
+ content: 'import jwt from "jsonwebtoken";\nimport type { Request, Response, NextFunction } from "express";\n\nconst JWT_SECRET = process.env.JWT_SECRET || "change-me";\n\nexport interface JwtPayload {\n userId: string;\n role: string;\n iat: number;\n exp: number;\n}\n\nexport function verifyToken(req: Request, res: Response, next: NextFunction): void {\n const header = req.headers.authorization;\n if (!header?.startsWith("Bearer ")) {\n res.status(401).json({ error: "Missing or malformed token" });\n return;\n }\n try {\n const decoded = jwt.verify(header.slice(7), JWT_SECRET) as JwtPayload;\n req.user = decoded;\n next();\n } catch (err) {\n if (err instanceof jwt.TokenExpiredError) {\n res.status(401).json({ error: "Token expired" });\n } else {\n res.status(403).json({ error: "Invalid token" });\n }\n }\n}\n',
354
+ },
355
+ }),
356
+ raw: JSON.stringify({ id: "tu-001-03", type: "tool_use" }),
357
+ },
358
+ {
359
+ sessionId: "sess-001",
360
+ eventType: "tool_result",
361
+ timestamp: "2026-02-27T08:15:14Z",
362
+ content: "File written: src/middleware/auth.ts (32 lines)",
363
+ raw: JSON.stringify({ tool_use_id: "tu-001-03" }),
364
+ },
365
+ {
366
+ sessionId: "sess-001",
367
+ eventType: "text",
368
+ timestamp: "2026-02-27T08:15:16Z",
369
+ content: "Now I need to update the login route to issue JWT tokens instead of setting session data.\n\nThe new login flow will:\n1. Validate credentials (unchanged)\n2. Generate a JWT with `userId` and `role` claims\n3. Return the token in the response body\n4. Include a refresh token for long-lived sessions",
370
+ },
371
+ {
372
+ sessionId: "sess-001",
373
+ eventType: "tool_use",
374
+ timestamp: "2026-02-27T08:15:17Z",
375
+ content: JSON.stringify({
376
+ tool: "Write",
377
+ args: {
378
+ file_path: "src/routes/login.ts",
379
+ content: 'import { Router } from "express";\nimport bcrypt from "bcrypt";\nimport jwt from "jsonwebtoken";\nimport crypto from "node:crypto";\nimport { db } from "../db.js";\n\nconst JWT_SECRET = process.env.JWT_SECRET || "change-me";\nconst ACCESS_TOKEN_EXPIRY = "24h";\nconst REFRESH_TOKEN_BYTES = 48;\n\nconst router = Router();\n\nrouter.post("/login", async (req, res) => {\n const { email, password } = req.body;\n const user = await db.users.findByEmail(email);\n if (!user || !(await bcrypt.compare(password, user.passwordHash))) {\n return res.status(401).json({ error: "Invalid credentials" });\n }\n\n const accessToken = jwt.sign(\n { userId: user.id, role: user.role },\n JWT_SECRET,\n { expiresIn: ACCESS_TOKEN_EXPIRY },\n );\n\n const refreshToken = crypto.randomBytes(REFRESH_TOKEN_BYTES).toString("hex");\n await db.refreshTokens.create({ userId: user.id, token: refreshToken });\n\n res.json({ accessToken, refreshToken, expiresIn: ACCESS_TOKEN_EXPIRY });\n});\n\nexport default router;\n',
380
+ },
381
+ }),
382
+ raw: JSON.stringify({ id: "tu-001-04", type: "tool_use" }),
383
+ },
384
+ {
385
+ sessionId: "sess-001",
386
+ eventType: "tool_result",
387
+ timestamp: "2026-02-27T08:15:18Z",
388
+ content: "File written: src/routes/login.ts (33 lines)",
389
+ raw: JSON.stringify({ tool_use_id: "tu-001-04" }),
390
+ },
391
+ {
392
+ sessionId: "sess-001",
393
+ eventType: "text",
394
+ timestamp: "2026-02-27T08:15:20Z",
395
+ content: "Let me update the protected route to use the new `req.user` shape from the JWT payload.",
396
+ },
397
+ {
398
+ sessionId: "sess-001",
399
+ eventType: "tool_use",
400
+ timestamp: "2026-02-27T08:15:21Z",
401
+ content: JSON.stringify({
402
+ tool: "Edit",
403
+ args: {
404
+ file_path: "src/routes/protected.ts",
405
+ old_string: "const userId = req.session.userId;",
406
+ new_string: "const { userId } = req.user as JwtPayload;",
407
+ },
408
+ }),
409
+ raw: JSON.stringify({ id: "tu-001-05", type: "tool_use" }),
410
+ },
411
+ {
412
+ sessionId: "sess-001",
413
+ eventType: "tool_result",
414
+ timestamp: "2026-02-27T08:15:22Z",
415
+ content: "Updated src/routes/protected.ts",
416
+ raw: JSON.stringify({ tool_use_id: "tu-001-05" }),
417
+ },
418
+ {
419
+ sessionId: "sess-001",
420
+ eventType: "text",
421
+ timestamp: "2026-02-27T08:15:24Z",
422
+ content: "Now let me run the existing tests to make sure nothing is broken, then I'll add new JWT-specific tests.",
423
+ },
424
+ {
425
+ sessionId: "sess-001",
426
+ eventType: "tool_use",
427
+ timestamp: "2026-02-27T08:15:25Z",
428
+ content: JSON.stringify({ tool: "Bash", args: { command: "npm test" } }),
429
+ raw: JSON.stringify({ id: "tu-001-06", type: "tool_use" }),
430
+ },
431
+ {
432
+ sessionId: "sess-001",
433
+ eventType: "tool_result",
434
+ timestamp: "2026-02-27T08:15:30Z",
435
+ content: " PASS src/routes/__tests__/login.test.ts\n PASS src/routes/__tests__/protected.test.ts\n FAIL src/middleware/__tests__/auth.test.ts\n x should reject missing session (12ms)\n x should allow valid session (8ms)\n\n 2 failed, 12 passed, 14 total\n\nTest Suites: 1 failed, 2 passed, 3 total",
436
+ raw: JSON.stringify({ tool_use_id: "tu-001-06", is_error: true }),
437
+ },
438
+ {
439
+ sessionId: "sess-001",
440
+ eventType: "text",
441
+ timestamp: "2026-02-27T08:15:32Z",
442
+ content: "Two tests failed because they still test the old session-based auth. I need to rewrite those tests for JWT.\n\nLet me update the auth middleware tests:",
443
+ },
444
+ {
445
+ sessionId: "sess-001",
446
+ eventType: "tool_use",
447
+ timestamp: "2026-02-27T08:15:33Z",
448
+ content: JSON.stringify({
449
+ tool: "Write",
450
+ args: {
451
+ file_path: "src/middleware/__tests__/auth.test.ts",
452
+ content: 'import { describe, it, expect } from "vitest";\nimport jwt from "jsonwebtoken";\nimport { verifyToken } from "../auth.js";\nimport { createMockReqRes } from "../../test-utils/mock-express.js";\n\nconst SECRET = "test-secret";\nprocess.env.JWT_SECRET = SECRET;\n\ndescribe("verifyToken", () => {\n it("should reject requests without Authorization header", () => {\n const { req, res, next } = createMockReqRes();\n verifyToken(req, res, next);\n expect(res.status).toHaveBeenCalledWith(401);\n });\n\n it("should reject expired tokens", () => {\n const token = jwt.sign({ userId: "u1", role: "user" }, SECRET, { expiresIn: "-1h" });\n const { req, res, next } = createMockReqRes({ authorization: `Bearer ${token}` });\n verifyToken(req, res, next);\n expect(res.status).toHaveBeenCalledWith(401);\n });\n\n it("should accept valid tokens and attach user to request", () => {\n const token = jwt.sign({ userId: "u1", role: "admin" }, SECRET, { expiresIn: "1h" });\n const { req, res, next } = createMockReqRes({ authorization: `Bearer ${token}` });\n verifyToken(req, res, next);\n expect(next).toHaveBeenCalled();\n expect(req.user).toMatchObject({ userId: "u1", role: "admin" });\n });\n});\n',
453
+ },
454
+ }),
455
+ raw: JSON.stringify({ id: "tu-001-07", type: "tool_use" }),
456
+ },
457
+ {
458
+ sessionId: "sess-001",
459
+ eventType: "tool_result",
460
+ timestamp: "2026-02-27T08:15:34Z",
461
+ content: "File written: src/middleware/__tests__/auth.test.ts (32 lines)",
462
+ raw: JSON.stringify({ tool_use_id: "tu-001-07" }),
463
+ },
464
+ {
465
+ sessionId: "sess-001",
466
+ eventType: "tool_use",
467
+ timestamp: "2026-02-27T08:15:35Z",
468
+ content: JSON.stringify({ tool: "Bash", args: { command: "npm test" } }),
469
+ raw: JSON.stringify({ id: "tu-001-08", type: "tool_use" }),
470
+ },
471
+ {
472
+ sessionId: "sess-001",
473
+ eventType: "tool_result",
474
+ timestamp: "2026-02-27T08:15:40Z",
475
+ content: " PASS src/routes/__tests__/login.test.ts\n PASS src/routes/__tests__/protected.test.ts\n PASS src/middleware/__tests__/auth.test.ts\n\nTest Suites: 3 passed, 3 total\nTests: 14 passed, 14 total",
476
+ raw: JSON.stringify({ tool_use_id: "tu-001-08" }),
477
+ },
478
+ {
479
+ sessionId: "sess-001",
480
+ eventType: "usage",
481
+ timestamp: "2026-02-27T08:15:41Z",
482
+ content: JSON.stringify({ input_tokens: 42600, output_tokens: 8100, cost_millicents: 22000 }),
483
+ },
484
+ {
485
+ sessionId: "sess-001",
486
+ eventType: "text",
487
+ timestamp: "2026-02-27T08:15:42Z",
488
+ content: "All 14 tests pass. Here's a summary of the changes:\n\n### Changes made\n\n| File | Action |\n|------|--------|\n| `src/middleware/auth.ts` | Rewrote to verify JWT Bearer tokens |\n| `src/routes/login.ts` | Now issues JWT access + refresh tokens |\n| `src/routes/protected.ts` | Updated to read `req.user` from JWT payload |\n| `src/middleware/__tests__/auth.test.ts` | Updated tests for JWT verification |\n\n### Key decisions\n- **Access token expiry**: 24 hours (configurable via `JWT_SECRET` env var)\n- **Refresh tokens**: 48-byte random hex, stored in DB\n- **Error handling**: Distinguishes expired tokens (401) from invalid tokens (403)",
489
+ },
490
+
491
+ // ── sess-002: completed unit test session ──
492
+
493
+ {
494
+ sessionId: "sess-002",
495
+ eventType: "status",
496
+ timestamp: "2026-02-27T07:30:01Z",
497
+ content: "running",
498
+ },
499
+ {
500
+ sessionId: "sess-002",
501
+ eventType: "system",
502
+ timestamp: "2026-02-27T07:30:02Z",
503
+ content: "You are a QA engineer. Write comprehensive tests covering happy paths, edge cases, error scenarios, and security.\nProject: acme/alpha — Node.js + Express REST API\nTask: Write unit tests for the user registration endpoint",
504
+ raw: JSON.stringify({ systemContext: true }),
505
+ },
506
+ {
507
+ sessionId: "sess-002",
508
+ eventType: "text",
509
+ timestamp: "2026-02-27T07:30:04Z",
510
+ content: "I'll write thorough tests for `POST /api/register`. Let me first read the registration endpoint to understand its behavior.",
511
+ },
512
+ {
513
+ sessionId: "sess-002",
514
+ eventType: "tool_use",
515
+ timestamp: "2026-02-27T07:30:05Z",
516
+ content: JSON.stringify({ tool: "Read", args: { file_path: "src/routes/register.ts" } }),
517
+ raw: JSON.stringify({ id: "tu-002-01", type: "tool_use" }),
518
+ },
519
+ {
520
+ sessionId: "sess-002",
521
+ eventType: "tool_result",
522
+ timestamp: "2026-02-27T07:30:06Z",
523
+ content: 'import { Router } from "express";\nimport bcrypt from "bcrypt";\nimport { db } from "../db.js";\nimport { validateEmail, validatePassword } from "../utils/validators.js";\n\nconst SALT_ROUNDS = 12;\nconst router = Router();\n\nrouter.post("/register", async (req, res) => {\n const { name, email, password } = req.body;\n\n if (!name || !email || !password) {\n return res.status(400).json({ error: "Missing required fields" });\n }\n if (!validateEmail(email)) {\n return res.status(400).json({ error: "Invalid email format" });\n }\n if (!validatePassword(password)) {\n return res.status(400).json({ error: "Password too weak" });\n }\n\n const existing = await db.users.findByEmail(email);\n if (existing) {\n return res.status(409).json({ error: "Email already registered" });\n }\n\n const passwordHash = await bcrypt.hash(password, SALT_ROUNDS);\n const user = await db.users.create({ name, email, passwordHash });\n res.status(201).json({ id: user.id, name: user.name, email: user.email });\n});\n\nexport default router;',
524
+ raw: JSON.stringify({ tool_use_id: "tu-002-01" }),
525
+ },
526
+ {
527
+ sessionId: "sess-002",
528
+ eventType: "text",
529
+ timestamp: "2026-02-27T07:30:10Z",
530
+ content: "Good. The endpoint validates input, checks for duplicates, hashes the password, and creates the user. I'll write tests covering:\n\n1. **Happy path** - valid registration\n2. **Missing fields** - each required field missing\n3. **Invalid email** - malformed email format\n4. **Weak password** - password validation failure\n5. **Duplicate email** - conflict with existing user\n6. **SQL injection** - malicious email input\n7. **XSS payload** - HTML in the name field\n8. **Response shape** - correct status codes and body structure",
531
+ },
532
+ {
533
+ sessionId: "sess-002",
534
+ eventType: "tool_use",
535
+ timestamp: "2026-02-27T07:30:12Z",
536
+ content: JSON.stringify({
537
+ tool: "Write",
538
+ args: {
539
+ file_path: "src/routes/__tests__/register.test.ts",
540
+ content: "// [test content - 8 test cases as described above]",
541
+ },
542
+ }),
543
+ raw: JSON.stringify({ id: "tu-002-02", type: "tool_use" }),
544
+ },
545
+ {
546
+ sessionId: "sess-002",
547
+ eventType: "tool_result",
548
+ timestamp: "2026-02-27T07:30:13Z",
549
+ content: "File written: src/routes/__tests__/register.test.ts (142 lines)",
550
+ raw: JSON.stringify({ tool_use_id: "tu-002-02" }),
551
+ },
552
+ {
553
+ sessionId: "sess-002",
554
+ eventType: "tool_use",
555
+ timestamp: "2026-02-27T07:31:00Z",
556
+ content: JSON.stringify({ tool: "Bash", args: { command: "npm test -- --testPathPattern=register" } }),
557
+ raw: JSON.stringify({ id: "tu-002-03", type: "tool_use" }),
558
+ },
559
+ {
560
+ sessionId: "sess-002",
561
+ eventType: "tool_result",
562
+ timestamp: "2026-02-27T07:31:30Z",
563
+ content: " PASS src/routes/__tests__/register.test.ts\n POST /api/register\n \u2713 should register a valid user (28ms)\n \u2713 should reject missing name (4ms)\n \u2713 should reject missing email (3ms)\n \u2713 should reject missing password (3ms)\n \u2713 should reject invalid email format (5ms)\n \u2713 should reject weak password (4ms)\n \u2713 should reject duplicate email (12ms)\n \u2713 should sanitize HTML in name field (6ms)\n\nTests: 8 passed, 8 total",
564
+ raw: JSON.stringify({ tool_use_id: "tu-002-03" }),
565
+ },
566
+ {
567
+ sessionId: "sess-002",
568
+ eventType: "usage",
569
+ timestamp: "2026-02-27T07:32:00Z",
570
+ content: JSON.stringify({ input_tokens: 31400, output_tokens: 9800, cost_millicents: 18000 }),
571
+ },
572
+ {
573
+ sessionId: "sess-002",
574
+ eventType: "text",
575
+ timestamp: "2026-02-27T07:32:30Z",
576
+ content: "All 8 test cases pass. Created `src/routes/__tests__/register.test.ts` with comprehensive coverage for the registration endpoint including input validation, duplicate detection, and security edge cases.",
577
+ },
578
+ {
579
+ sessionId: "sess-002",
580
+ eventType: "status",
581
+ timestamp: "2026-02-27T07:33:00Z",
582
+ content: "completed",
583
+ },
584
+
585
+ // ── sess-003: database migration (interrupted) ──
586
+
587
+ {
588
+ sessionId: "sess-003",
589
+ eventType: "status",
590
+ timestamp: "2026-02-26T22:45:01Z",
591
+ content: "running",
592
+ },
593
+ {
594
+ sessionId: "sess-003",
595
+ eventType: "system",
596
+ timestamp: "2026-02-26T22:45:02Z",
597
+ content: "You are a database engineer. Focus on safe migrations, zero-downtime deployments, and data integrity.\nProject: acme/alpha — Node.js + Express + PostgreSQL\nTask: Add audit_log table and migrate schema",
598
+ raw: JSON.stringify({ systemContext: true }),
599
+ },
600
+ {
601
+ sessionId: "sess-003",
602
+ eventType: "text",
603
+ timestamp: "2026-02-26T22:45:05Z",
604
+ content: "I need to add an `audit_log` table for tracking all write operations. Let me review the current schema first.",
605
+ },
606
+ {
607
+ sessionId: "sess-003",
608
+ eventType: "tool_use",
609
+ timestamp: "2026-02-26T22:45:06Z",
610
+ content: JSON.stringify({ tool: "Read", args: { file_path: "migrations/001_initial.sql" } }),
611
+ raw: JSON.stringify({ id: "tu-003-01", type: "tool_use" }),
612
+ },
613
+ {
614
+ sessionId: "sess-003",
615
+ eventType: "tool_result",
616
+ timestamp: "2026-02-26T22:45:07Z",
617
+ content: "CREATE TABLE users (\n id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n name TEXT NOT NULL,\n email TEXT UNIQUE NOT NULL,\n password_hash TEXT NOT NULL,\n role TEXT DEFAULT 'user',\n created_at TIMESTAMPTZ DEFAULT now(),\n updated_at TIMESTAMPTZ DEFAULT now()\n);\n\nCREATE TABLE sessions (\n id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n user_id UUID REFERENCES users(id),\n expires_at TIMESTAMPTZ NOT NULL,\n created_at TIMESTAMPTZ DEFAULT now()\n);",
618
+ raw: JSON.stringify({ tool_use_id: "tu-003-01" }),
619
+ },
620
+ {
621
+ sessionId: "sess-003",
622
+ eventType: "text",
623
+ timestamp: "2026-02-26T22:45:12Z",
624
+ content: "I see the schema. I'll create a migration that:\n1. Adds a `last_login_at` column to `users`\n2. Adds an index on `sessions.expires_at` for efficient cleanup queries\n3. Creates the `audit_log` table\n\nLet me write the migration file:",
625
+ },
626
+ {
627
+ sessionId: "sess-003",
628
+ eventType: "tool_use",
629
+ timestamp: "2026-02-26T22:45:14Z",
630
+ content: JSON.stringify({
631
+ tool: "Write",
632
+ args: {
633
+ file_path: "migrations/002_audit_log.sql",
634
+ content: "-- Migration: 002_audit_log\n-- Add audit logging and user activity tracking\n\nBEGIN;\n\n-- 1. Add last_login_at to users\nALTER TABLE users ADD COLUMN last_login_at TIMESTAMPTZ;\n\n-- 2. Index for session cleanup queries\nCREATE INDEX CONCURRENTLY idx_sessions_expires_at ON sessions (expires_at);\n\n-- 3. Audit log table\nCREATE TABLE audit_log (\n id BIGSERIAL PRIMARY KEY,\n user_id UUID REFERENCES users(id),\n action TEXT NOT NULL,\n entity_type TEXT NOT NULL,\n entity_id TEXT NOT NULL,\n old_value JSONB,\n new_value JSONB,\n ip_address INET,\n created_at TIMESTAMPTZ DEFAULT now()\n);\n\nCREATE INDEX idx_audit_log_user ON audit_log (user_id);\nCREATE INDEX idx_audit_log_entity ON audit_log (entity_type, entity_id);\nCREATE INDEX idx_audit_log_created ON audit_log (created_at);\n\nCOMMIT;\n",
635
+ },
636
+ }),
637
+ raw: JSON.stringify({ id: "tu-003-02", type: "tool_use" }),
638
+ },
639
+ {
640
+ sessionId: "sess-003",
641
+ eventType: "tool_result",
642
+ timestamp: "2026-02-26T22:45:15Z",
643
+ content: "File written: migrations/002_audit_log.sql (29 lines)",
644
+ raw: JSON.stringify({ tool_use_id: "tu-003-02" }),
645
+ },
646
+ {
647
+ sessionId: "sess-003",
648
+ eventType: "text",
649
+ timestamp: "2026-02-26T22:46:00Z",
650
+ content: "Now I need to implement the audit middleware that logs write operations. This will intercept POST, PUT, PATCH, and DELETE requests.",
651
+ },
652
+ {
653
+ sessionId: "sess-003",
654
+ eventType: "tool_use",
655
+ timestamp: "2026-02-26T22:46:02Z",
656
+ content: JSON.stringify({ tool: "Bash", args: { command: "npm run migrate -- --dry-run" } }),
657
+ raw: JSON.stringify({ id: "tu-003-03", type: "tool_use" }),
658
+ },
659
+ {
660
+ sessionId: "sess-003",
661
+ eventType: "tool_result",
662
+ timestamp: "2026-02-26T22:46:10Z",
663
+ content: "Dry run: would apply 002_audit_log.sql\n - ALTER TABLE users ADD COLUMN last_login_at\n - CREATE INDEX CONCURRENTLY idx_sessions_expires_at\n - CREATE TABLE audit_log\n - 3 indexes on audit_log\n\nWARNING: CREATE INDEX CONCURRENTLY cannot run inside a transaction block.\nPlease split this into a separate migration step.",
664
+ raw: JSON.stringify({ tool_use_id: "tu-003-03", is_error: true }),
665
+ },
666
+ {
667
+ sessionId: "sess-003",
668
+ eventType: "text",
669
+ timestamp: "2026-02-26T22:46:15Z",
670
+ content: "Good catch from the dry run! `CREATE INDEX CONCURRENTLY` can't run inside a transaction. I need to split the migration into two files: one for the transactional DDL and another for the concurrent index creation.",
671
+ },
672
+ {
673
+ sessionId: "sess-003",
674
+ eventType: "usage",
675
+ timestamp: "2026-02-26T23:00:00Z",
676
+ content: JSON.stringify({ input_tokens: 56300, output_tokens: 14200, cost_millicents: 31000 }),
677
+ },
678
+ {
679
+ sessionId: "sess-003",
680
+ eventType: "status",
681
+ timestamp: "2026-02-26T23:10:00Z",
682
+ content: "interrupted",
683
+ },
684
+
685
+ // ── sess-004: rate limiting implementation (currently running) ──
686
+
687
+ {
688
+ sessionId: "sess-004",
689
+ eventType: "status",
690
+ timestamp: "2026-02-27T09:00:01Z",
691
+ content: "running",
692
+ },
693
+ {
694
+ sessionId: "sess-004",
695
+ eventType: "system",
696
+ timestamp: "2026-02-27T09:00:02Z",
697
+ content: "You are a backend engineer specializing in API infrastructure.\nProject: acme/data-pipeline — ETL pipelines for analytics\nTask: Implement rate limiting for the public API using a token-bucket algorithm",
698
+ raw: JSON.stringify({ systemContext: true }),
699
+ },
700
+ {
701
+ sessionId: "sess-004",
702
+ eventType: "text",
703
+ timestamp: "2026-02-27T09:00:05Z",
704
+ content: "I'll implement rate limiting using a token-bucket algorithm. Let me first survey the existing API infrastructure to find the right integration point.",
705
+ },
706
+ {
707
+ sessionId: "sess-004",
708
+ eventType: "tool_use",
709
+ timestamp: "2026-02-27T09:00:05.5Z",
710
+ content: JSON.stringify({
711
+ tool: "TodoWrite",
712
+ args: {
713
+ todos: [
714
+ { content: "Survey middleware structure", activeForm: "Surveying middleware", status: "in_progress" },
715
+ { content: "Implement token-bucket algorithm", activeForm: "Implementing token bucket", status: "pending" },
716
+ { content: "Add rate limit middleware to app", activeForm: "Adding middleware", status: "pending" },
717
+ { content: "Write tests for rate limiting", activeForm: "Writing tests", status: "pending" },
718
+ { content: "Add Redis adapter for multi-instance", activeForm: "Adding Redis adapter", status: "pending" },
719
+ ],
720
+ },
721
+ }),
722
+ },
723
+ {
724
+ sessionId: "sess-004",
725
+ eventType: "tool_use",
726
+ timestamp: "2026-02-27T09:00:06Z",
727
+ content: JSON.stringify({ tool: "Glob", args: { pattern: "src/middleware/**/*.ts" } }),
728
+ raw: JSON.stringify({ id: "tu-004-01", type: "tool_use" }),
729
+ },
730
+ {
731
+ sessionId: "sess-004",
732
+ eventType: "tool_result",
733
+ timestamp: "2026-02-27T09:00:07Z",
734
+ content: "src/middleware/cors.ts\nsrc/middleware/error-handler.ts\nsrc/middleware/logger.ts\nsrc/middleware/request-id.ts",
735
+ raw: JSON.stringify({ tool_use_id: "tu-004-01" }),
736
+ },
737
+ {
738
+ sessionId: "sess-004",
739
+ eventType: "tool_use",
740
+ timestamp: "2026-02-27T09:00:09Z",
741
+ content: JSON.stringify({
742
+ tool: "TodoWrite",
743
+ args: {
744
+ todos: [
745
+ { content: "Survey middleware structure", activeForm: "Surveying middleware", status: "completed" },
746
+ { content: "Implement token-bucket algorithm", activeForm: "Implementing token bucket", status: "in_progress" },
747
+ { content: "Add rate limit middleware to app", activeForm: "Adding middleware", status: "pending" },
748
+ { content: "Write tests for rate limiting", activeForm: "Writing tests", status: "pending" },
749
+ { content: "Add Redis adapter for multi-instance", activeForm: "Adding Redis adapter", status: "pending" },
750
+ ],
751
+ },
752
+ }),
753
+ },
754
+ {
755
+ sessionId: "sess-004",
756
+ eventType: "text",
757
+ timestamp: "2026-02-27T09:00:10Z",
758
+ content: "Good middleware structure already in place. I'll add a `rate-limiter.ts` module that:\n\n- Uses an **in-memory token bucket** per client IP\n- Supports configurable rate (requests/second) and burst size\n- Returns `429 Too Many Requests` with `Retry-After` header\n- Has a Redis adapter option for multi-instance deployments\n\nLet me implement it:",
759
+ },
760
+ {
761
+ sessionId: "sess-004",
762
+ eventType: "tool_use",
763
+ timestamp: "2026-02-27T09:00:12Z",
764
+ content: JSON.stringify({
765
+ tool: "Write",
766
+ args: {
767
+ file_path: "src/middleware/rate-limiter.ts",
768
+ content: "// Token bucket rate limiter implementation",
769
+ },
770
+ }),
771
+ raw: JSON.stringify({ id: "tu-004-02", type: "tool_use" }),
772
+ },
773
+ {
774
+ sessionId: "sess-004",
775
+ eventType: "tool_result",
776
+ timestamp: "2026-02-27T09:00:13Z",
777
+ content: "File written: src/middleware/rate-limiter.ts (87 lines)",
778
+ raw: JSON.stringify({ tool_use_id: "tu-004-02" }),
779
+ },
780
+ {
781
+ sessionId: "sess-004",
782
+ eventType: "text",
783
+ timestamp: "2026-02-27T09:00:18Z",
784
+ content: "Now I need to wire the rate limiter into the Express middleware stack. It should go after the request-ID middleware but before route handlers.",
785
+ },
786
+
787
+ // ── sess-005: suspended session (DB pooling refactor) ──
788
+
789
+ {
790
+ sessionId: "sess-005",
791
+ eventType: "status",
792
+ timestamp: "2026-02-27T06:00:01Z",
793
+ content: "running",
794
+ },
795
+ {
796
+ sessionId: "sess-005",
797
+ eventType: "system",
798
+ timestamp: "2026-02-27T06:00:02Z",
799
+ content: "You are a database specialist. Focus on connection management, performance, and multi-tenant isolation.\nProject: acme/alpha — Node.js + Express + PostgreSQL\nTask: Refactor database connection pooling for multi-tenant isolation",
800
+ raw: JSON.stringify({ systemContext: true }),
801
+ },
802
+ {
803
+ sessionId: "sess-005",
804
+ eventType: "text",
805
+ timestamp: "2026-02-27T06:00:05Z",
806
+ content: "I'll refactor the DB layer to support per-tenant connection pools. This is needed because the current single-pool design means one slow tenant can exhaust connections for everyone.",
807
+ },
808
+ {
809
+ sessionId: "sess-005",
810
+ eventType: "tool_use",
811
+ timestamp: "2026-02-27T06:00:06Z",
812
+ content: JSON.stringify({ tool: "Read", args: { file_path: "src/db/pool.ts" } }),
813
+ raw: JSON.stringify({ id: "tu-005-01", type: "tool_use" }),
814
+ },
815
+ {
816
+ sessionId: "sess-005",
817
+ eventType: "tool_result",
818
+ timestamp: "2026-02-27T06:00:07Z",
819
+ content: 'import pg from "pg";\n\nconst pool = new pg.Pool({\n connectionString: process.env.DATABASE_URL,\n max: 20,\n idleTimeoutMillis: 30000,\n});\n\nexport { pool };',
820
+ raw: JSON.stringify({ tool_use_id: "tu-005-01" }),
821
+ },
822
+ {
823
+ sessionId: "sess-005",
824
+ eventType: "text",
825
+ timestamp: "2026-02-27T06:00:12Z",
826
+ content: "The current pool is a single shared instance with `max: 20`. For multi-tenant isolation, I need:\n\n1. **A `PoolManager` class** that creates and caches per-tenant pools\n2. **Configurable limits** per tenant (max connections, idle timeout)\n3. **Health checks** with automatic pool eviction for unhealthy connections\n4. **Graceful shutdown** that drains all pools",
827
+ },
828
+ {
829
+ sessionId: "sess-005",
830
+ eventType: "tool_use",
831
+ timestamp: "2026-02-27T06:00:14Z",
832
+ content: JSON.stringify({ tool: "Grep", args: { pattern: "import.*pool|from.*pool", path: "src/", output_mode: "content" } }),
833
+ raw: JSON.stringify({ id: "tu-005-02", type: "tool_use" }),
834
+ },
835
+ {
836
+ sessionId: "sess-005",
837
+ eventType: "tool_result",
838
+ timestamp: "2026-02-27T06:00:15Z",
839
+ content: 'src/db/index.ts:1:import { pool } from "./pool.js";\nsrc/routes/users.ts:2:import { pool } from "../db/pool.js";\nsrc/routes/register.ts:3:import { pool } from "../db/pool.js";\nsrc/routes/admin.ts:4:import { pool } from "../db/pool.js";\nsrc/services/billing.ts:2:import { pool } from "../db/pool.js";\nsrc/services/analytics.ts:1:import { pool } from "../db/pool.js";',
840
+ raw: JSON.stringify({ tool_use_id: "tu-005-02" }),
841
+ },
842
+ {
843
+ sessionId: "sess-005",
844
+ eventType: "text",
845
+ timestamp: "2026-02-27T06:00:20Z",
846
+ content: "Six files import the pool directly. I'll need to:\n1. Create a new `PoolManager` abstraction\n2. Update all consumers to use `getPool(tenantId)` instead of the global pool\n3. Add middleware to extract tenant ID from the request\n\nThis is a significant refactor. Let me start with the PoolManager class.",
847
+ },
848
+ {
849
+ sessionId: "sess-005",
850
+ eventType: "usage",
851
+ timestamp: "2026-02-27T06:30:00Z",
852
+ content: JSON.stringify({ input_tokens: 67800, output_tokens: 19300, cost_millicents: 38000 }),
853
+ },
854
+ {
855
+ sessionId: "sess-005",
856
+ eventType: "status",
857
+ timestamp: "2026-02-27T06:30:01Z",
858
+ content: "suspended",
859
+ },
860
+ ];
861
+
862
+ // ─── Workspaces ─────────────────────────────────────
863
+
864
+ /** Sample workspaces at different lifecycle stages. */
865
+ export const MOCK_WORKSPACES: Workspace[] = [
866
+ {
867
+ id: "proj-alpha",
868
+ name: "Workspace Alpha",
869
+ description: "Core platform API and authentication services",
870
+ repoUrl: "https://github.com/acme/alpha",
871
+ linkedEnvironmentIds: ["env-local-01", "env-docker-01"],
872
+ status: "active",
873
+ workingDirectory: "",
874
+ useWorktrees: true,
875
+ defaultPersonaId: "",
876
+ tokenBudget: 0,
877
+ costBudgetMillicents: 0,
878
+ createdAt: "2026-01-15T10:00:00Z",
879
+ updatedAt: "2026-01-15T10:00:00Z",
880
+ },
881
+ {
882
+ id: "proj-beta",
883
+ name: "Data Pipeline",
884
+ description: "ETL pipelines for analytics and reporting",
885
+ repoUrl: "https://github.com/acme/data-pipeline",
886
+ linkedEnvironmentIds: ["env-docker-01"],
887
+ status: "active",
888
+ workingDirectory: "",
889
+ useWorktrees: true,
890
+ defaultPersonaId: "",
891
+ tokenBudget: 0,
892
+ costBudgetMillicents: 0,
893
+ createdAt: "2026-02-01T14:30:00Z",
894
+ updatedAt: "2026-02-01T14:30:00Z",
895
+ },
896
+ {
897
+ id: "proj-gamma",
898
+ name: "Mobile App",
899
+ description: "React Native cross-platform mobile application",
900
+ repoUrl: "",
901
+ linkedEnvironmentIds: ["env-local-01"],
902
+ status: "archived",
903
+ workingDirectory: "",
904
+ useWorktrees: true,
905
+ defaultPersonaId: "",
906
+ tokenBudget: 0,
907
+ costBudgetMillicents: 0,
908
+ createdAt: "2025-11-20T09:00:00Z",
909
+ updatedAt: "2025-11-20T09:00:00Z",
910
+ },
911
+ ];
912
+
913
+ // ─── Tasks ──────────────────────────────────────────
914
+
915
+ /** Sample tasks demonstrating every status in the lifecycle, including parent/child hierarchy. */
916
+ export const MOCK_TASKS: TaskData[] = [
917
+ // ── Root tasks for proj-alpha ──────────────────────
918
+ {
919
+ id: "task-001",
920
+ workspaceId: "proj-alpha",
921
+ title: "Implement JWT authentication",
922
+ description: "Replace session-based auth with JWT tokens across all protected routes",
923
+ status: "working",
924
+ branch: "feat/jwt-auth",
925
+ latestSessionId: "sess-001",
926
+ dependsOn: [],
927
+ reviewNotes: undefined,
928
+ sortOrder: 1,
929
+ createdAt: "2026-02-25T10:00:00Z",
930
+ parentTaskId: "",
931
+ depth: 0,
932
+ childTaskIds: ["task-001a", "task-001b", "task-001c"],
933
+ canDecompose: true,
934
+ defaultPersonaId: "",
935
+ workpad: JSON.stringify({
936
+ status: "in_progress",
937
+ summary: "Design and implement JWT-based authentication with access/refresh tokens, replacing session-based auth.",
938
+ extra: {
939
+ approach: "Replace express-session with JWT Bearer tokens. Access tokens expire in 24h, refresh tokens stored in DB.",
940
+ files_changed: ["src/middleware/auth.ts", "src/routes/login.ts", "src/routes/protected.ts"],
941
+ decisions: [
942
+ "Chose jsonwebtoken over jose for consistency with existing codebase",
943
+ "24h access token expiry — long enough for dev sessions, short enough for security",
944
+ "Refresh tokens are 48-byte random hex stored in DB with user association",
945
+ ],
946
+ blockers: "Need to verify token rotation strategy with security team before merging",
947
+ },
948
+ }),
949
+ tokenBudget: 0,
950
+ costBudgetMillicents: 0,
951
+ },
952
+ // ── Children of task-001 ───────────────────────────
953
+ {
954
+ id: "task-001a",
955
+ workspaceId: "proj-alpha",
956
+ title: "Design token schema",
957
+ description: "Define JWT payload structure, expiry, and refresh token strategy",
958
+ status: "complete",
959
+ branch: "feat/jwt-auth/design-token-schema",
960
+ latestSessionId: "",
961
+ dependsOn: [],
962
+ reviewNotes: undefined,
963
+ sortOrder: 1,
964
+ createdAt: "2026-02-25T10:10:00Z",
965
+ parentTaskId: "task-001",
966
+ depth: 1,
967
+ childTaskIds: [],
968
+ canDecompose: false,
969
+ defaultPersonaId: "",
970
+ workpad: "",
971
+ tokenBudget: 0,
972
+ costBudgetMillicents: 0,
973
+ },
974
+ {
975
+ id: "task-001b",
976
+ workspaceId: "proj-alpha",
977
+ title: "Implement auth middleware",
978
+ description: "Build Express middleware that verifies JWT Bearer tokens",
979
+ status: "working",
980
+ branch: "feat/jwt-auth/implement-auth-middleware",
981
+ latestSessionId: "sess-001",
982
+ dependsOn: [],
983
+ reviewNotes: undefined,
984
+ sortOrder: 2,
985
+ createdAt: "2026-02-25T10:15:00Z",
986
+ parentTaskId: "task-001",
987
+ depth: 1,
988
+ childTaskIds: [],
989
+ canDecompose: false,
990
+ defaultPersonaId: "",
991
+ workpad: "",
992
+ tokenBudget: 0,
993
+ costBudgetMillicents: 0,
994
+ },
995
+ {
996
+ id: "task-001c",
997
+ workspaceId: "proj-alpha",
998
+ title: "Write auth integration tests",
999
+ description: "End-to-end tests for login flow, token refresh, and protected route access",
1000
+ status: "not_started",
1001
+ branch: "",
1002
+ latestSessionId: "",
1003
+ dependsOn: ["task-001b"],
1004
+ reviewNotes: undefined,
1005
+ sortOrder: 3,
1006
+ createdAt: "2026-02-25T10:20:00Z",
1007
+ parentTaskId: "task-001",
1008
+ depth: 1,
1009
+ childTaskIds: [],
1010
+ canDecompose: false,
1011
+ defaultPersonaId: "",
1012
+ workpad: "",
1013
+ tokenBudget: 0,
1014
+ costBudgetMillicents: 0,
1015
+ },
1016
+ // ── Remaining root tasks for proj-alpha ────────────
1017
+ {
1018
+ id: "task-002",
1019
+ workspaceId: "proj-alpha",
1020
+ title: "Add rate limiting",
1021
+ description: "Implement token-bucket rate limiting for public API endpoints",
1022
+ status: "not_started",
1023
+ branch: "",
1024
+ latestSessionId: "",
1025
+ dependsOn: ["task-001"],
1026
+ reviewNotes: undefined,
1027
+ sortOrder: 2,
1028
+ createdAt: "2026-02-25T10:05:00Z",
1029
+ parentTaskId: "",
1030
+ depth: 0,
1031
+ childTaskIds: [],
1032
+ canDecompose: false,
1033
+ defaultPersonaId: "",
1034
+ workpad: "",
1035
+ tokenBudget: 0,
1036
+ costBudgetMillicents: 0,
1037
+ },
1038
+ {
1039
+ id: "task-003",
1040
+ workspaceId: "proj-alpha",
1041
+ title: "Set up OpenAPI documentation",
1042
+ description: "Generate Swagger docs from route decorators and serve at /api/docs",
1043
+ status: "paused",
1044
+ branch: "feat/openapi-docs",
1045
+ latestSessionId: "sess-002",
1046
+ dependsOn: [],
1047
+ reviewNotes: undefined,
1048
+ sortOrder: 3,
1049
+ createdAt: "2026-02-24T16:00:00Z",
1050
+ parentTaskId: "",
1051
+ depth: 0,
1052
+ childTaskIds: [],
1053
+ canDecompose: false,
1054
+ defaultPersonaId: "",
1055
+ workpad: "",
1056
+ tokenBudget: 0,
1057
+ costBudgetMillicents: 0,
1058
+ },
1059
+ {
1060
+ id: "task-004",
1061
+ workspaceId: "proj-alpha",
1062
+ title: "Database connection pooling",
1063
+ description: "Switch from single connection to a connection pool with health checks",
1064
+ status: "complete",
1065
+ branch: "feat/db-pool",
1066
+ latestSessionId: "",
1067
+ dependsOn: [],
1068
+ reviewNotes: undefined,
1069
+ sortOrder: 4,
1070
+ createdAt: "2026-02-23T11:00:00Z",
1071
+ parentTaskId: "",
1072
+ depth: 0,
1073
+ childTaskIds: [],
1074
+ canDecompose: false,
1075
+ defaultPersonaId: "",
1076
+ workpad: "",
1077
+ tokenBudget: 0,
1078
+ costBudgetMillicents: 0,
1079
+ },
1080
+ {
1081
+ id: "task-005",
1082
+ workspaceId: "proj-alpha",
1083
+ title: "Fix N+1 query in user list",
1084
+ description: "Use a JOIN instead of per-row lookups in GET /api/users",
1085
+ status: "not_started",
1086
+ branch: "fix/user-list-n1",
1087
+ latestSessionId: "",
1088
+ dependsOn: [],
1089
+ reviewNotes: undefined,
1090
+ sortOrder: 5,
1091
+ createdAt: "2026-02-22T09:30:00Z",
1092
+ parentTaskId: "",
1093
+ depth: 0,
1094
+ childTaskIds: [],
1095
+ canDecompose: false,
1096
+ defaultPersonaId: "",
1097
+ workpad: "",
1098
+ tokenBudget: 0,
1099
+ costBudgetMillicents: 0,
1100
+ },
1101
+ // ── Tasks for proj-beta ────────────────────────────
1102
+ {
1103
+ id: "task-006",
1104
+ workspaceId: "proj-beta",
1105
+ title: "Add Parquet export support",
1106
+ description: "Allow pipeline outputs to be written as Parquet files for Spark consumption",
1107
+ status: "working",
1108
+ branch: "feat/parquet-export",
1109
+ latestSessionId: "",
1110
+ dependsOn: [],
1111
+ reviewNotes: undefined,
1112
+ sortOrder: 1,
1113
+ createdAt: "2026-02-26T08:00:00Z",
1114
+ parentTaskId: "",
1115
+ depth: 0,
1116
+ childTaskIds: ["task-006a", "task-006b", "task-006c", "task-006d"],
1117
+ canDecompose: true,
1118
+ defaultPersonaId: "",
1119
+ workpad: JSON.stringify({
1120
+ status: "in_progress",
1121
+ summary: "Plan for adding Parquet export support, including compression options and performance benchmarks.",
1122
+ extra: {
1123
+ approach: "Use apache-arrow npm package for Parquet write support. Map internal column types to Arrow type system.",
1124
+ compression: { supported: ["snappy", "zstd", "gzip"], default: "snappy" },
1125
+ benchmarks: { "1M rows snappy": "2.3s", "1M rows zstd": "3.1s", "1M rows gzip": "4.8s" },
1126
+ },
1127
+ }),
1128
+ tokenBudget: 0,
1129
+ costBudgetMillicents: 0,
1130
+ },
1131
+ // ── Children of task-006 ───────────────────────────
1132
+ {
1133
+ id: "task-006a",
1134
+ workspaceId: "proj-beta",
1135
+ title: "Define Parquet schema mapping",
1136
+ description: "Map internal column types to Arrow/Parquet type system",
1137
+ status: "complete",
1138
+ branch: "feat/parquet-export/define-parquet-schema-mapping",
1139
+ latestSessionId: "",
1140
+ dependsOn: [],
1141
+ reviewNotes: undefined,
1142
+ sortOrder: 1,
1143
+ createdAt: "2026-02-26T08:05:00Z",
1144
+ parentTaskId: "task-006",
1145
+ depth: 1,
1146
+ childTaskIds: [],
1147
+ canDecompose: false,
1148
+ defaultPersonaId: "",
1149
+ workpad: "",
1150
+ tokenBudget: 0,
1151
+ costBudgetMillicents: 0,
1152
+ },
1153
+ {
1154
+ id: "task-006b",
1155
+ workspaceId: "proj-beta",
1156
+ title: "Implement row-group writer",
1157
+ description: "Write buffered row groups with configurable batch size",
1158
+ status: "complete",
1159
+ branch: "feat/parquet-export/implement-row-group-writer",
1160
+ latestSessionId: "",
1161
+ dependsOn: ["task-006a"],
1162
+ reviewNotes: undefined,
1163
+ sortOrder: 2,
1164
+ createdAt: "2026-02-26T08:10:00Z",
1165
+ parentTaskId: "task-006",
1166
+ depth: 1,
1167
+ childTaskIds: [],
1168
+ canDecompose: false,
1169
+ defaultPersonaId: "",
1170
+ workpad: "",
1171
+ tokenBudget: 0,
1172
+ costBudgetMillicents: 0,
1173
+ },
1174
+ {
1175
+ id: "task-006c",
1176
+ workspaceId: "proj-beta",
1177
+ title: "Add compression options",
1178
+ description: "Support Snappy, ZSTD, and GZIP compression for Parquet output",
1179
+ status: "working",
1180
+ branch: "feat/parquet-export/add-compression-options",
1181
+ latestSessionId: "sess-004",
1182
+ dependsOn: ["task-006b"],
1183
+ reviewNotes: undefined,
1184
+ sortOrder: 3,
1185
+ createdAt: "2026-02-26T08:15:00Z",
1186
+ parentTaskId: "task-006",
1187
+ depth: 1,
1188
+ childTaskIds: [],
1189
+ canDecompose: false,
1190
+ defaultPersonaId: "",
1191
+ workpad: "",
1192
+ tokenBudget: 0,
1193
+ costBudgetMillicents: 0,
1194
+ },
1195
+ {
1196
+ id: "task-006d",
1197
+ workspaceId: "proj-beta",
1198
+ title: "Write Parquet integration tests",
1199
+ description: "Round-trip tests: write Parquet, read back with pyarrow, verify data integrity",
1200
+ status: "not_started",
1201
+ branch: "",
1202
+ latestSessionId: "",
1203
+ dependsOn: ["task-006c"],
1204
+ reviewNotes: undefined,
1205
+ sortOrder: 4,
1206
+ createdAt: "2026-02-26T08:20:00Z",
1207
+ parentTaskId: "task-006",
1208
+ depth: 1,
1209
+ childTaskIds: [],
1210
+ canDecompose: false,
1211
+ defaultPersonaId: "",
1212
+ workpad: "",
1213
+ tokenBudget: 0,
1214
+ costBudgetMillicents: 0,
1215
+ },
1216
+ // ── Remaining root tasks for proj-beta ─────────────
1217
+ {
1218
+ id: "task-007",
1219
+ workspaceId: "proj-beta",
1220
+ title: "Implement incremental loads",
1221
+ description: "Track watermarks so pipelines only process new/changed rows",
1222
+ status: "working",
1223
+ branch: "feat/incremental",
1224
+ latestSessionId: "sess-004",
1225
+ dependsOn: [],
1226
+ reviewNotes: undefined,
1227
+ sortOrder: 2,
1228
+ createdAt: "2026-02-26T08:30:00Z",
1229
+ parentTaskId: "",
1230
+ depth: 0,
1231
+ childTaskIds: ["task-007a", "task-007b"],
1232
+ canDecompose: true,
1233
+ defaultPersonaId: "",
1234
+ workpad: "",
1235
+ tokenBudget: 0,
1236
+ costBudgetMillicents: 0,
1237
+ },
1238
+ // ── Children of task-007 ───────────────────────────
1239
+ {
1240
+ id: "task-007a",
1241
+ workspaceId: "proj-beta",
1242
+ title: "Design watermark storage",
1243
+ description: "Define schema for per-pipeline high-watermark tracking",
1244
+ status: "complete",
1245
+ branch: "feat/incremental/design-watermark-storage",
1246
+ latestSessionId: "",
1247
+ dependsOn: [],
1248
+ reviewNotes: undefined,
1249
+ sortOrder: 1,
1250
+ createdAt: "2026-02-26T08:35:00Z",
1251
+ parentTaskId: "task-007",
1252
+ depth: 1,
1253
+ childTaskIds: [],
1254
+ canDecompose: false,
1255
+ defaultPersonaId: "",
1256
+ workpad: "",
1257
+ tokenBudget: 0,
1258
+ costBudgetMillicents: 0,
1259
+ },
1260
+ {
1261
+ id: "task-007b",
1262
+ workspaceId: "proj-beta",
1263
+ title: "Implement change detection query",
1264
+ description: "Generate WHERE clauses from watermarks to fetch only changed rows",
1265
+ status: "failed",
1266
+ branch: "feat/incremental/implement-change-detection-query",
1267
+ latestSessionId: "",
1268
+ dependsOn: ["task-007a"],
1269
+ reviewNotes: undefined,
1270
+ sortOrder: 2,
1271
+ createdAt: "2026-02-26T08:40:00Z",
1272
+ parentTaskId: "task-007",
1273
+ depth: 1,
1274
+ childTaskIds: [],
1275
+ canDecompose: false,
1276
+ defaultPersonaId: "",
1277
+ workpad: "",
1278
+ tokenBudget: 0,
1279
+ costBudgetMillicents: 0,
1280
+ },
1281
+ {
1282
+ id: "task-008",
1283
+ workspaceId: "proj-beta",
1284
+ title: "Add pipeline monitoring dashboard",
1285
+ description: "Real-time metrics for pipeline throughput, latency, and error rates",
1286
+ status: "paused",
1287
+ branch: "feat/monitoring",
1288
+ latestSessionId: "",
1289
+ dependsOn: [],
1290
+ reviewNotes: undefined,
1291
+ sortOrder: 3,
1292
+ createdAt: "2026-02-26T09:00:00Z",
1293
+ parentTaskId: "",
1294
+ depth: 0,
1295
+ childTaskIds: [],
1296
+ canDecompose: false,
1297
+ defaultPersonaId: "",
1298
+ workpad: "",
1299
+ tokenBudget: 0,
1300
+ costBudgetMillicents: 0,
1301
+ },
1302
+ ];
1303
+
1304
+ // ─── Findings ───────────────────────────────────────
1305
+
1306
+ /** Sample findings across every category to exercise the FindingsPanel styling. */
1307
+ export const MOCK_FINDINGS: FindingData[] = [
1308
+ {
1309
+ id: "find-001",
1310
+ workspaceId: "proj-alpha",
1311
+ taskId: "task-001",
1312
+ sessionId: "sess-001",
1313
+ category: "architecture",
1314
+ title: "Auth middleware is tightly coupled to Express",
1315
+ content:
1316
+ "The current auth middleware directly references Express Request/Response types. Consider extracting a framework-agnostic token verification layer so we can reuse it in the WebSocket auth path.",
1317
+ tags: ["auth", "decoupling", "middleware"],
1318
+ createdAt: "2026-02-27T08:16:00Z",
1319
+ },
1320
+ {
1321
+ id: "find-002",
1322
+ workspaceId: "proj-alpha",
1323
+ taskId: "task-003",
1324
+ sessionId: "sess-002",
1325
+ category: "api",
1326
+ title: "Missing pagination on GET /api/users",
1327
+ content:
1328
+ "The users endpoint returns all rows without limit/offset. For datasets over 10k rows this will cause timeouts. Recommend cursor-based pagination with a default page size of 50.",
1329
+ tags: ["api", "pagination", "performance"],
1330
+ createdAt: "2026-02-27T07:31:00Z",
1331
+ },
1332
+ {
1333
+ id: "find-003",
1334
+ workspaceId: "proj-alpha",
1335
+ taskId: "task-005",
1336
+ sessionId: "sess-003",
1337
+ category: "bug",
1338
+ title: "Race condition in session cleanup",
1339
+ content:
1340
+ "When two requests hit /api/logout concurrently, the second call throws a 500 because the session row has already been deleted. Needs an idempotent DELETE or a conditional check.",
1341
+ tags: ["bug", "concurrency", "sessions"],
1342
+ createdAt: "2026-02-26T22:50:00Z",
1343
+ },
1344
+ {
1345
+ id: "find-004",
1346
+ workspaceId: "proj-alpha",
1347
+ taskId: "task-004",
1348
+ sessionId: "",
1349
+ category: "decision",
1350
+ title: "Chose pg-pool over knex connection pool",
1351
+ content:
1352
+ "pg-pool gives us direct control over idle timeout, max connections, and health check queries. Knex wraps pg-pool anyway and adds overhead we don't need since we write raw SQL.",
1353
+ tags: ["database", "decision", "postgres"],
1354
+ createdAt: "2026-02-23T11:30:00Z",
1355
+ },
1356
+ {
1357
+ id: "find-005",
1358
+ workspaceId: "proj-alpha",
1359
+ taskId: "",
1360
+ sessionId: "",
1361
+ category: "dependency",
1362
+ title: "jsonwebtoken has 3 high-severity CVEs",
1363
+ content:
1364
+ "The jsonwebtoken package (v8.x) has known vulnerabilities. Consider migrating to jose which is maintained, supports ESM, and covers the same JWS/JWE surface area with zero dependencies.",
1365
+ tags: ["security", "dependency", "jwt"],
1366
+ createdAt: "2026-02-27T08:20:00Z",
1367
+ },
1368
+ {
1369
+ id: "find-006",
1370
+ workspaceId: "proj-alpha",
1371
+ taskId: "task-001",
1372
+ sessionId: "sess-001",
1373
+ category: "pattern",
1374
+ title: "Consistent error response shape",
1375
+ content:
1376
+ 'All error responses should follow the shape `{ error: string, code: string, details?: unknown }`. Currently some routes return `{ message: string }` and others return `{ error: string }`.',
1377
+ tags: ["api", "consistency", "error-handling"],
1378
+ createdAt: "2026-02-27T08:17:00Z",
1379
+ },
1380
+ {
1381
+ id: "find-007",
1382
+ workspaceId: "proj-beta",
1383
+ taskId: "task-007",
1384
+ sessionId: "sess-004",
1385
+ category: "architecture",
1386
+ title: "Watermark storage should be pluggable",
1387
+ content:
1388
+ "The incremental load watermarks are currently stored in a local SQLite file. For production multi-worker scenarios, this needs to be backed by a shared store (Redis or Postgres).",
1389
+ tags: ["architecture", "pipeline", "scalability"],
1390
+ createdAt: "2026-02-27T09:05:00Z",
1391
+ },
1392
+ ];
1393
+
1394
+ // ─── Tokens ──────────────────────────────────────────
1395
+
1396
+ /** Sample tokens for the settings panel. */
1397
+ export const MOCK_TOKENS: TokenInfo[] = [
1398
+ {
1399
+ name: "anthropic",
1400
+ tokenType: "env_var",
1401
+ envVar: "ANTHROPIC_API_KEY",
1402
+ filePath: "",
1403
+ expiresAt: "",
1404
+ },
1405
+ {
1406
+ name: "github",
1407
+ tokenType: "env_var",
1408
+ envVar: "GITHUB_TOKEN",
1409
+ filePath: "",
1410
+ expiresAt: "2026-12-31T23:59:59Z",
1411
+ },
1412
+ {
1413
+ name: "gcp-service-account",
1414
+ tokenType: "file",
1415
+ envVar: "",
1416
+ filePath: "/home/user/.config/gcloud/credentials.json",
1417
+ expiresAt: "",
1418
+ },
1419
+ ];
1420
+
1421
+ // ─── Personas ───────────────────────────────────────
1422
+
1423
+ /** Sample personas demonstrating different agent specializations. */
1424
+ export const MOCK_PERSONAS: PersonaData[] = [
1425
+ {
1426
+ id: "persona-arch",
1427
+ name: "Software Architect",
1428
+ description: "Designs system architecture, defines interfaces, and decomposes large features into implementable tasks.",
1429
+ systemPrompt: "You are a senior software architect. Focus on clean interfaces, separation of concerns, and scalable patterns. When decomposing work, create concrete subtasks with clear acceptance criteria.",
1430
+ toolConfig: JSON.stringify({ allowedTools: ["Read", "Grep", "Glob", "Write", "Edit"] }),
1431
+ runtime: "claude-code",
1432
+ model: "claude-sonnet-4-6",
1433
+ maxTurns: 50,
1434
+ mcpServers: "[]",
1435
+ createdAt: "2026-02-20T10:00:00Z",
1436
+ updatedAt: "2026-02-20T10:00:00Z",
1437
+ type: "agent",
1438
+ script: "",
1439
+ allowedMcpTools: ["task_create", "task_update", "task_list", "finding_post", "finding_list", "knowledge_search", "knowledge_create_node", "session_spawn"],
1440
+ },
1441
+ {
1442
+ id: "persona-fe",
1443
+ name: "Frontend Engineer",
1444
+ description: "React specialist — builds UI components, writes CSS, implements routing, and creates Playwright tests.",
1445
+ systemPrompt: "You are a frontend engineer specializing in React, TypeScript, and modern CSS. Follow component composition patterns, use semantic HTML, and write accessible markup.",
1446
+ toolConfig: JSON.stringify({ allowedTools: ["Read", "Grep", "Glob", "Write", "Edit", "Bash"] }),
1447
+ runtime: "claude-code",
1448
+ model: "claude-sonnet-4-6",
1449
+ maxTurns: 100,
1450
+ mcpServers: "[]",
1451
+ createdAt: "2026-02-20T10:05:00Z",
1452
+ updatedAt: "2026-02-20T10:05:00Z",
1453
+ type: "agent",
1454
+ script: "",
1455
+ allowedMcpTools: [],
1456
+ },
1457
+ {
1458
+ id: "persona-reviewer",
1459
+ name: "Code Reviewer",
1460
+ description: "Reviews diffs for correctness, security, and style. Posts findings for issues discovered.",
1461
+ systemPrompt: "You are a meticulous code reviewer. Check for security vulnerabilities, performance issues, and style consistency. Post findings for anything noteworthy.",
1462
+ toolConfig: JSON.stringify({ allowedTools: ["Read", "Grep", "Glob"] }),
1463
+ runtime: "claude-code",
1464
+ model: "claude-sonnet-4-6",
1465
+ maxTurns: 30,
1466
+ mcpServers: "[]",
1467
+ createdAt: "2026-02-20T10:10:00Z",
1468
+ updatedAt: "2026-02-20T10:10:00Z",
1469
+ type: "agent",
1470
+ script: "",
1471
+ allowedMcpTools: ["finding_post", "finding_list", "knowledge_search"],
1472
+ },
1473
+ {
1474
+ id: "persona-tester",
1475
+ name: "QA Engineer",
1476
+ description: "Writes comprehensive test suites — unit tests, integration tests, and E2E Playwright specs.",
1477
+ systemPrompt: "You are a QA engineer focused on test coverage. Write tests that cover happy paths, edge cases, error scenarios, and accessibility. Use Playwright for E2E tests.",
1478
+ toolConfig: JSON.stringify({ allowedTools: ["Read", "Grep", "Glob", "Write", "Edit", "Bash"] }),
1479
+ runtime: "claude-code",
1480
+ model: "claude-sonnet-4-6",
1481
+ maxTurns: 80,
1482
+ mcpServers: "[]",
1483
+ createdAt: "2026-02-20T10:15:00Z",
1484
+ updatedAt: "2026-02-20T10:15:00Z",
1485
+ type: "agent",
1486
+ script: "",
1487
+ allowedMcpTools: [],
1488
+ },
1489
+ {
1490
+ id: "persona-lint",
1491
+ name: "Lint & Format",
1492
+ description: "Scripted persona — runs ESLint and Prettier on changed files, auto-fixes violations, and posts a findings summary.",
1493
+ systemPrompt: "",
1494
+ toolConfig: "{}",
1495
+ runtime: "genaiscript",
1496
+ model: "",
1497
+ maxTurns: 1,
1498
+ mcpServers: "[]",
1499
+ createdAt: "2026-02-21T09:00:00Z",
1500
+ updatedAt: "2026-02-21T09:00:00Z",
1501
+ type: "script",
1502
+ script: 'const files = env.files.filter(f => /\\.(ts|tsx|js)$/.test(f.filename));\nfor (const f of files) {\n const eslintResult = await host.exec("npx", ["eslint", "--fix", f.filename]);\n if (eslintResult.exitCode !== 0) {\n env.findings.push({ category: "lint", title: `Lint issues in ${f.filename}`, content: eslintResult.stderr });\n }\n const prettierResult = await host.exec("npx", ["prettier", "--write", f.filename]);\n if (prettierResult.exitCode !== 0) {\n env.findings.push({ category: "format", title: `Prettier issues in ${f.filename}`, content: prettierResult.stderr });\n }\n}\nenv.findings.push({ category: "summary", title: "Lint & format pass complete", content: `Ran ESLint and Prettier on ${files.length} files` });',
1503
+ allowedMcpTools: [],
1504
+ },
1505
+ ];
1506
+
1507
+ // ─── Task Sessions ──────────────────────────────────
1508
+
1509
+ /** Sample session history per task, showing multiple attempts. */
1510
+ export const MOCK_TASK_SESSIONS: Record<string, Session[]> = {
1511
+ "task-001b": [
1512
+ {
1513
+ id: "sess-001-prev",
1514
+ environmentId: "env-local-01",
1515
+ runtime: "claude-code",
1516
+ status: "stopped",
1517
+ endReason: "interrupted",
1518
+ prompt: "Implement auth middleware",
1519
+ startedAt: "2026-02-26T14:00:00Z",
1520
+ },
1521
+ {
1522
+ id: "sess-001",
1523
+ environmentId: "env-local-01",
1524
+ runtime: "claude-code",
1525
+ status: "running",
1526
+ prompt: "Implement auth middleware",
1527
+ startedAt: "2026-02-27T08:15:00Z",
1528
+ },
1529
+ ],
1530
+ "task-006c": [
1531
+ {
1532
+ id: "sess-004",
1533
+ environmentId: "env-docker-01",
1534
+ runtime: "claude-code",
1535
+ status: "running",
1536
+ prompt: "Add compression options",
1537
+ startedAt: "2026-02-27T09:00:00Z",
1538
+ },
1539
+ ],
1540
+ };
1541
+
1542
+ // ─── Stream Scenarios ───────────────────────────────
1543
+
1544
+ /**
1545
+ * A single step in a mock event stream, describing the delay before
1546
+ * emitting and the event content (without sessionId, which is assigned
1547
+ * at playback time).
1548
+ */
1549
+ export interface MockStreamStep {
1550
+ /** Milliseconds to wait before emitting this event. */
1551
+ delayMs: number;
1552
+ /** The event to emit (sessionId is filled in at playback time). */
1553
+ event: Omit<SessionEvent, "sessionId">;
1554
+ }
1555
+
1556
+ /**
1557
+ * A complete mock stream scenario describing a sequence of events,
1558
+ * optionally pausing midway through to wait for user input.
1559
+ */
1560
+ export interface MockStreamScenario {
1561
+ /** Human-readable label for console logging. */
1562
+ label: string;
1563
+ /** Whether the scenario pauses for user input partway through. */
1564
+ pauseForInput: boolean;
1565
+ /** Step index after which to pause (only when pauseForInput is true). */
1566
+ pauseAfterStep?: number;
1567
+ /** Steps to play after the user provides input. */
1568
+ resumeSteps?: MockStreamStep[];
1569
+ /** The main sequence of steps. */
1570
+ steps: MockStreamStep[];
1571
+ }
1572
+
1573
+ /** Timestamp helper that returns an ISO string offset from "now". */
1574
+ function ts(offsetMs: number): string {
1575
+ return new Date(Date.now() + offsetMs).toISOString();
1576
+ }
1577
+
1578
+ /**
1579
+ * Pre-built scenarios that exercise different UI paths with rich event types.
1580
+ * Each scenario uses a mix of text, tool_use, tool_result, and usage events
1581
+ * to produce a realistic agent session experience.
1582
+ *
1583
+ * - Scenario A: straight-through to "completed" with tool calls
1584
+ * - Scenario B: pauses at "idle" for user confirmation, then resumes
1585
+ * - Scenario C: runs partway then hits a tool error and ends in "failed"
1586
+ */
1587
+ export const MOCK_STREAM_SCENARIOS: MockStreamScenario[] = [
1588
+ // ── Scenario A — API Refactor (straight-through with tool calls) ──────
1589
+ {
1590
+ label: "API Refactor",
1591
+ pauseForInput: false,
1592
+ steps: [
1593
+ {
1594
+ delayMs: 0,
1595
+ event: { eventType: "status", timestamp: ts(0), content: "running" },
1596
+ },
1597
+ {
1598
+ delayMs: 600,
1599
+ event: {
1600
+ eventType: "text",
1601
+ timestamp: ts(600),
1602
+ content: "I'll start by finding all API route files that need refactoring.",
1603
+ },
1604
+ },
1605
+ {
1606
+ delayMs: 1200,
1607
+ event: {
1608
+ eventType: "tool_use",
1609
+ timestamp: ts(1200),
1610
+ content: JSON.stringify({ tool: "Glob", args: { pattern: "src/routes/**/*.ts" } }),
1611
+ raw: JSON.stringify({ id: "stream-a-01", type: "tool_use" }),
1612
+ },
1613
+ },
1614
+ {
1615
+ delayMs: 1600,
1616
+ event: {
1617
+ eventType: "tool_result",
1618
+ timestamp: ts(1600),
1619
+ content: "src/routes/users.ts\nsrc/routes/auth.ts\nsrc/routes/billing.ts\nsrc/routes/admin.ts\nsrc/routes/health.ts",
1620
+ raw: JSON.stringify({ tool_use_id: "stream-a-01" }),
1621
+ },
1622
+ },
1623
+ {
1624
+ delayMs: 2200,
1625
+ event: {
1626
+ eventType: "text",
1627
+ timestamp: ts(2200),
1628
+ content: "Found 5 route files. Let me refactor users.ts to use Express Router with a middleware chain.\n\n```typescript\nconst router = Router();\nrouter.use(authenticate);\nrouter.use(validateBody);\n\nrouter.get(\"/\", listUsers);\nrouter.post(\"/\", createUser);\nrouter.get(\"/:id\", getUser);\nrouter.put(\"/:id\", updateUser);\nrouter.delete(\"/:id\", deleteUser);\n```",
1629
+ },
1630
+ },
1631
+ {
1632
+ delayMs: 3200,
1633
+ event: {
1634
+ eventType: "tool_use",
1635
+ timestamp: ts(3200),
1636
+ content: JSON.stringify({ tool: "Bash", args: { command: "npm test -- --testPathPattern=routes" } }),
1637
+ raw: JSON.stringify({ id: "stream-a-02", type: "tool_use" }),
1638
+ },
1639
+ },
1640
+ {
1641
+ delayMs: 4000,
1642
+ event: {
1643
+ eventType: "tool_result",
1644
+ timestamp: ts(4000),
1645
+ content: " PASS src/routes/__tests__/users.test.ts\n PASS src/routes/__tests__/auth.test.ts\n PASS src/routes/__tests__/billing.test.ts\n\nTest Suites: 3 passed, 3 total\nTests: 22 passed, 22 total",
1646
+ raw: JSON.stringify({ tool_use_id: "stream-a-02" }),
1647
+ },
1648
+ },
1649
+ {
1650
+ delayMs: 4500,
1651
+ event: {
1652
+ eventType: "usage",
1653
+ timestamp: ts(4500),
1654
+ content: JSON.stringify({ input_tokens: 28400, output_tokens: 7200, cost_millicents: 15000 }),
1655
+ },
1656
+ },
1657
+ {
1658
+ delayMs: 5000,
1659
+ event: {
1660
+ eventType: "text",
1661
+ timestamp: ts(5000),
1662
+ content: "All 22 tests pass. The route refactoring is complete.",
1663
+ },
1664
+ },
1665
+ {
1666
+ delayMs: 5500,
1667
+ event: { eventType: "status", timestamp: ts(5500), content: "completed" },
1668
+ },
1669
+ ],
1670
+ },
1671
+
1672
+ // ── Scenario B — Database Migration (pause for input) ─
1673
+ {
1674
+ label: "Database Migration",
1675
+ pauseForInput: true,
1676
+ pauseAfterStep: 4,
1677
+ steps: [
1678
+ {
1679
+ delayMs: 0,
1680
+ event: { eventType: "status", timestamp: ts(0), content: "running" },
1681
+ },
1682
+ {
1683
+ delayMs: 600,
1684
+ event: {
1685
+ eventType: "text",
1686
+ timestamp: ts(600),
1687
+ content: "Scanning the current database schema to plan the migration.",
1688
+ },
1689
+ },
1690
+ {
1691
+ delayMs: 1200,
1692
+ event: {
1693
+ eventType: "tool_use",
1694
+ timestamp: ts(1200),
1695
+ content: JSON.stringify({ tool: "Read", args: { file_path: "migrations/001_initial.sql" } }),
1696
+ raw: JSON.stringify({ id: "stream-b-01", type: "tool_use" }),
1697
+ },
1698
+ },
1699
+ {
1700
+ delayMs: 1800,
1701
+ event: {
1702
+ eventType: "tool_result",
1703
+ timestamp: ts(1800),
1704
+ content: "CREATE TABLE users (\n id UUID PRIMARY KEY,\n name TEXT NOT NULL,\n email TEXT UNIQUE NOT NULL,\n created_at TIMESTAMPTZ DEFAULT now()\n);\n\nCREATE TABLE sessions (\n id UUID PRIMARY KEY,\n user_id UUID REFERENCES users(id),\n expires_at TIMESTAMPTZ NOT NULL\n);",
1705
+ raw: JSON.stringify({ tool_use_id: "stream-b-01" }),
1706
+ },
1707
+ },
1708
+ {
1709
+ delayMs: 2600,
1710
+ event: {
1711
+ eventType: "text",
1712
+ timestamp: ts(2600),
1713
+ content: "I found the tables to modify:\n - **users**: add column `last_login_at`\n - **sessions**: add index on `expires_at`\n - **audit_log**: new table\n\nThis migration will add a NOT NULL column to the `users` table. The table has **50,000+ rows** which may lock the table briefly.\n\nPlease confirm to proceed.",
1714
+ },
1715
+ },
1716
+ // step index 4 is the last step before pause (indices 0-4)
1717
+ ],
1718
+ resumeSteps: [
1719
+ {
1720
+ delayMs: 500,
1721
+ event: {
1722
+ eventType: "text",
1723
+ timestamp: ts(0),
1724
+ content: "Proceeding with migration.",
1725
+ },
1726
+ },
1727
+ {
1728
+ delayMs: 1200,
1729
+ event: {
1730
+ eventType: "tool_use",
1731
+ timestamp: ts(700),
1732
+ content: JSON.stringify({ tool: "Bash", args: { command: "npm run migrate" } }),
1733
+ raw: JSON.stringify({ id: "stream-b-02", type: "tool_use" }),
1734
+ },
1735
+ },
1736
+ {
1737
+ delayMs: 2200,
1738
+ event: {
1739
+ eventType: "tool_result",
1740
+ timestamp: ts(1700),
1741
+ content: "Applying 002_add_last_login.sql... done\nApplying 003_sessions_index.sql... done\nApplying 004_audit_log.sql... done\n\n3 migrations applied successfully.",
1742
+ raw: JSON.stringify({ tool_use_id: "stream-b-02" }),
1743
+ },
1744
+ },
1745
+ {
1746
+ delayMs: 2800,
1747
+ event: {
1748
+ eventType: "text",
1749
+ timestamp: ts(2300),
1750
+ content: "All 3 migrations applied successfully. The `audit_log` table is ready.",
1751
+ },
1752
+ },
1753
+ {
1754
+ delayMs: 3200,
1755
+ event: { eventType: "status", timestamp: ts(2700), content: "completed" },
1756
+ },
1757
+ ],
1758
+ },
1759
+
1760
+ // ── Scenario C — Test Writer (error/failed with tool error) ───────────
1761
+ {
1762
+ label: "Test Writer",
1763
+ pauseForInput: false,
1764
+ steps: [
1765
+ {
1766
+ delayMs: 0,
1767
+ event: { eventType: "status", timestamp: ts(0), content: "running" },
1768
+ },
1769
+ {
1770
+ delayMs: 600,
1771
+ event: {
1772
+ eventType: "text",
1773
+ timestamp: ts(600),
1774
+ content: "I'll generate tests for `src/services/billing.ts`. Let me read the implementation first.",
1775
+ },
1776
+ },
1777
+ {
1778
+ delayMs: 1200,
1779
+ event: {
1780
+ eventType: "tool_use",
1781
+ timestamp: ts(1200),
1782
+ content: JSON.stringify({ tool: "Read", args: { file_path: "src/services/billing.ts" } }),
1783
+ raw: JSON.stringify({ id: "stream-c-01", type: "tool_use" }),
1784
+ },
1785
+ },
1786
+ {
1787
+ delayMs: 1800,
1788
+ event: {
1789
+ eventType: "tool_result",
1790
+ timestamp: ts(1800),
1791
+ content: 'import { StripeClient } from "./stripe-client.js";\nimport type { Invoice, LineItem } from "../types/billing.js";\n\nexport class BillingService {\n constructor(private stripe: StripeClient) {}\n\n async calculateMonthlyTotal(userId: string): Promise<number> {\n const invoices = await this.stripe.listInvoices(userId);\n return invoices.reduce((sum, inv) => sum + inv.total, 0);\n }\n}',
1792
+ raw: JSON.stringify({ tool_use_id: "stream-c-01" }),
1793
+ },
1794
+ },
1795
+ {
1796
+ delayMs: 2400,
1797
+ event: {
1798
+ eventType: "text",
1799
+ timestamp: ts(2400),
1800
+ content: "The billing service imports from `./stripe-client.js`. Let me check if that module exists.",
1801
+ },
1802
+ },
1803
+ {
1804
+ delayMs: 2800,
1805
+ event: {
1806
+ eventType: "tool_use",
1807
+ timestamp: ts(2800),
1808
+ content: JSON.stringify({ tool: "Read", args: { file_path: "src/services/stripe-client.ts" } }),
1809
+ raw: JSON.stringify({ id: "stream-c-02", type: "tool_use" }),
1810
+ },
1811
+ },
1812
+ {
1813
+ delayMs: 3200,
1814
+ event: {
1815
+ eventType: "tool_result",
1816
+ timestamp: ts(3200),
1817
+ content: "Error: ENOENT: no such file or directory, open 'src/services/stripe-client.ts'",
1818
+ raw: JSON.stringify({ tool_use_id: "stream-c-02", is_error: true }),
1819
+ },
1820
+ },
1821
+ {
1822
+ delayMs: 3600,
1823
+ event: {
1824
+ eventType: "error",
1825
+ timestamp: ts(3600),
1826
+ content: "Cannot resolve import 'src/services/stripe-client.ts' — module not found. The billing service depends on a Stripe client that doesn't exist in this environment.",
1827
+ },
1828
+ },
1829
+ {
1830
+ delayMs: 4000,
1831
+ event: { eventType: "status", timestamp: ts(4000), content: "failed" },
1832
+ },
1833
+ ],
1834
+ },
1835
+
1836
+ // ── Scenario D — Fix Auth Bug (tool-heavy) ────────────
1837
+ {
1838
+ label: "Fix Auth Bug",
1839
+ pauseForInput: false,
1840
+ steps: [
1841
+ {
1842
+ delayMs: 0,
1843
+ event: { eventType: "status", timestamp: ts(0), content: "running" },
1844
+ },
1845
+ {
1846
+ delayMs: 600,
1847
+ event: {
1848
+ eventType: "text",
1849
+ timestamp: ts(600),
1850
+ content: "Let me investigate the authentication bug...",
1851
+ },
1852
+ },
1853
+ // Read file
1854
+ {
1855
+ delayMs: 1200,
1856
+ event: {
1857
+ eventType: "tool_use",
1858
+ timestamp: ts(1200),
1859
+ content: JSON.stringify({ tool: "Read", args: { file_path: "/src/middleware/auth.ts" } }),
1860
+ raw: JSON.stringify({ id: "tool-mock-001" }),
1861
+ },
1862
+ },
1863
+ {
1864
+ delayMs: 1800,
1865
+ event: {
1866
+ eventType: "tool_result",
1867
+ timestamp: ts(1800),
1868
+ content: 'import jwt from "jsonwebtoken";\nimport type { Request, Response, NextFunction } from "express";\n\nconst JWT_SECRET = process.env.JWT_SECRET || "change-me";\n\nexport function verifyToken(req: Request, res: Response, next: NextFunction): void {\n const header = req.headers.authorization;\n if (!header?.startsWith("Bearer ")) {\n res.status(401).json({ error: "Missing token" });\n return;\n }\n try {\n const decoded = jwt.verify(header.slice(7), JWT_SECRET);\n if (decoded.exp > Date.now() / 1000) {\n res.status(403).json({ error: "Token expired" });\n return;\n }\n req.user = decoded;\n next();\n } catch {\n res.status(403).json({ error: "Invalid token" });\n }\n}',
1869
+ raw: JSON.stringify({ tool_use_id: "tool-mock-001", is_error: false }),
1870
+ },
1871
+ },
1872
+ {
1873
+ delayMs: 2600,
1874
+ event: {
1875
+ eventType: "text",
1876
+ timestamp: ts(2600),
1877
+ content: "Found the issue — the token expiry check on line 14 is using `>` instead of `<`. A token is expired when `exp < now`, but the code checks `exp > now` which means it rejects *valid* tokens and accepts *expired* ones. Let me fix it.",
1878
+ },
1879
+ },
1880
+ // Edit file
1881
+ {
1882
+ delayMs: 3400,
1883
+ event: {
1884
+ eventType: "tool_use",
1885
+ timestamp: ts(3400),
1886
+ content: JSON.stringify({
1887
+ tool: "Edit",
1888
+ args: {
1889
+ file_path: "/src/middleware/auth.ts",
1890
+ old_string: " if (decoded.exp > Date.now() / 1000) {",
1891
+ new_string: " if (decoded.exp < Date.now() / 1000) {",
1892
+ },
1893
+ }),
1894
+ raw: JSON.stringify({ id: "tool-mock-002" }),
1895
+ },
1896
+ },
1897
+ {
1898
+ delayMs: 4000,
1899
+ event: {
1900
+ eventType: "tool_result",
1901
+ timestamp: ts(4000),
1902
+ content: "File updated successfully",
1903
+ raw: JSON.stringify({ tool_use_id: "tool-mock-002", is_error: false }),
1904
+ },
1905
+ },
1906
+ // Run tests
1907
+ {
1908
+ delayMs: 4600,
1909
+ event: {
1910
+ eventType: "tool_use",
1911
+ timestamp: ts(4600),
1912
+ content: JSON.stringify({ tool: "Bash", args: { command: "npm test -- --grep auth" } }),
1913
+ raw: JSON.stringify({ id: "tool-mock-003" }),
1914
+ },
1915
+ },
1916
+ {
1917
+ delayMs: 5800,
1918
+ event: {
1919
+ eventType: "tool_result",
1920
+ timestamp: ts(5800),
1921
+ content: "[exit 0] > jest --grep auth\n\n PASS src/auth.test.ts\n ✓ validates JWT token (12ms)\n ✓ rejects expired token (3ms)\n ✓ handles missing header (1ms)\n\nTests: 3 passed, 3 total\nTime: 1.234s",
1922
+ raw: JSON.stringify({ tool_use_id: "tool-mock-003", is_error: false }),
1923
+ },
1924
+ },
1925
+ // Search for other occurrences
1926
+ {
1927
+ delayMs: 6400,
1928
+ event: {
1929
+ eventType: "tool_use",
1930
+ timestamp: ts(6400),
1931
+ content: JSON.stringify({ tool: "Grep", args: { pattern: "decoded\\.exp", path: "src/" } }),
1932
+ raw: JSON.stringify({ id: "tool-mock-004" }),
1933
+ },
1934
+ },
1935
+ {
1936
+ delayMs: 7000,
1937
+ event: {
1938
+ eventType: "tool_result",
1939
+ timestamp: ts(7000),
1940
+ content: "src/middleware/auth.ts:14: if (decoded.exp < Date.now() / 1000) {\nsrc/utils/token-refresh.ts:28: const remaining = decoded.exp - Date.now() / 1000;",
1941
+ raw: JSON.stringify({ tool_use_id: "tool-mock-004", is_error: false }),
1942
+ },
1943
+ },
1944
+ {
1945
+ delayMs: 7800,
1946
+ event: {
1947
+ eventType: "text",
1948
+ timestamp: ts(7800),
1949
+ content: "Fixed. The token expiry comparison was inverted — `decoded.exp > Date.now()` should have been `<` (a token is expired when its `exp` is in the past). The other occurrence in `token-refresh.ts` uses subtraction (not comparison), so it was already correct. All 3 auth tests pass.",
1950
+ },
1951
+ },
1952
+ {
1953
+ delayMs: 8400,
1954
+ event: {
1955
+ eventType: "usage",
1956
+ timestamp: ts(8400),
1957
+ content: JSON.stringify({ input_tokens: 42600, output_tokens: 1850, cost_millicents: 12000 }),
1958
+ },
1959
+ },
1960
+ {
1961
+ delayMs: 8800,
1962
+ event: { eventType: "status", timestamp: ts(8800), content: "completed" },
1963
+ },
1964
+ ],
1965
+ },
1966
+ ];