nitrostack 1.0.71 → 1.0.73

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 (253) hide show
  1. package/dist/auth/api-key.js.map +1 -1
  2. package/dist/auth/client.js.map +1 -1
  3. package/dist/auth/index.d.ts +2 -1
  4. package/dist/auth/index.d.ts.map +1 -1
  5. package/dist/auth/index.js +3 -0
  6. package/dist/auth/index.js.map +1 -1
  7. package/dist/auth/middleware.d.ts +1 -1
  8. package/dist/auth/middleware.d.ts.map +1 -1
  9. package/dist/auth/middleware.js.map +1 -1
  10. package/dist/auth/secure-secret.d.ts +136 -0
  11. package/dist/auth/secure-secret.d.ts.map +1 -0
  12. package/dist/auth/secure-secret.js +182 -0
  13. package/dist/auth/secure-secret.js.map +1 -0
  14. package/dist/auth/server-metadata.d.ts.map +1 -1
  15. package/dist/auth/server-metadata.js.map +1 -1
  16. package/dist/auth/simple-jwt.d.ts +100 -14
  17. package/dist/auth/simple-jwt.d.ts.map +1 -1
  18. package/dist/auth/simple-jwt.js +19 -9
  19. package/dist/auth/simple-jwt.js.map +1 -1
  20. package/dist/auth/token-store.js +1 -1
  21. package/dist/auth/token-store.js.map +1 -1
  22. package/dist/auth/token-validation.js +1 -1
  23. package/dist/auth/token-validation.js.map +1 -1
  24. package/dist/cli/commands/build.js +1 -1
  25. package/dist/cli/commands/build.js.map +1 -1
  26. package/dist/cli/commands/generate-types.js +12 -12
  27. package/dist/cli/commands/generate-types.js.map +1 -1
  28. package/dist/cli/commands/generate.d.ts +8 -1
  29. package/dist/cli/commands/generate.d.ts.map +1 -1
  30. package/dist/cli/commands/generate.js +13 -12
  31. package/dist/cli/commands/generate.js.map +1 -1
  32. package/dist/cli/commands/init.js +1 -1
  33. package/dist/cli/commands/init.js.map +1 -1
  34. package/dist/cli/commands/upgrade.d.ts +10 -0
  35. package/dist/cli/commands/upgrade.d.ts.map +1 -0
  36. package/dist/cli/commands/upgrade.js +221 -0
  37. package/dist/cli/commands/upgrade.js.map +1 -0
  38. package/dist/cli/index.js +7 -0
  39. package/dist/cli/index.js.map +1 -1
  40. package/dist/core/app-decorator.d.ts +4 -3
  41. package/dist/core/app-decorator.d.ts.map +1 -1
  42. package/dist/core/app-decorator.js +67 -28
  43. package/dist/core/app-decorator.js.map +1 -1
  44. package/dist/core/builders.d.ts +19 -7
  45. package/dist/core/builders.d.ts.map +1 -1
  46. package/dist/core/builders.js +15 -8
  47. package/dist/core/builders.js.map +1 -1
  48. package/dist/core/component.d.ts +8 -8
  49. package/dist/core/component.d.ts.map +1 -1
  50. package/dist/core/component.js +3 -2
  51. package/dist/core/component.js.map +1 -1
  52. package/dist/core/config-module.d.ts +11 -4
  53. package/dist/core/config-module.d.ts.map +1 -1
  54. package/dist/core/config-module.js +1 -1
  55. package/dist/core/config-module.js.map +1 -1
  56. package/dist/core/decorators/cache.decorator.d.ts +9 -9
  57. package/dist/core/decorators/cache.decorator.d.ts.map +1 -1
  58. package/dist/core/decorators/cache.decorator.js +3 -3
  59. package/dist/core/decorators/cache.decorator.js.map +1 -1
  60. package/dist/core/decorators/health-check.decorator.d.ts +3 -3
  61. package/dist/core/decorators/health-check.decorator.d.ts.map +1 -1
  62. package/dist/core/decorators/health-check.decorator.js +2 -2
  63. package/dist/core/decorators/health-check.decorator.js.map +1 -1
  64. package/dist/core/decorators/rate-limit.decorator.d.ts +5 -4
  65. package/dist/core/decorators/rate-limit.decorator.d.ts.map +1 -1
  66. package/dist/core/decorators/rate-limit.decorator.js +3 -3
  67. package/dist/core/decorators/rate-limit.decorator.js.map +1 -1
  68. package/dist/core/decorators.d.ts +47 -29
  69. package/dist/core/decorators.d.ts.map +1 -1
  70. package/dist/core/decorators.js +9 -9
  71. package/dist/core/decorators.js.map +1 -1
  72. package/dist/core/di/container.d.ts +21 -4
  73. package/dist/core/di/container.d.ts.map +1 -1
  74. package/dist/core/di/container.js +11 -7
  75. package/dist/core/di/container.js.map +1 -1
  76. package/dist/core/di/injectable.decorator.d.ts +5 -3
  77. package/dist/core/di/injectable.decorator.d.ts.map +1 -1
  78. package/dist/core/di/injectable.decorator.js.map +1 -1
  79. package/dist/core/errors.d.ts +4 -4
  80. package/dist/core/errors.d.ts.map +1 -1
  81. package/dist/core/errors.js.map +1 -1
  82. package/dist/core/events/event-emitter.d.ts +3 -3
  83. package/dist/core/events/event-emitter.d.ts.map +1 -1
  84. package/dist/core/events/event-emitter.js.map +1 -1
  85. package/dist/core/events/event.decorator.d.ts +5 -5
  86. package/dist/core/events/event.decorator.d.ts.map +1 -1
  87. package/dist/core/events/event.decorator.js +10 -6
  88. package/dist/core/events/event.decorator.js.map +1 -1
  89. package/dist/core/events/log-emitter.d.ts +7 -1
  90. package/dist/core/events/log-emitter.d.ts.map +1 -1
  91. package/dist/core/events/log-emitter.js.map +1 -1
  92. package/dist/core/filters/exception-filter.decorator.d.ts +5 -5
  93. package/dist/core/filters/exception-filter.decorator.d.ts.map +1 -1
  94. package/dist/core/filters/exception-filter.decorator.js +3 -3
  95. package/dist/core/filters/exception-filter.decorator.js.map +1 -1
  96. package/dist/core/filters/exception-filter.interface.d.ts +14 -5
  97. package/dist/core/filters/exception-filter.interface.d.ts.map +1 -1
  98. package/dist/core/guards/apikey.guard.d.ts +1 -1
  99. package/dist/core/guards/apikey.guard.d.ts.map +1 -1
  100. package/dist/core/guards/guard.interface.d.ts +1 -1
  101. package/dist/core/guards/guard.interface.d.ts.map +1 -1
  102. package/dist/core/guards/jwt.guard.d.ts +1 -1
  103. package/dist/core/guards/jwt.guard.d.ts.map +1 -1
  104. package/dist/core/guards/oauth.guard.d.ts +1 -1
  105. package/dist/core/guards/oauth.guard.d.ts.map +1 -1
  106. package/dist/core/guards/use-guards.decorator.d.ts +3 -3
  107. package/dist/core/guards/use-guards.decorator.d.ts.map +1 -1
  108. package/dist/core/guards/use-guards.decorator.js +1 -1
  109. package/dist/core/guards/use-guards.decorator.js.map +1 -1
  110. package/dist/core/index.d.ts +2 -2
  111. package/dist/core/index.d.ts.map +1 -1
  112. package/dist/core/index.js.map +1 -1
  113. package/dist/core/interceptors/interceptor.decorator.d.ts +4 -4
  114. package/dist/core/interceptors/interceptor.decorator.d.ts.map +1 -1
  115. package/dist/core/interceptors/interceptor.decorator.js +2 -2
  116. package/dist/core/interceptors/interceptor.decorator.js.map +1 -1
  117. package/dist/core/interceptors/interceptor.interface.d.ts +3 -3
  118. package/dist/core/interceptors/interceptor.interface.d.ts.map +1 -1
  119. package/dist/core/logger.d.ts.map +1 -1
  120. package/dist/core/logger.js.map +1 -1
  121. package/dist/core/middleware/middleware.decorator.d.ts +4 -4
  122. package/dist/core/middleware/middleware.decorator.d.ts.map +1 -1
  123. package/dist/core/middleware/middleware.decorator.js +2 -2
  124. package/dist/core/middleware/middleware.decorator.js.map +1 -1
  125. package/dist/core/middleware/middleware.interface.d.ts +3 -3
  126. package/dist/core/middleware/middleware.interface.d.ts.map +1 -1
  127. package/dist/core/module.d.ts +33 -14
  128. package/dist/core/module.d.ts.map +1 -1
  129. package/dist/core/module.js +11 -6
  130. package/dist/core/module.js.map +1 -1
  131. package/dist/core/oauth-module.d.ts +9 -3
  132. package/dist/core/oauth-module.d.ts.map +1 -1
  133. package/dist/core/oauth-module.js +4 -3
  134. package/dist/core/oauth-module.js.map +1 -1
  135. package/dist/core/pipes/pipe.decorator.d.ts +14 -5
  136. package/dist/core/pipes/pipe.decorator.d.ts.map +1 -1
  137. package/dist/core/pipes/pipe.decorator.js +2 -2
  138. package/dist/core/pipes/pipe.decorator.js.map +1 -1
  139. package/dist/core/pipes/pipe.interface.d.ts +9 -4
  140. package/dist/core/pipes/pipe.interface.d.ts.map +1 -1
  141. package/dist/core/prompt.d.ts +13 -4
  142. package/dist/core/prompt.d.ts.map +1 -1
  143. package/dist/core/prompt.js +2 -2
  144. package/dist/core/prompt.js.map +1 -1
  145. package/dist/core/resource.d.ts +7 -2
  146. package/dist/core/resource.d.ts.map +1 -1
  147. package/dist/core/resource.js +2 -2
  148. package/dist/core/resource.js.map +1 -1
  149. package/dist/core/server.d.ts +49 -3
  150. package/dist/core/server.d.ts.map +1 -1
  151. package/dist/core/server.js +61 -34
  152. package/dist/core/server.js.map +1 -1
  153. package/dist/core/tool.d.ts +44 -16
  154. package/dist/core/tool.d.ts.map +1 -1
  155. package/dist/core/tool.js +19 -6
  156. package/dist/core/tool.js.map +1 -1
  157. package/dist/core/transports/discovery-http-server.d.ts +7 -1
  158. package/dist/core/transports/discovery-http-server.d.ts.map +1 -1
  159. package/dist/core/transports/discovery-http-server.js.map +1 -1
  160. package/dist/core/transports/http-server.d.ts +2 -2
  161. package/dist/core/transports/http-server.d.ts.map +1 -1
  162. package/dist/core/transports/http-server.js +1 -1
  163. package/dist/core/transports/http-server.js.map +1 -1
  164. package/dist/core/transports/streamable-http.d.ts +4 -4
  165. package/dist/core/transports/streamable-http.d.ts.map +1 -1
  166. package/dist/core/transports/streamable-http.js +1 -1
  167. package/dist/core/transports/streamable-http.js.map +1 -1
  168. package/dist/core/types.d.ts +87 -15
  169. package/dist/core/types.d.ts.map +1 -1
  170. package/dist/core/widgets/widget-registry.d.ts +2 -2
  171. package/dist/core/widgets/widget-registry.d.ts.map +1 -1
  172. package/dist/core/widgets/widget-registry.js +1 -1
  173. package/dist/core/widgets/widget-registry.js.map +1 -1
  174. package/dist/testing/index.d.ts +44 -17
  175. package/dist/testing/index.d.ts.map +1 -1
  176. package/dist/testing/index.js +5 -8
  177. package/dist/testing/index.js.map +1 -1
  178. package/dist/ui-next/index.d.ts +1 -1
  179. package/dist/ui-next/index.d.ts.map +1 -1
  180. package/dist/ui-next/index.js.map +1 -1
  181. package/dist/widgets/hooks/useWidgetSDK.d.ts +5 -5
  182. package/dist/widgets/runtime/WidgetLayout.js.map +1 -1
  183. package/dist/widgets/sdk.d.ts +5 -5
  184. package/dist/widgets/sdk.d.ts.map +1 -1
  185. package/dist/widgets/sdk.js.map +1 -1
  186. package/package.json +1 -1
  187. package/src/studio/app/api/auth/fetch-metadata/route.ts +3 -2
  188. package/src/studio/app/api/auth/register-client/route.ts +3 -2
  189. package/src/studio/app/api/chat/route.ts +33 -17
  190. package/src/studio/app/api/health/checks/route.ts +5 -4
  191. package/src/studio/app/api/init/route.ts +3 -2
  192. package/src/studio/app/api/ping/route.ts +3 -2
  193. package/src/studio/app/api/prompts/[name]/route.ts +4 -3
  194. package/src/studio/app/api/prompts/route.ts +3 -2
  195. package/src/studio/app/api/resources/[...uri]/route.ts +3 -2
  196. package/src/studio/app/api/resources/route.ts +3 -2
  197. package/src/studio/app/api/roots/route.ts +3 -2
  198. package/src/studio/app/api/sampling/route.ts +3 -2
  199. package/src/studio/app/api/tools/[name]/call/route.ts +3 -2
  200. package/src/studio/app/api/tools/route.ts +4 -3
  201. package/src/studio/app/api/widget-examples/route.ts +5 -4
  202. package/src/studio/app/auth/callback/page.tsx +9 -8
  203. package/src/studio/app/chat/page.tsx +1535 -468
  204. package/src/studio/app/chat/page.tsx.backup +1046 -187
  205. package/src/studio/app/globals.css +361 -191
  206. package/src/studio/app/health/page.tsx +73 -77
  207. package/src/studio/app/layout.tsx +9 -11
  208. package/src/studio/app/logs/page.tsx +31 -32
  209. package/src/studio/app/page.tsx +136 -232
  210. package/src/studio/app/prompts/page.tsx +115 -97
  211. package/src/studio/app/resources/page.tsx +115 -124
  212. package/src/studio/app/settings/page.tsx +1083 -127
  213. package/src/studio/app/tools/page.tsx +343 -0
  214. package/src/studio/components/EnlargeModal.tsx +76 -65
  215. package/src/studio/components/LogMessage.tsx +6 -6
  216. package/src/studio/components/MarkdownRenderer.tsx +246 -349
  217. package/src/studio/components/Sidebar.tsx +165 -210
  218. package/src/studio/components/SplashScreen.tsx +109 -0
  219. package/src/studio/components/ToolCard.tsx +50 -41
  220. package/src/studio/components/VoiceOrbOverlay.tsx +475 -0
  221. package/src/studio/components/WidgetErrorBoundary.tsx +48 -0
  222. package/src/studio/components/WidgetRenderer.tsx +169 -211
  223. package/src/studio/components/ops/OpsCanvas.tsx +748 -0
  224. package/src/studio/components/ops/OpsNodeDetailPanel.tsx +150 -0
  225. package/src/studio/components/ops/OpsSummaryBar.tsx +90 -0
  226. package/src/studio/components/ops/index.ts +5 -0
  227. package/src/studio/components/ops/nodes/BaseNode.tsx +65 -0
  228. package/src/studio/components/ops/nodes/LLMCallNode.tsx +34 -0
  229. package/src/studio/components/ops/nodes/LLMResponseNode.tsx +33 -0
  230. package/src/studio/components/ops/nodes/ToolCallNode.tsx +30 -0
  231. package/src/studio/components/ops/nodes/ToolResultNode.tsx +43 -0
  232. package/src/studio/components/ops/nodes/UserPromptNode.tsx +34 -0
  233. package/src/studio/components/ops/nodes/WidgetRenderNode.tsx +23 -0
  234. package/src/studio/components/ops/nodes/index.ts +8 -0
  235. package/src/studio/components/tools/ToolsCanvas.tsx +327 -0
  236. package/src/studio/lib/api.ts +61 -42
  237. package/src/studio/lib/http-client-transport.ts +2 -2
  238. package/src/studio/lib/llm-service.ts +126 -47
  239. package/src/studio/lib/mcp-client.ts +9 -6
  240. package/src/studio/lib/ops-store.ts +427 -0
  241. package/src/studio/lib/ops-tracker.ts +416 -0
  242. package/src/studio/lib/ops-types.ts +164 -0
  243. package/src/studio/lib/store.ts +23 -11
  244. package/src/studio/lib/types.ts +228 -38
  245. package/src/studio/lib/widget-loader.ts +2 -2
  246. package/src/studio/package-lock.json +3303 -0
  247. package/src/studio/package.json +3 -1
  248. package/src/studio/public/NitroStudio Isotype Color.png +0 -0
  249. package/src/studio/tailwind.config.ts +63 -17
  250. package/templates/typescript-oauth/src/modules/flights/flights.prompts.ts +19 -22
  251. package/dist/cli/build-widgets.mjs +0 -165
  252. package/src/studio/app/auth/page.tsx +0 -560
  253. package/src/studio/app/ping/page.tsx +0 -209
@@ -0,0 +1,427 @@
1
+ /**
2
+ * Ops Store - Zustand store for Agent Operations Visualization
3
+ */
4
+
5
+ import { create } from 'zustand';
6
+ import type {
7
+ OpsNode,
8
+ OpsEdge,
9
+ OpsSession,
10
+ OpsSessionSummary,
11
+ OpsNodeType,
12
+ OpsNodeData,
13
+ OpsTurn,
14
+ LLMProvider,
15
+ } from './ops-types';
16
+
17
+ interface OpsState {
18
+ // Current session
19
+ currentSession: OpsSession | null;
20
+
21
+ // Current turn being tracked
22
+ currentTurnId: string | null;
23
+
24
+ // Selected turn for viewing (null = show all)
25
+ selectedTurnId: string | null;
26
+
27
+ // Playback state
28
+ isPlaying: boolean;
29
+ playbackTurnIndex: number;
30
+
31
+ // UI state
32
+ isOpsViewOpen: boolean;
33
+ selectedNodeId: string | null;
34
+
35
+ // History (for replay/comparison)
36
+ sessionHistory: OpsSession[];
37
+
38
+ // Actions
39
+ startSession: () => void;
40
+ endSession: () => void;
41
+ clearSession: () => void;
42
+
43
+ startTurn: (promptPreview: string) => string;
44
+ endTurn: () => void;
45
+
46
+ addNode: (type: OpsNodeType, data: OpsNodeData) => string;
47
+ updateNode: (nodeId: string, updates: Partial<OpsNode>) => void;
48
+ updateNodeData: (nodeId: string, dataUpdates: Partial<OpsNodeData>) => void;
49
+
50
+ addEdge: (source: string, target: string, label?: string) => void;
51
+
52
+ setOpsViewOpen: (open: boolean) => void;
53
+ toggleOpsView: () => void;
54
+ setSelectedNode: (nodeId: string | null) => void;
55
+ setSelectedTurn: (turnId: string | null) => void;
56
+
57
+ // Playback controls
58
+ startPlayback: () => void;
59
+ stopPlayback: () => void;
60
+ nextTurn: () => void;
61
+ prevTurn: () => void;
62
+
63
+ // Computed
64
+ getSummary: () => OpsSessionSummary;
65
+ getNodesForTurn: (turnId: string | null) => OpsNode[];
66
+ getEdgesForTurn: (turnId: string | null) => OpsEdge[];
67
+ }
68
+
69
+ // Generate unique ID
70
+ const generateId = (): string => {
71
+ return `ops_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
72
+ };
73
+
74
+ // Create empty summary
75
+ const createEmptySummary = (): OpsSessionSummary => ({
76
+ totalTokens: 0,
77
+ inputTokens: 0,
78
+ outputTokens: 0,
79
+ estimatedCost: 0,
80
+ totalDuration: 0,
81
+ toolCalls: 0,
82
+ llmCalls: 0,
83
+ widgetRenders: 0,
84
+ errors: 0,
85
+ });
86
+
87
+ export const useOpsStore = create<OpsState>((set, get) => ({
88
+ // Initial state
89
+ currentSession: null,
90
+ currentTurnId: null,
91
+ selectedTurnId: null,
92
+ isPlaying: false,
93
+ playbackTurnIndex: 0,
94
+ isOpsViewOpen: false,
95
+ selectedNodeId: null,
96
+ sessionHistory: [],
97
+
98
+ // Start a new session
99
+ startSession: () => {
100
+ const session: OpsSession = {
101
+ id: generateId(),
102
+ startTime: Date.now(),
103
+ nodes: [],
104
+ edges: [],
105
+ turns: [],
106
+ summary: createEmptySummary(),
107
+ };
108
+ set({ currentSession: session, currentTurnId: null, selectedTurnId: null });
109
+ },
110
+
111
+ // End current session
112
+ endSession: () => {
113
+ const { currentSession, sessionHistory } = get();
114
+ if (currentSession) {
115
+ const endedSession = {
116
+ ...currentSession,
117
+ endTime: Date.now(),
118
+ summary: get().getSummary(),
119
+ };
120
+ set({
121
+ currentSession: null,
122
+ currentTurnId: null,
123
+ sessionHistory: [...sessionHistory, endedSession],
124
+ });
125
+ }
126
+ },
127
+
128
+ // Clear current session
129
+ clearSession: () => {
130
+ set({
131
+ currentSession: null,
132
+ currentTurnId: null,
133
+ selectedTurnId: null,
134
+ selectedNodeId: null,
135
+ isPlaying: false,
136
+ playbackTurnIndex: 0,
137
+ });
138
+ },
139
+
140
+ // Start a new turn/cycle
141
+ startTurn: (promptPreview: string) => {
142
+ const { currentSession } = get();
143
+
144
+ // Auto-start session if not started
145
+ if (!currentSession) {
146
+ get().startSession();
147
+ }
148
+
149
+ const session = get().currentSession;
150
+ if (!session) return '';
151
+
152
+ const turnId = generateId();
153
+ const turn: OpsTurn = {
154
+ id: turnId,
155
+ startTime: Date.now(),
156
+ promptPreview: promptPreview.substring(0, 40) + (promptPreview.length > 40 ? '...' : ''),
157
+ nodeCount: 0,
158
+ hasError: false,
159
+ };
160
+
161
+ set({
162
+ currentTurnId: turnId,
163
+ currentSession: {
164
+ ...session,
165
+ turns: [...session.turns, turn],
166
+ },
167
+ });
168
+
169
+ return turnId;
170
+ },
171
+
172
+ // End current turn
173
+ endTurn: () => {
174
+ const { currentSession, currentTurnId } = get();
175
+ if (!currentSession || !currentTurnId) return;
176
+
177
+ const turnNodes = currentSession.nodes.filter(n => n.turnId === currentTurnId);
178
+ const hasError = turnNodes.some(n => n.data.status === 'error');
179
+
180
+ set({
181
+ currentTurnId: null,
182
+ currentSession: {
183
+ ...currentSession,
184
+ turns: currentSession.turns.map(t =>
185
+ t.id === currentTurnId
186
+ ? { ...t, endTime: Date.now(), nodeCount: turnNodes.length, hasError }
187
+ : t
188
+ ),
189
+ },
190
+ });
191
+ },
192
+
193
+ // Add a node
194
+ addNode: (type: OpsNodeType, data: OpsNodeData): string => {
195
+ const { currentSession, currentTurnId } = get();
196
+
197
+ // Auto-start session if not started
198
+ if (!currentSession) {
199
+ get().startSession();
200
+ }
201
+
202
+ const session = get().currentSession;
203
+ const turnId = get().currentTurnId;
204
+ if (!session) return '';
205
+
206
+ // If no turn is active, create one
207
+ let effectiveTurnId = turnId;
208
+ if (!effectiveTurnId && type === 'user_prompt') {
209
+ const content = (data as { content?: string }).content || 'New conversation';
210
+ effectiveTurnId = get().startTurn(content);
211
+ } else if (!effectiveTurnId) {
212
+ effectiveTurnId = get().startTurn('Continuation');
213
+ }
214
+
215
+ const nodeId = generateId();
216
+
217
+ const newNode: OpsNode = {
218
+ id: nodeId,
219
+ type,
220
+ timestamp: Date.now(),
221
+ data,
222
+ turnId: effectiveTurnId || '',
223
+ };
224
+
225
+ set({
226
+ currentSession: {
227
+ ...session,
228
+ nodes: [...session.nodes, newNode],
229
+ },
230
+ });
231
+
232
+ return nodeId;
233
+ },
234
+
235
+ // Update a node
236
+ updateNode: (nodeId: string, updates: Partial<OpsNode>) => {
237
+ const { currentSession } = get();
238
+ if (!currentSession) return;
239
+
240
+ set({
241
+ currentSession: {
242
+ ...currentSession,
243
+ nodes: currentSession.nodes.map((node) =>
244
+ node.id === nodeId ? { ...node, ...updates } : node
245
+ ),
246
+ },
247
+ });
248
+ },
249
+
250
+ // Update node data
251
+ updateNodeData: (nodeId: string, dataUpdates: Partial<OpsNodeData>) => {
252
+ const { currentSession } = get();
253
+ if (!currentSession) return;
254
+
255
+ set({
256
+ currentSession: {
257
+ ...currentSession,
258
+ nodes: currentSession.nodes.map((node) =>
259
+ node.id === nodeId
260
+ ? { ...node, data: { ...node.data, ...dataUpdates } as OpsNodeData }
261
+ : node
262
+ ),
263
+ },
264
+ });
265
+ },
266
+
267
+ // Add an edge
268
+ addEdge: (source: string, target: string, label?: string) => {
269
+ const { currentSession, currentTurnId } = get();
270
+ if (!currentSession) return;
271
+
272
+ const edgeId = `edge_${source}_${target}`;
273
+
274
+ // Don't add duplicate edges
275
+ if (currentSession.edges.some((e) => e.id === edgeId)) return;
276
+
277
+ // Find the turn from source node
278
+ const sourceNode = currentSession.nodes.find(n => n.id === source);
279
+ const turnId = sourceNode?.turnId || currentTurnId || '';
280
+
281
+ const newEdge: OpsEdge = {
282
+ id: edgeId,
283
+ source,
284
+ target,
285
+ label,
286
+ animated: false,
287
+ turnId,
288
+ };
289
+
290
+ set({
291
+ currentSession: {
292
+ ...currentSession,
293
+ edges: [...currentSession.edges, newEdge],
294
+ },
295
+ });
296
+ },
297
+
298
+ // Toggle ops view
299
+ setOpsViewOpen: (open: boolean) => set({ isOpsViewOpen: open }),
300
+ toggleOpsView: () => set((state) => ({ isOpsViewOpen: !state.isOpsViewOpen })),
301
+
302
+ // Select node
303
+ setSelectedNode: (nodeId: string | null) => set({ selectedNodeId: nodeId }),
304
+
305
+ // Select turn
306
+ setSelectedTurn: (turnId: string | null) => set({
307
+ selectedTurnId: turnId,
308
+ selectedNodeId: null,
309
+ }),
310
+
311
+ // Playback controls
312
+ startPlayback: () => {
313
+ const { currentSession } = get();
314
+ if (!currentSession || currentSession.turns.length === 0) return;
315
+
316
+ set({
317
+ isPlaying: true,
318
+ playbackTurnIndex: 0,
319
+ selectedTurnId: currentSession.turns[0]?.id || null,
320
+ });
321
+ },
322
+
323
+ stopPlayback: () => {
324
+ set({ isPlaying: false });
325
+ },
326
+
327
+ nextTurn: () => {
328
+ const { currentSession, playbackTurnIndex } = get();
329
+ if (!currentSession) return;
330
+
331
+ const nextIndex = playbackTurnIndex + 1;
332
+ if (nextIndex < currentSession.turns.length) {
333
+ set({
334
+ playbackTurnIndex: nextIndex,
335
+ selectedTurnId: currentSession.turns[nextIndex]?.id || null,
336
+ });
337
+ } else {
338
+ // End playback when we reach the end
339
+ set({ isPlaying: false });
340
+ }
341
+ },
342
+
343
+ prevTurn: () => {
344
+ const { currentSession, playbackTurnIndex } = get();
345
+ if (!currentSession) return;
346
+
347
+ const prevIndex = playbackTurnIndex - 1;
348
+ if (prevIndex >= 0) {
349
+ set({
350
+ playbackTurnIndex: prevIndex,
351
+ selectedTurnId: currentSession.turns[prevIndex]?.id || null,
352
+ });
353
+ }
354
+ },
355
+
356
+ // Get nodes for a specific turn (or all if null)
357
+ getNodesForTurn: (turnId: string | null): OpsNode[] => {
358
+ const { currentSession } = get();
359
+ if (!currentSession) return [];
360
+
361
+ if (turnId === null) {
362
+ return currentSession.nodes;
363
+ }
364
+
365
+ return currentSession.nodes.filter(n => n.turnId === turnId);
366
+ },
367
+
368
+ // Get edges for a specific turn (or all if null)
369
+ getEdgesForTurn: (turnId: string | null): OpsEdge[] => {
370
+ const { currentSession } = get();
371
+ if (!currentSession) return [];
372
+
373
+ if (turnId === null) {
374
+ return currentSession.edges;
375
+ }
376
+
377
+ return currentSession.edges.filter(e => e.turnId === turnId);
378
+ },
379
+
380
+ // Calculate summary
381
+ getSummary: (): OpsSessionSummary => {
382
+ const { currentSession, selectedTurnId } = get();
383
+ if (!currentSession) return createEmptySummary();
384
+
385
+ const summary = createEmptySummary();
386
+ const nodes = selectedTurnId
387
+ ? currentSession.nodes.filter(n => n.turnId === selectedTurnId)
388
+ : currentSession.nodes;
389
+
390
+ let totalDuration = 0;
391
+
392
+ for (const node of nodes) {
393
+ if (node.duration) {
394
+ totalDuration += node.duration;
395
+ }
396
+
397
+ switch (node.type) {
398
+ case 'llm_call':
399
+ case 'llm_response': {
400
+ summary.llmCalls++;
401
+ const llmData = node.data as { inputTokens?: number; outputTokens?: number; totalTokens?: number };
402
+ if (llmData.inputTokens) summary.inputTokens += llmData.inputTokens;
403
+ if (llmData.outputTokens) summary.outputTokens += llmData.outputTokens;
404
+ if (llmData.totalTokens) summary.totalTokens += llmData.totalTokens;
405
+ break;
406
+ }
407
+ case 'tool_call':
408
+ summary.toolCalls++;
409
+ break;
410
+ case 'tool_result': {
411
+ const resultData = node.data as { status?: string };
412
+ if (resultData.status === 'error') {
413
+ summary.errors++;
414
+ }
415
+ break;
416
+ }
417
+ case 'widget_render':
418
+ summary.widgetRenders++;
419
+ break;
420
+ }
421
+ }
422
+
423
+ summary.totalDuration = totalDuration;
424
+
425
+ return summary;
426
+ },
427
+ }));