@flowdrop/flowdrop 1.9.0 → 1.11.0

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 (32) hide show
  1. package/dist/api/enhanced-client.js +5 -1
  2. package/dist/components/Navbar.svelte +1 -10
  3. package/dist/components/Navbar.svelte.d.ts +1 -9
  4. package/dist/components/PipelineStatus.svelte +17 -1
  5. package/dist/components/PipelineStatus.svelte.d.ts +2 -0
  6. package/dist/components/WorkflowEditor.svelte +26 -0
  7. package/dist/components/playground/ChatPanel.svelte +33 -235
  8. package/dist/components/playground/ExecutionList.svelte +2 -6
  9. package/dist/components/playground/MessageBubble.svelte +61 -4
  10. package/dist/components/playground/PipelinePanel.svelte +17 -7
  11. package/dist/components/playground/PipelinePanel.svelte.d.ts +2 -1
  12. package/dist/components/playground/Playground.svelte +123 -73
  13. package/dist/components/playground/PlaygroundApp.svelte +110 -0
  14. package/dist/components/playground/PlaygroundApp.svelte.d.ts +28 -0
  15. package/dist/components/playground/PlaygroundStudio.svelte +404 -0
  16. package/dist/components/playground/PlaygroundStudio.svelte.d.ts +30 -0
  17. package/dist/editor/index.d.ts +1 -1
  18. package/dist/editor/index.js +1 -1
  19. package/dist/playground/index.d.ts +8 -3
  20. package/dist/playground/index.js +15 -5
  21. package/dist/playground/mount.d.ts +63 -4
  22. package/dist/playground/mount.js +173 -84
  23. package/dist/services/nodeExecutionService.js +4 -2
  24. package/dist/services/playgroundService.d.ts +11 -4
  25. package/dist/services/playgroundService.js +22 -12
  26. package/dist/stores/playgroundStore.svelte.d.ts +22 -21
  27. package/dist/stores/playgroundStore.svelte.js +79 -58
  28. package/dist/svelte-app.d.ts +2 -10
  29. package/dist/types/navbar.d.ts +14 -0
  30. package/dist/types/navbar.js +1 -0
  31. package/dist/types/playground.d.ts +3 -5
  32. package/package.json +1 -1
@@ -46,9 +46,76 @@
46
46
  import { mount, unmount } from 'svelte';
47
47
  import Playground from '../components/playground/Playground.svelte';
48
48
  import PlaygroundModal from '../components/playground/PlaygroundModal.svelte';
49
+ import PlaygroundStudio from '../components/playground/PlaygroundStudio.svelte';
50
+ import PlaygroundApp from '../components/playground/PlaygroundApp.svelte';
51
+ import { initializeSettings } from '../stores/settingsStore.svelte.js';
49
52
  import { setEndpointConfig } from '../services/api.js';
50
53
  import { playgroundService } from '../services/playgroundService.js';
51
- import { getCurrentSession, getSessions, getMessages, playgroundActions, createPollingCallback, subscribeToSessionStatus } from '../stores/playgroundStore.svelte.js';
54
+ import { getCurrentSession, getSessions, getMessages, getIsExecuting, playgroundActions, applyServerResponse, subscribeToSessionStatus } from '../stores/playgroundStore.svelte.js';
55
+ async function resolveEndpointConfig(endpointConfig) {
56
+ if (!endpointConfig)
57
+ return undefined;
58
+ const { defaultEndpointConfig } = await import('../config/endpoints.js');
59
+ const resolved = {
60
+ ...defaultEndpointConfig,
61
+ ...endpointConfig,
62
+ endpoints: { ...defaultEndpointConfig.endpoints, ...endpointConfig.endpoints }
63
+ };
64
+ setEndpointConfig(resolved);
65
+ return resolved;
66
+ }
67
+ /**
68
+ * Shared prelude for the playground mount functions: resolves the endpoint
69
+ * config and initializes settings/theme. Each mount function still owns its
70
+ * own argument validation and container sizing (those vary per mode).
71
+ */
72
+ async function prepareMount(options) {
73
+ const finalEndpointConfig = await resolveEndpointConfig(options.endpointConfig);
74
+ await initializeSettings({ defaults: options.settings });
75
+ return finalEndpointConfig;
76
+ }
77
+ /**
78
+ * Apply caller-supplied height/width as inline styles. When omitted, the host
79
+ * CSS owns container sizing — this is deliberate: a default of `100%` does not
80
+ * resolve inside parents with only `min-height` set (a common pattern in
81
+ * Drupal admin chrome), collapsing the playground to zero.
82
+ */
83
+ function sizeContainer(container, height, width) {
84
+ if (height !== undefined)
85
+ container.style.height = height;
86
+ if (width !== undefined)
87
+ container.style.width = width;
88
+ }
89
+ function buildMountedPlayground(svelteApp, workflowId, config, onSessionStatusChange) {
90
+ const pollingInterval = config.pollingInterval ?? 1500;
91
+ const unsubscribeStatus = onSessionStatusChange
92
+ ? subscribeToSessionStatus(onSessionStatusChange)
93
+ : undefined;
94
+ return {
95
+ destroy: () => {
96
+ unsubscribeStatus?.();
97
+ playgroundService.stopPolling();
98
+ playgroundActions.reset();
99
+ unmount(svelteApp);
100
+ },
101
+ getCurrentSession: () => getCurrentSession(),
102
+ getSessions: () => getSessions(),
103
+ getMessageCount: () => getMessages().length,
104
+ isExecuting: () => getIsExecuting(),
105
+ stopPolling: () => playgroundService.stopPolling(),
106
+ startPolling: () => {
107
+ const session = getCurrentSession();
108
+ if (session) {
109
+ playgroundService.startPolling(session.id, (response) => applyServerResponse(response), pollingInterval, config.shouldStopPolling, playgroundService.getLastSequenceNumber());
110
+ }
111
+ },
112
+ pushMessages: (response) => applyServerResponse(response),
113
+ reset: () => {
114
+ playgroundService.stopPolling();
115
+ playgroundActions.reset();
116
+ }
117
+ };
118
+ }
52
119
  /**
53
120
  * Mount the Playground component in a container
54
121
  *
@@ -79,7 +146,7 @@ import { getCurrentSession, getSessions, getMessages, playgroundActions, createP
79
146
  * ```
80
147
  */
81
148
  export async function mountPlayground(container, options) {
82
- const { workflowId, workflow, mode = 'standalone', initialSessionId, endpointConfig, config = {}, height = '100%', width = '100%', onClose, onSessionStatusChange } = options;
149
+ const { workflowId, workflow, mode = 'standalone', initialSessionId, endpointConfig, config = {}, height, width, settings: initialSettings, onClose, onSessionStatusChange } = options;
83
150
  // Validate required parameters
84
151
  if (!workflowId) {
85
152
  throw new Error('workflowId is required for mountPlayground()');
@@ -91,37 +158,17 @@ export async function mountPlayground(container, options) {
91
158
  if (mode === 'modal' && !onClose) {
92
159
  throw new Error('onClose callback is required for modal mode');
93
160
  }
94
- // Set endpoint configuration if provided
95
- let finalEndpointConfig;
96
- if (endpointConfig) {
97
- // Merge with default configuration to ensure all required endpoints are present
98
- const { defaultEndpointConfig } = await import('../config/endpoints.js');
99
- finalEndpointConfig = {
100
- ...defaultEndpointConfig,
101
- ...endpointConfig,
102
- endpoints: {
103
- ...defaultEndpointConfig.endpoints,
104
- ...endpointConfig.endpoints
105
- }
106
- };
107
- setEndpointConfig(finalEndpointConfig);
108
- }
109
- // Handle modal mode differently
110
- // For modal mode, PlaygroundModal creates its own backdrop, so we mount directly to body
111
- // For other modes, use the provided container
161
+ const finalEndpointConfig = await prepareMount({
162
+ endpointConfig,
163
+ settings: initialSettings
164
+ });
112
165
  let targetContainer = container;
113
166
  if (mode === 'modal') {
114
- // For modal mode, create a container in the body
115
- // PlaygroundModal will handle the backdrop itself
116
167
  targetContainer = document.body;
117
168
  }
118
169
  else {
119
- // Apply container styling for non-modal modes
120
- container.style.height = height;
121
- container.style.width = width;
170
+ sizeContainer(container, height, width);
122
171
  }
123
- // Mount the appropriate component
124
- // Separate the mount calls to avoid TypeScript inference issues with different props types
125
172
  let svelteApp;
126
173
  if (mode === 'modal') {
127
174
  svelteApp = mount(PlaygroundModal, {
@@ -155,63 +202,7 @@ export async function mountPlayground(container, options) {
155
202
  }
156
203
  });
157
204
  }
158
- // Store state for cleanup
159
- const state = {
160
- svelteApp,
161
- container: targetContainer,
162
- originalContainer: mode === 'modal' ? container : undefined,
163
- workflowId
164
- };
165
- // Create shared polling callback using lifecycle hooks from config
166
- const pollingCallback = createPollingCallback(config.isTerminalStatus);
167
- const pollingInterval = config.pollingInterval ?? 1500;
168
- // Subscribe to session status changes if callback provided
169
- let unsubscribeStatus;
170
- if (onSessionStatusChange) {
171
- unsubscribeStatus = subscribeToSessionStatus(onSessionStatusChange);
172
- }
173
- // Create the mounted playground interface
174
- const mountedPlayground = {
175
- destroy: () => {
176
- // Unsubscribe from status changes
177
- unsubscribeStatus?.();
178
- // Stop any active polling
179
- playgroundService.stopPolling();
180
- // Reset playground state
181
- playgroundActions.reset();
182
- // Unmount Svelte component
183
- unmount(state.svelteApp);
184
- },
185
- getCurrentSession: () => {
186
- return getCurrentSession();
187
- },
188
- getSessions: () => {
189
- return getSessions();
190
- },
191
- getMessageCount: () => {
192
- return getMessages().length;
193
- },
194
- isExecuting: () => {
195
- return playgroundService.isPolling();
196
- },
197
- stopPolling: () => {
198
- playgroundService.stopPolling();
199
- },
200
- startPolling: () => {
201
- const session = getCurrentSession();
202
- if (session) {
203
- playgroundService.startPolling(session.id, pollingCallback, pollingInterval, config.shouldStopPolling);
204
- }
205
- },
206
- pushMessages: (response) => {
207
- pollingCallback(response);
208
- },
209
- reset: () => {
210
- playgroundService.stopPolling();
211
- playgroundActions.reset();
212
- }
213
- };
214
- return mountedPlayground;
205
+ return buildMountedPlayground(svelteApp, workflowId, config, onSessionStatusChange);
215
206
  }
216
207
  /**
217
208
  * Unmount a Playground instance
@@ -233,3 +224,101 @@ export function unmountPlayground(app) {
233
224
  app.destroy();
234
225
  }
235
226
  }
227
+ export async function mountPlaygroundStudio(container, options) {
228
+ const { workflowId, workflow, mode = 'standalone', initialSessionId, endpointConfig, config = {}, height, width, initialPipelineOpen, minChatWidth, initialPipelineWidth, settings: initialSettings, onClose, onSessionNavigate, onSessionStatusChange } = options;
229
+ if (!workflowId) {
230
+ throw new Error('workflowId is required for mountPlaygroundStudio()');
231
+ }
232
+ if (!container) {
233
+ throw new Error('container element is required for mountPlaygroundStudio()');
234
+ }
235
+ if (mode === 'modal') {
236
+ throw new Error('modal mode is not supported by mountPlaygroundStudio() — use mountPlayground() instead');
237
+ }
238
+ const finalEndpointConfig = await prepareMount({
239
+ endpointConfig,
240
+ settings: initialSettings
241
+ });
242
+ sizeContainer(container, height, width);
243
+ const svelteApp = mount(PlaygroundStudio, {
244
+ target: container,
245
+ props: {
246
+ workflowId,
247
+ workflow,
248
+ mode,
249
+ initialSessionId,
250
+ endpointConfig: finalEndpointConfig,
251
+ config,
252
+ onClose,
253
+ onSessionNavigate,
254
+ initialPipelineOpen,
255
+ minChatWidth,
256
+ initialPipelineWidth
257
+ }
258
+ });
259
+ return buildMountedPlayground(svelteApp, workflowId, config, onSessionStatusChange);
260
+ }
261
+ /**
262
+ * Mount the full-page PlaygroundApp (Navbar + PlaygroundStudio) into a container.
263
+ *
264
+ * Use this when you want the same chrome as the FlowDrop editor — logo,
265
+ * branding, and settings modal — wrapped around the playground. For an
266
+ * embeddable split-pane without the navbar, use mountPlaygroundStudio().
267
+ *
268
+ * @example
269
+ * ```typescript
270
+ * const app = await mountPlaygroundApp(container, {
271
+ * workflowId: 'wf-123',
272
+ * endpointConfig: createEndpointConfig('/api/flowdrop'),
273
+ * navbarTitle: 'My Workflow',
274
+ * primaryActions: [
275
+ * { label: 'Edit', href: '/workflows/wf-123/edit', icon: 'mdi:pencil-outline', variant: 'secondary' },
276
+ * { label: 'Workflows', href: '/workflows', icon: 'mdi:arrow-left', variant: 'outline' }
277
+ * ]
278
+ * });
279
+ * ```
280
+ */
281
+ export async function mountPlaygroundApp(container, options) {
282
+ const { workflowId, workflow, mode = 'standalone', initialSessionId, endpointConfig, config = {}, height, width, showNavbar = true, navbarTitle, primaryActions, showSettings = true, settingsCategories, showSettingsSyncButton, showSettingsResetButton, initialPipelineOpen, minChatWidth, initialPipelineWidth, settings: initialSettings, onClose, onSessionNavigate, onSessionStatusChange } = options;
283
+ if (!workflowId) {
284
+ throw new Error('workflowId is required for mountPlaygroundApp()');
285
+ }
286
+ if (!container) {
287
+ throw new Error('container element is required for mountPlaygroundApp()');
288
+ }
289
+ // Positive narrowing (not `=== 'modal'`) because PlaygroundAppMountOptions
290
+ // Omit-narrows `mode`, so the type lies for JS callers — check the values
291
+ // we actually accept instead of the one we don't.
292
+ if (mode !== 'standalone' && mode !== 'embedded') {
293
+ throw new Error(`mountPlaygroundApp(): mode must be 'standalone' or 'embedded', got ${String(mode)}`);
294
+ }
295
+ const finalEndpointConfig = await prepareMount({
296
+ endpointConfig,
297
+ settings: initialSettings
298
+ });
299
+ sizeContainer(container, height, width);
300
+ const svelteApp = mount(PlaygroundApp, {
301
+ target: container,
302
+ props: {
303
+ workflowId,
304
+ workflow,
305
+ mode,
306
+ initialSessionId,
307
+ endpointConfig: finalEndpointConfig,
308
+ config,
309
+ showNavbar,
310
+ navbarTitle,
311
+ primaryActions,
312
+ showSettings,
313
+ settingsCategories,
314
+ showSettingsSyncButton,
315
+ showSettingsResetButton,
316
+ initialPipelineOpen,
317
+ minChatWidth,
318
+ initialPipelineWidth,
319
+ onClose,
320
+ onSessionNavigate
321
+ }
322
+ });
323
+ return buildMountedPlayground(svelteApp, workflowId, config, onSessionStatusChange);
324
+ }
@@ -41,7 +41,8 @@ export class NodeExecutionService {
41
41
  if (!response.ok) {
42
42
  throw new Error(`HTTP error! status: ${response.status}`);
43
43
  }
44
- const pipelineData = await response.json();
44
+ const raw = await response.json();
45
+ const pipelineData = raw.data ?? raw;
45
46
  const jobs = pipelineData.jobs || [];
46
47
  const nodeStatuses = pipelineData.node_statuses || {};
47
48
  // Find the job for this node
@@ -116,7 +117,8 @@ export class NodeExecutionService {
116
117
  }
117
118
  throw new Error(`HTTP error! status: ${response.status}`);
118
119
  }
119
- const result = await response.json();
120
+ const raw = await response.json();
121
+ const result = raw.data ?? raw;
120
122
  const jobs = result.jobs || [];
121
123
  const executionInfoMap = {};
122
124
  // Initialize all nodes with default values
@@ -18,7 +18,7 @@ export declare class PlaygroundService {
18
18
  private pollingInterval;
19
19
  private pollingSessionId;
20
20
  private currentBackoff;
21
- private lastMessageTimestamp;
21
+ private lastSequenceNumber;
22
22
  private constructor();
23
23
  /**
24
24
  * Get the singleton instance of PlaygroundService
@@ -78,11 +78,11 @@ export declare class PlaygroundService {
78
78
  * Get messages from a playground session
79
79
  *
80
80
  * @param sessionId - The session UUID
81
- * @param since - Optional timestamp to fetch only newer messages (ISO 8601)
81
+ * @param afterSequence - Optional sequence number cursor — returns only messages with sequenceNumber > this value
82
82
  * @param limit - Maximum number of messages to return
83
83
  * @returns Messages and session status
84
84
  */
85
- getMessages(sessionId: string, since?: string, limit?: number): Promise<PlaygroundMessagesApiResponse>;
85
+ getMessages(sessionId: string, afterSequence?: number, limit?: number): Promise<PlaygroundMessagesApiResponse>;
86
86
  /**
87
87
  * Send a message to a playground session
88
88
  *
@@ -105,8 +105,9 @@ export declare class PlaygroundService {
105
105
  * @param callback - Callback function to handle new messages
106
106
  * @param interval - Polling interval in milliseconds (default: 1500)
107
107
  * @param shouldStopPolling - Optional override for stop conditions (default: defaultShouldStopPolling)
108
+ * @param initialSequenceNumber - Optional sequence number to seed polling from (avoids re-fetching already loaded messages)
108
109
  */
109
- startPolling(sessionId: string, callback: (response: PlaygroundMessagesApiResponse) => void, interval?: number, shouldStopPolling?: (status: PlaygroundSessionStatus) => boolean): void;
110
+ startPolling(sessionId: string, callback: (response: PlaygroundMessagesApiResponse) => void, interval?: number, shouldStopPolling?: (status: PlaygroundSessionStatus) => boolean, initialSequenceNumber?: number | null): void;
110
111
  /**
111
112
  * Stop polling for messages
112
113
  */
@@ -123,6 +124,12 @@ export declare class PlaygroundService {
123
124
  * @returns The session ID being polled, or null
124
125
  */
125
126
  getPollingSessionId(): string | null;
127
+ /**
128
+ * Get the last sequence number used as cursor for incremental polling
129
+ *
130
+ * @returns The last sequence number, or null
131
+ */
132
+ getLastSequenceNumber(): number | null;
126
133
  }
127
134
  /**
128
135
  * Export singleton instance
@@ -29,7 +29,7 @@ export class PlaygroundService {
29
29
  pollingInterval = null;
30
30
  pollingSessionId = null;
31
31
  currentBackoff = DEFAULT_POLLING_INTERVAL;
32
- lastMessageTimestamp = null;
32
+ lastSequenceNumber = null;
33
33
  constructor() { }
34
34
  /**
35
35
  * Get the singleton instance of PlaygroundService
@@ -171,19 +171,18 @@ export class PlaygroundService {
171
171
  * Get messages from a playground session
172
172
  *
173
173
  * @param sessionId - The session UUID
174
- * @param since - Optional timestamp to fetch only newer messages (ISO 8601)
174
+ * @param afterSequence - Optional sequence number cursor — returns only messages with sequenceNumber > this value
175
175
  * @param limit - Maximum number of messages to return
176
176
  * @returns Messages and session status
177
177
  */
178
- async getMessages(sessionId, since, limit) {
178
+ async getMessages(sessionId, afterSequence, limit) {
179
179
  const config = this.getConfig();
180
180
  let url = buildEndpointUrl(config, config.endpoints.playground.getMessages, {
181
181
  sessionId
182
182
  });
183
- // Add query parameters
184
183
  const params = new URLSearchParams();
185
- if (since) {
186
- params.append('since', since);
184
+ if (afterSequence !== undefined) {
185
+ params.append('since', afterSequence.toString());
187
186
  }
188
187
  if (limit !== undefined) {
189
188
  params.append('limit', limit.toString());
@@ -244,24 +243,27 @@ export class PlaygroundService {
244
243
  * @param callback - Callback function to handle new messages
245
244
  * @param interval - Polling interval in milliseconds (default: 1500)
246
245
  * @param shouldStopPolling - Optional override for stop conditions (default: defaultShouldStopPolling)
246
+ * @param initialSequenceNumber - Optional sequence number to seed polling from (avoids re-fetching already loaded messages)
247
247
  */
248
- startPolling(sessionId, callback, interval = DEFAULT_POLLING_INTERVAL, shouldStopPolling) {
248
+ startPolling(sessionId, callback, interval = DEFAULT_POLLING_INTERVAL, shouldStopPolling, initialSequenceNumber) {
249
249
  // Stop any existing polling
250
250
  this.stopPolling();
251
251
  this.pollingSessionId = sessionId;
252
252
  this.currentBackoff = interval;
253
- this.lastMessageTimestamp = null;
253
+ this.lastSequenceNumber = initialSequenceNumber ?? null;
254
254
  const shouldStop = shouldStopPolling ?? defaultShouldStopPolling;
255
255
  const poll = async () => {
256
256
  if (this.pollingSessionId !== sessionId) {
257
257
  return;
258
258
  }
259
259
  try {
260
- const response = await this.getMessages(sessionId, this.lastMessageTimestamp ?? undefined);
261
- // Update last message timestamp
260
+ const response = await this.getMessages(sessionId, this.lastSequenceNumber ?? undefined);
261
+ // Update last sequence number cursor
262
262
  if (response.data && response.data.length > 0) {
263
263
  const lastMessage = response.data[response.data.length - 1];
264
- this.lastMessageTimestamp = lastMessage.timestamp;
264
+ if (lastMessage.sequenceNumber !== undefined) {
265
+ this.lastSequenceNumber = lastMessage.sequenceNumber;
266
+ }
265
267
  }
266
268
  // Reset backoff on successful request
267
269
  this.currentBackoff = interval;
@@ -295,7 +297,7 @@ export class PlaygroundService {
295
297
  this.pollingInterval = null;
296
298
  }
297
299
  this.pollingSessionId = null;
298
- this.lastMessageTimestamp = null;
300
+ this.lastSequenceNumber = null;
299
301
  this.currentBackoff = DEFAULT_POLLING_INTERVAL;
300
302
  }
301
303
  /**
@@ -314,6 +316,14 @@ export class PlaygroundService {
314
316
  getPollingSessionId() {
315
317
  return this.pollingSessionId;
316
318
  }
319
+ /**
320
+ * Get the last sequence number used as cursor for incremental polling
321
+ *
322
+ * @returns The last sequence number, or null
323
+ */
324
+ getLastSequenceNumber() {
325
+ return this.lastSequenceNumber;
326
+ }
317
327
  }
318
328
  /**
319
329
  * Export singleton instance
@@ -37,13 +37,18 @@ export declare function getError(): string | null;
37
37
  */
38
38
  export declare function getCurrentWorkflow(): Workflow | null;
39
39
  /**
40
- * Get the last poll timestamp
40
+ * Get the last poll sequence number cursor
41
41
  */
42
- export declare function getLastPollTimestamp(): string | null;
42
+ export declare function getLastPollSequenceNumber(): number | null;
43
43
  /**
44
44
  * Get current session status
45
45
  */
46
46
  export declare function getSessionStatus(): PlaygroundSessionStatus;
47
+ /**
48
+ * Whether the user can currently send a message.
49
+ * False when executing, when awaiting input, or when no session exists.
50
+ */
51
+ export declare function getCanSendMessage(): boolean;
47
52
  /**
48
53
  * Get message count
49
54
  */
@@ -78,6 +83,12 @@ export declare function getSessionCount(): number;
78
83
  export declare function getPinnedExecutionId(): string | null;
79
84
  export declare function getLatestExecutionId(): string | null;
80
85
  export declare function getActiveExecutionId(): string | null;
86
+ /**
87
+ * Counter that increments whenever new messages arrive and the pipeline display
88
+ * should re-fetch — i.e. when following latest or pinned to the latest execution.
89
+ * Pass to PipelinePanel's refreshTrigger prop.
90
+ */
91
+ export declare function getPipelineRefreshTrigger(): number;
81
92
  /**
82
93
  * Playground store actions for modifying state
83
94
  */
@@ -143,12 +154,6 @@ export declare const playgroundActions: {
143
154
  * Clear all messages
144
155
  */
145
156
  clearMessages: () => void;
146
- /**
147
- * Set the executing state
148
- *
149
- * @param executing - Whether execution is in progress
150
- */
151
- setExecuting: (executing: boolean) => void;
152
157
  /**
153
158
  * Set the loading state
154
159
  *
@@ -166,7 +171,7 @@ export declare const playgroundActions: {
166
171
  *
167
172
  * @param timestamp - ISO 8601 timestamp
168
173
  */
169
- updateLastPollTimestamp: (timestamp: string) => void;
174
+ updateLastPollSequenceNumber: (seq: number) => void;
170
175
  /**
171
176
  * Reset all playground state
172
177
  */
@@ -180,14 +185,11 @@ export declare const playgroundActions: {
180
185
  pinExecution(executionId: string | null): void;
181
186
  };
182
187
  /**
183
- * Create a polling callback that processes poll responses.
184
- * This is the single source of truth for how poll responses update stores.
185
- * Used by mount.ts, Playground.svelte, and refreshSessionMessages.
186
- *
187
- * @param isTerminalStatus - Function to determine if a status clears isExecuting (default: defaultIsTerminalStatus)
188
- * @returns A callback suitable for playgroundService.startPolling() or pushMessages()
188
+ * Apply a server response to the store. All message and status updates from
189
+ * the server flow through here polling callback, manual fetches, interrupt
190
+ * resolution. Nothing updates messages or session status except this function.
189
191
  */
190
- export declare function createPollingCallback(isTerminalStatus?: (status: PlaygroundSessionStatus) => boolean): (response: PlaygroundMessagesApiResponse) => void;
192
+ export declare function applyServerResponse(response: PlaygroundMessagesApiResponse): void;
191
193
  /**
192
194
  * Get the current session ID
193
195
  *
@@ -208,11 +210,11 @@ export declare function isSessionSelected(sessionId: string): boolean;
208
210
  */
209
211
  export declare function getMessagesSnapshot(): PlaygroundMessage[];
210
212
  /**
211
- * Get the latest message timestamp for polling
213
+ * Get the sequence number of the latest message, used to seed incremental polling.
212
214
  *
213
- * @returns ISO 8601 timestamp of the latest message, or null
215
+ * @returns Sequence number of the last message, or null
214
216
  */
215
- export declare function getLatestMessageTimestamp(): string | null;
217
+ export declare function getLatestSequenceNumber(): number | null;
216
218
  /**
217
219
  * Subscribe to session status changes using $effect.root.
218
220
  * This is designed for use in non-component contexts (e.g., mount.ts).
@@ -228,7 +230,6 @@ export declare function subscribeToSessionStatus(callback: (status: PlaygroundSe
228
230
  * has stopped but new messages may exist on the server.
229
231
  *
230
232
  * @param fetchMessages - Async function to fetch messages from the API
231
- * @param isTerminalStatus - Optional override for terminal status check
232
233
  * @returns Promise that resolves when messages are refreshed
233
234
  */
234
- export declare function refreshSessionMessages(fetchMessages: (sessionId: string) => Promise<PlaygroundMessagesApiResponse>, isTerminalStatus?: (status: PlaygroundSessionStatus) => boolean): Promise<void>;
235
+ export declare function refreshSessionMessages(fetchMessages: (sessionId: string) => Promise<PlaygroundMessagesApiResponse>): Promise<void>;