@fragno-dev/pi-fragment 0.0.1 → 0.0.3

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 (98) hide show
  1. package/README.md +39 -3
  2. package/dist/browser/client/react.d.ts +44 -36
  3. package/dist/browser/client/react.d.ts.map +1 -1
  4. package/dist/browser/client/react.js +105 -22
  5. package/dist/browser/client/react.js.map +1 -1
  6. package/dist/browser/client/solid.d.ts +42 -36
  7. package/dist/browser/client/solid.d.ts.map +1 -1
  8. package/dist/browser/client/solid.js +27 -13
  9. package/dist/browser/client/solid.js.map +1 -1
  10. package/dist/browser/client/svelte.d.ts +42 -36
  11. package/dist/browser/client/svelte.d.ts.map +1 -1
  12. package/dist/browser/client/svelte.js +14 -6
  13. package/dist/browser/client/svelte.js.map +1 -1
  14. package/dist/browser/client/vanilla.d.ts +99 -39
  15. package/dist/browser/client/vanilla.d.ts.map +1 -1
  16. package/dist/browser/client/vanilla.js +151 -3
  17. package/dist/browser/client/vanilla.js.map +1 -1
  18. package/dist/browser/client/vue.d.ts +54 -38
  19. package/dist/browser/client/vue.d.ts.map +1 -1
  20. package/dist/browser/client/vue.js +25 -17
  21. package/dist/browser/client/vue.js.map +1 -1
  22. package/dist/browser/{factory-DKoO_lRA.js → clients-BscY_HVe.js} +1051 -799
  23. package/dist/browser/clients-BscY_HVe.js.map +1 -0
  24. package/dist/browser/index.d.ts +3 -776
  25. package/dist/browser/index.js +801 -2
  26. package/dist/browser/index.js.map +1 -0
  27. package/dist/browser/routes-CpL_YGWK.d.ts +1560 -0
  28. package/dist/browser/routes-CpL_YGWK.d.ts.map +1 -0
  29. package/dist/cli/mod.d.ts.map +1 -1
  30. package/dist/cli/mod.js +245 -7
  31. package/dist/cli/mod.js.map +1 -1
  32. package/dist/node/{pi → client}/clients.d.ts +46 -36
  33. package/dist/node/client/clients.d.ts.map +1 -0
  34. package/dist/node/client/clients.js +54 -0
  35. package/dist/node/client/clients.js.map +1 -0
  36. package/dist/node/client/session-controller.d.ts +31 -0
  37. package/dist/node/client/session-controller.d.ts.map +1 -0
  38. package/dist/node/client/session-controller.js +33 -0
  39. package/dist/node/client/session-controller.js.map +1 -0
  40. package/dist/node/client/session-store.d.ts +71 -0
  41. package/dist/node/client/session-store.d.ts.map +1 -0
  42. package/dist/node/client/session-store.js +637 -0
  43. package/dist/node/client/session-store.js.map +1 -0
  44. package/dist/node/debug-log.d.ts +9 -0
  45. package/dist/node/debug-log.d.ts.map +1 -0
  46. package/dist/node/debug-log.js +58 -0
  47. package/dist/node/debug-log.js.map +1 -0
  48. package/dist/node/index.d.ts +5 -4
  49. package/dist/node/index.js +5 -3
  50. package/dist/node/pi/definition.d.ts +1 -1
  51. package/dist/node/pi/definition.d.ts.map +1 -1
  52. package/dist/node/pi/dsl.d.ts +5 -2
  53. package/dist/node/pi/dsl.d.ts.map +1 -1
  54. package/dist/node/pi/dsl.js +22 -3
  55. package/dist/node/pi/dsl.js.map +1 -1
  56. package/dist/node/pi/factory.d.ts +37 -34
  57. package/dist/node/pi/factory.d.ts.map +1 -1
  58. package/dist/node/pi/factory.js.map +1 -1
  59. package/dist/node/pi/mappers.js +0 -1
  60. package/dist/node/pi/mappers.js.map +1 -1
  61. package/dist/node/pi/route-schemas.js +42 -10
  62. package/dist/node/pi/route-schemas.js.map +1 -1
  63. package/dist/node/pi/types.d.ts +155 -7
  64. package/dist/node/pi/types.d.ts.map +1 -1
  65. package/dist/node/pi/types.js +6 -0
  66. package/dist/node/pi/types.js.map +1 -0
  67. package/dist/node/pi/workflow/active-session.d.ts +2 -0
  68. package/dist/node/pi/workflow/active-session.js +107 -0
  69. package/dist/node/pi/workflow/active-session.js.map +1 -0
  70. package/dist/node/pi/workflow/agent-runner.d.ts +13 -0
  71. package/dist/node/pi/workflow/agent-runner.d.ts.map +1 -0
  72. package/dist/node/pi/workflow/agent-runner.js +228 -0
  73. package/dist/node/pi/workflow/agent-runner.js.map +1 -0
  74. package/dist/node/pi/workflow/tool-journal.js +157 -0
  75. package/dist/node/pi/workflow/tool-journal.js.map +1 -0
  76. package/dist/node/pi/workflow/workflow.d.ts +29 -0
  77. package/dist/node/pi/workflow/workflow.d.ts.map +1 -0
  78. package/dist/node/pi/workflow/workflow.js +219 -0
  79. package/dist/node/pi/workflow/workflow.js.map +1 -0
  80. package/dist/node/routes.d.ts +38 -35
  81. package/dist/node/routes.d.ts.map +1 -1
  82. package/dist/node/routes.js +203 -132
  83. package/dist/node/routes.js.map +1 -1
  84. package/dist/node/schema.js +1 -1
  85. package/dist/node/schema.js.map +1 -1
  86. package/package.json +30 -29
  87. package/dist/browser/client-Bk-J98pf.d.ts +0 -679
  88. package/dist/browser/client-Bk-J98pf.d.ts.map +0 -1
  89. package/dist/browser/factory-DKoO_lRA.js.map +0 -1
  90. package/dist/browser/index.d.ts.map +0 -1
  91. package/dist/node/pi/clients.d.ts.map +0 -1
  92. package/dist/node/pi/clients.js +0 -18
  93. package/dist/node/pi/clients.js.map +0 -1
  94. package/dist/node/pi/workflow.d.ts +0 -31
  95. package/dist/node/pi/workflow.d.ts.map +0 -1
  96. package/dist/node/pi/workflow.js +0 -242
  97. package/dist/node/pi/workflow.js.map +0 -1
  98. package/dist/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,637 @@
1
+ import { atom, computed, onMount } from "nanostores";
2
+
3
+ //#region src/client/session-store.ts
4
+ const createOverlayState = (sessionId) => ({
5
+ sessionId,
6
+ connection: "idle",
7
+ error: null,
8
+ sendError: null,
9
+ sending: false,
10
+ activity: { kind: "none" },
11
+ pendingTurn: false,
12
+ pendingSnapshotReconcile: false,
13
+ readyForInputOverride: null,
14
+ optimisticMessages: [],
15
+ streamedMessages: [],
16
+ draftAssistant: null,
17
+ runningTools: [],
18
+ trace: []
19
+ });
20
+ const buildOptimisticUserMessage = (text) => ({
21
+ role: "user",
22
+ content: [{
23
+ type: "text",
24
+ text
25
+ }],
26
+ timestamp: Date.now()
27
+ });
28
+ const formatJson = (value) => {
29
+ try {
30
+ return JSON.stringify(value, null, 2);
31
+ } catch {
32
+ return String(value);
33
+ }
34
+ };
35
+ const buildLiveToolResultMessage = (message) => {
36
+ const result = message.result;
37
+ const content = result && Array.isArray(result.content) ? result.content : [{
38
+ type: "text",
39
+ text: formatJson(message.result)
40
+ }];
41
+ return {
42
+ role: "toolResult",
43
+ toolCallId: message.toolCallId,
44
+ toolName: message.toolName,
45
+ content,
46
+ details: result?.details,
47
+ isError: message.isError,
48
+ timestamp: Date.now()
49
+ };
50
+ };
51
+ const appendOrReplaceLiveMessage = (messages, nextMessage) => {
52
+ if (nextMessage.role === "assistant") {
53
+ const existingIndex = messages.findIndex((message) => message.role === "assistant" && message.timestamp === nextMessage.timestamp);
54
+ if (existingIndex >= 0) return [
55
+ ...messages.slice(0, existingIndex),
56
+ nextMessage,
57
+ ...messages.slice(existingIndex + 1)
58
+ ];
59
+ }
60
+ if (nextMessage.role === "toolResult") {
61
+ const existingIndex = messages.findIndex((message) => message.role === "toolResult" && message.toolCallId === nextMessage.toolCallId);
62
+ if (existingIndex >= 0) return [
63
+ ...messages.slice(0, existingIndex),
64
+ nextMessage,
65
+ ...messages.slice(existingIndex + 1)
66
+ ];
67
+ }
68
+ return [...messages, nextMessage];
69
+ };
70
+ const mergeMessages = (snapshotMessages, optimisticMessages, streamedMessages, draftAssistant) => {
71
+ let messages = [...snapshotMessages];
72
+ for (const optimisticMessage of optimisticMessages) messages = appendOrReplaceLiveMessage(messages, optimisticMessage);
73
+ for (const streamedMessage of streamedMessages) messages = appendOrReplaceLiveMessage(messages, streamedMessage);
74
+ if (draftAssistant) messages = appendOrReplaceLiveMessage(messages, draftAssistant);
75
+ return messages;
76
+ };
77
+ const hasActiveOverlay = (state) => state.draftAssistant !== null || state.runningTools.length > 0 || state.streamedMessages.length > 0;
78
+ const activityLabel = (activity) => {
79
+ switch (activity.kind) {
80
+ case "none": return null;
81
+ case "sending-message": return "Sending message";
82
+ case "assistant-responding": return "Assistant responding";
83
+ case "assistant-ready": return "Assistant response ready";
84
+ case "tool-running": return `Running ${activity.toolName}`;
85
+ case "tool-updating": return `Updating ${activity.toolName}`;
86
+ case "tool-finished": return `${activity.toolName} finished`;
87
+ case "agent-started": return "Agent started";
88
+ case "agent-finished": return "Agent finished";
89
+ case "turn-started": return "Turn started";
90
+ case "turn-finished": return "Turn finished";
91
+ default: return null;
92
+ }
93
+ };
94
+ const connectionStatusText = (connection) => {
95
+ switch (connection) {
96
+ case "connecting": return "Connecting to live updates";
97
+ case "reconnecting": return "Reconnecting to live updates";
98
+ default: return null;
99
+ }
100
+ };
101
+ const reduceProtocolMessage = (state, message) => {
102
+ if (message.layer === "system") switch (message.type) {
103
+ case "snapshot": {
104
+ const readyForInput = isSessionReadyForInput(message.phase, message.waitingFor);
105
+ return {
106
+ ...state,
107
+ error: null,
108
+ activity: readyForInput ? { kind: "none" } : state.activity,
109
+ pendingTurn: !readyForInput,
110
+ readyForInputOverride: readyForInput
111
+ };
112
+ }
113
+ case "inactive": return {
114
+ ...state,
115
+ connection: "idle",
116
+ error: null,
117
+ readyForInputOverride: false
118
+ };
119
+ case "settled": return {
120
+ ...state,
121
+ connection: "reconnecting",
122
+ activity: { kind: "turn-finished" },
123
+ pendingTurn: true,
124
+ pendingSnapshotReconcile: true,
125
+ readyForInputOverride: false,
126
+ error: null
127
+ };
128
+ default: return state;
129
+ }
130
+ const event = message.event;
131
+ const trace = [...state.trace, event];
132
+ switch (event.type) {
133
+ case "message_start":
134
+ case "message_update": return {
135
+ ...state,
136
+ trace,
137
+ draftAssistant: event.message,
138
+ activity: { kind: "assistant-responding" },
139
+ pendingTurn: true,
140
+ error: null
141
+ };
142
+ case "message_end": return {
143
+ ...state,
144
+ trace,
145
+ streamedMessages: appendOrReplaceLiveMessage(state.streamedMessages, event.message),
146
+ draftAssistant: null,
147
+ activity: { kind: "assistant-ready" },
148
+ pendingTurn: true,
149
+ error: null
150
+ };
151
+ case "tool_execution_start": return {
152
+ ...state,
153
+ trace,
154
+ runningTools: [...state.runningTools.filter((tool) => tool.toolCallId !== event.toolCallId), {
155
+ toolCallId: event.toolCallId,
156
+ toolName: event.toolName,
157
+ args: event.args,
158
+ partialResult: null
159
+ }],
160
+ activity: {
161
+ kind: "tool-running",
162
+ toolName: event.toolName
163
+ },
164
+ pendingTurn: true,
165
+ error: null
166
+ };
167
+ case "tool_execution_update": return {
168
+ ...state,
169
+ trace,
170
+ runningTools: state.runningTools.map((tool) => tool.toolCallId === event.toolCallId ? {
171
+ ...tool,
172
+ partialResult: event.partialResult
173
+ } : tool),
174
+ activity: {
175
+ kind: "tool-updating",
176
+ toolName: event.toolName
177
+ },
178
+ pendingTurn: true,
179
+ error: null
180
+ };
181
+ case "tool_execution_end": return {
182
+ ...state,
183
+ trace,
184
+ streamedMessages: appendOrReplaceLiveMessage(state.streamedMessages, buildLiveToolResultMessage(event)),
185
+ runningTools: state.runningTools.filter((tool) => tool.toolCallId !== event.toolCallId),
186
+ activity: {
187
+ kind: "tool-finished",
188
+ toolName: event.toolName
189
+ },
190
+ pendingTurn: true,
191
+ error: null
192
+ };
193
+ case "agent_start": return {
194
+ ...state,
195
+ trace,
196
+ activity: { kind: "agent-started" },
197
+ pendingTurn: true,
198
+ error: null
199
+ };
200
+ case "agent_end": return {
201
+ ...state,
202
+ trace,
203
+ activity: { kind: "agent-finished" },
204
+ pendingTurn: true,
205
+ error: null
206
+ };
207
+ case "turn_start": return {
208
+ ...state,
209
+ trace,
210
+ activity: { kind: "turn-started" },
211
+ pendingTurn: true,
212
+ error: null
213
+ };
214
+ case "turn_end": return {
215
+ ...state,
216
+ trace,
217
+ activity: { kind: "turn-finished" },
218
+ pendingTurn: true,
219
+ error: null
220
+ };
221
+ default: return {
222
+ ...state,
223
+ trace,
224
+ pendingTurn: true,
225
+ error: null
226
+ };
227
+ }
228
+ };
229
+ const reduceOverlayState = (state, action) => {
230
+ switch (action.type) {
231
+ case "snapshot-updated":
232
+ if (action.sessionId !== state.sessionId) return createOverlayState(action.sessionId);
233
+ if (!state.pendingSnapshotReconcile) return {
234
+ ...state,
235
+ optimisticMessages: [],
236
+ sendError: null,
237
+ error: null,
238
+ readyForInputOverride: null
239
+ };
240
+ return {
241
+ ...state,
242
+ optimisticMessages: [],
243
+ streamedMessages: [],
244
+ draftAssistant: null,
245
+ runningTools: [],
246
+ trace: [],
247
+ error: null,
248
+ sendError: null,
249
+ activity: { kind: "none" },
250
+ pendingTurn: false,
251
+ pendingSnapshotReconcile: false,
252
+ readyForInputOverride: null
253
+ };
254
+ case "send-started": return {
255
+ ...state,
256
+ sending: true,
257
+ sendError: null,
258
+ optimisticMessages: [...state.optimisticMessages, {
259
+ clientId: action.clientId,
260
+ message: buildOptimisticUserMessage(action.text)
261
+ }],
262
+ activity: { kind: "sending-message" },
263
+ pendingTurn: true,
264
+ readyForInputOverride: false,
265
+ error: null
266
+ };
267
+ case "send-acknowledged": return {
268
+ ...state,
269
+ sending: false,
270
+ sendError: null
271
+ };
272
+ case "send-failed": {
273
+ const optimisticMessages = state.optimisticMessages.filter((message) => message.clientId !== action.clientId);
274
+ const keepPending = state.pendingSnapshotReconcile || hasActiveOverlay(state);
275
+ return {
276
+ ...state,
277
+ sending: false,
278
+ sendError: action.message,
279
+ optimisticMessages,
280
+ activity: keepPending ? state.activity : { kind: "none" },
281
+ pendingTurn: keepPending,
282
+ readyForInputOverride: keepPending ? state.readyForInputOverride : null
283
+ };
284
+ }
285
+ case "stream-connecting": {
286
+ const connection = action.reconnecting ? "reconnecting" : "connecting";
287
+ return {
288
+ ...state,
289
+ connection,
290
+ error: null
291
+ };
292
+ }
293
+ case "stream-open": return {
294
+ ...state,
295
+ connection: "listening",
296
+ error: null
297
+ };
298
+ case "stream-message": return reduceProtocolMessage(state, action.message);
299
+ case "stream-settled": return {
300
+ ...state,
301
+ connection: "reconnecting"
302
+ };
303
+ case "stream-inactive": return {
304
+ ...state,
305
+ connection: "idle",
306
+ error: null
307
+ };
308
+ case "stream-error": return {
309
+ ...state,
310
+ connection: "error",
311
+ error: action.message
312
+ };
313
+ case "stream-idle": return {
314
+ ...state,
315
+ connection: "idle",
316
+ error: null,
317
+ activity: state.pendingTurn ? state.activity : { kind: "none" }
318
+ };
319
+ default: return state;
320
+ }
321
+ };
322
+ const readPiRouteError = async (response) => {
323
+ const text = await response.text();
324
+ if (!text) return `Request failed (${response.status}).`;
325
+ try {
326
+ const parsed = JSON.parse(text);
327
+ return parsed.message ?? parsed.error ?? text;
328
+ } catch {
329
+ return text;
330
+ }
331
+ };
332
+ const consumeProtocolStream = async (body, onMessage, signal) => {
333
+ const reader = body.getReader();
334
+ const decoder = new TextDecoder();
335
+ let buffer = "";
336
+ try {
337
+ while (true) {
338
+ if (signal.aborted) {
339
+ await reader.cancel();
340
+ return;
341
+ }
342
+ const { done, value } = await reader.read();
343
+ if (done) break;
344
+ buffer += decoder.decode(value, { stream: true });
345
+ const lines = buffer.split("\n");
346
+ buffer = lines.pop() ?? "";
347
+ for (const line of lines) {
348
+ const trimmed = line.trim();
349
+ if (!trimmed) continue;
350
+ onMessage(JSON.parse(trimmed));
351
+ }
352
+ }
353
+ const trailing = `${buffer}${decoder.decode()}`.trim();
354
+ if (trailing) onMessage(JSON.parse(trailing));
355
+ } finally {
356
+ reader.releaseLock();
357
+ }
358
+ };
359
+ const waitWithAbort = (ms, signal) => new Promise((resolve, reject) => {
360
+ const timeoutId = setTimeout(() => {
361
+ signal.removeEventListener("abort", handleAbort);
362
+ resolve();
363
+ }, ms);
364
+ function handleAbort() {
365
+ clearTimeout(timeoutId);
366
+ signal.removeEventListener("abort", handleAbort);
367
+ reject(new DOMException("Aborted", "AbortError"));
368
+ }
369
+ if (signal.aborted) {
370
+ handleAbort();
371
+ return;
372
+ }
373
+ signal.addEventListener("abort", handleAbort, { once: true });
374
+ });
375
+ const isSessionReadyForInput = (phase, waitingFor) => phase === "waiting-for-user" && waitingFor?.type === "user_message";
376
+ const shouldKeepLiveConnection = (session) => session?.phase !== "complete";
377
+ const withAcceptHeader = (defaultOptions) => {
378
+ const headers = new Headers(defaultOptions?.headers);
379
+ headers.set("accept", "application/x-ndjson");
380
+ return {
381
+ ...defaultOptions,
382
+ headers
383
+ };
384
+ };
385
+ function createPiSessionStore(deps, args) {
386
+ const detailStore = deps.createDetailStore(args.sessionId);
387
+ const snapshotStore = atom(args.initialData ?? null);
388
+ const overlayStore = atom(createOverlayState(args.sessionId));
389
+ const lastSnapshotVersion = { current: args.initialData ? `${args.initialData.id}:${String(args.initialData.updatedAt)}` : null };
390
+ let destroyed = false;
391
+ let mounted = false;
392
+ let activeAbortController = null;
393
+ let activeLoopPromise = null;
394
+ let detailUnsubscribe = null;
395
+ const logActive = (event, details) => {
396
+ deps.activeLogger?.(event, {
397
+ sessionId: args.sessionId,
398
+ ...details
399
+ });
400
+ };
401
+ const updateSnapshot = (session) => {
402
+ if (!session) return;
403
+ snapshotStore.set(session);
404
+ const nextVersion = `${session.id}:${String(session.updatedAt)}`;
405
+ if (nextVersion !== lastSnapshotVersion.current) {
406
+ logActive("snapshot:updated", {
407
+ version: nextVersion,
408
+ phase: session.phase,
409
+ status: session.status,
410
+ turn: session.turn
411
+ });
412
+ lastSnapshotVersion.current = nextVersion;
413
+ overlayStore.set(reduceOverlayState(overlayStore.get(), {
414
+ type: "snapshot-updated",
415
+ sessionId: session.id
416
+ }));
417
+ }
418
+ };
419
+ const refetch = () => {
420
+ logActive("detail:refetch");
421
+ detailStore.revalidate();
422
+ };
423
+ const stopActiveLoop = () => {
424
+ if (activeAbortController || activeLoopPromise) logActive("stream:stop");
425
+ activeAbortController?.abort();
426
+ activeAbortController = null;
427
+ activeLoopPromise = null;
428
+ };
429
+ const ensureActiveLoop = () => {
430
+ if (destroyed || !mounted || activeLoopPromise || deps.enableActiveStream === false) return;
431
+ const currentSession = snapshotStore.get();
432
+ if (!shouldKeepLiveConnection(currentSession)) {
433
+ logActive("stream:idle", {
434
+ reason: "session-complete",
435
+ phase: currentSession?.phase ?? null,
436
+ status: currentSession?.status ?? null
437
+ });
438
+ overlayStore.set(reduceOverlayState(overlayStore.get(), { type: "stream-idle" }));
439
+ return;
440
+ }
441
+ logActive("stream:start", {
442
+ phase: currentSession?.phase ?? null,
443
+ status: currentSession?.status ?? null,
444
+ turn: currentSession?.turn ?? null
445
+ });
446
+ const abortController = new AbortController();
447
+ activeAbortController = abortController;
448
+ activeLoopPromise = (async () => {
449
+ let reconnecting = false;
450
+ while (!abortController.signal.aborted && !destroyed) {
451
+ if (!shouldKeepLiveConnection(snapshotStore.get())) {
452
+ overlayStore.set(reduceOverlayState(overlayStore.get(), { type: "stream-idle" }));
453
+ return;
454
+ }
455
+ const activeUrl = deps.buildActiveUrl(args.sessionId);
456
+ logActive("stream:connecting", {
457
+ reconnecting,
458
+ url: activeUrl
459
+ });
460
+ overlayStore.set(reduceOverlayState(overlayStore.get(), {
461
+ type: "stream-connecting",
462
+ reconnecting
463
+ }));
464
+ try {
465
+ const response = await deps.fetcher(activeUrl, {
466
+ ...withAcceptHeader(deps.defaultOptions),
467
+ method: "GET",
468
+ cache: "no-store",
469
+ signal: abortController.signal
470
+ });
471
+ logActive("stream:response", {
472
+ ok: response.ok,
473
+ status: response.status,
474
+ statusText: response.statusText,
475
+ url: activeUrl
476
+ });
477
+ if (!response.ok) throw new Error(await readPiRouteError(response));
478
+ if (!response.body) throw new Error("The active session stream did not return a response body.");
479
+ logActive("stream:open", { url: activeUrl });
480
+ overlayStore.set(reduceOverlayState(overlayStore.get(), { type: "stream-open" }));
481
+ let sawInactive = false;
482
+ let sawSettled = false;
483
+ await consumeProtocolStream(response.body, (message) => {
484
+ logActive("stream:message", { message });
485
+ overlayStore.set(reduceOverlayState(overlayStore.get(), {
486
+ type: "stream-message",
487
+ message
488
+ }));
489
+ if (message.layer === "system" && message.type === "inactive") sawInactive = true;
490
+ if (message.layer === "system" && message.type === "settled") sawSettled = true;
491
+ }, abortController.signal);
492
+ if (abortController.signal.aborted || destroyed) return;
493
+ if (sawSettled || sawInactive) {
494
+ logActive("stream:settled", {
495
+ sawSettled,
496
+ sawInactive
497
+ });
498
+ overlayStore.set(reduceOverlayState(overlayStore.get(), { type: "stream-settled" }));
499
+ detailStore.revalidate();
500
+ }
501
+ if (sawInactive) {
502
+ logActive("stream:inactive");
503
+ overlayStore.set(reduceOverlayState(overlayStore.get(), { type: "stream-inactive" }));
504
+ return;
505
+ }
506
+ reconnecting = true;
507
+ logActive("stream:retry", { delayMs: 250 });
508
+ await waitWithAbort(250, abortController.signal);
509
+ } catch (error) {
510
+ if (abortController.signal.aborted || destroyed) return;
511
+ const message = error instanceof Error ? error.message : "Failed to stream the active session.";
512
+ logActive("stream:error", { message });
513
+ overlayStore.set(reduceOverlayState(overlayStore.get(), {
514
+ type: "stream-error",
515
+ message
516
+ }));
517
+ reconnecting = true;
518
+ logActive("stream:retry", { delayMs: 1e3 });
519
+ await waitWithAbort(1e3, abortController.signal).catch(() => void 0);
520
+ }
521
+ }
522
+ })().finally(() => {
523
+ logActive("stream:end");
524
+ if (activeAbortController === abortController) activeAbortController = null;
525
+ if (activeLoopPromise) activeLoopPromise = null;
526
+ });
527
+ };
528
+ const store = computed([
529
+ detailStore,
530
+ snapshotStore,
531
+ overlayStore
532
+ ], (detailValue, session, overlay) => {
533
+ const optimisticMessages = overlay.optimisticMessages.map((entry) => entry.message);
534
+ const messages = mergeMessages(session?.messages ?? [], optimisticMessages, overlay.streamedMessages, overlay.draftAssistant);
535
+ const statusText = activityLabel(overlay.activity) ?? connectionStatusText(overlay.connection);
536
+ const readyForInput = session !== null && (overlay.readyForInputOverride ?? (isSessionReadyForInput(session.phase, session.waitingFor) && !overlay.pendingTurn));
537
+ return {
538
+ loading: detailValue.loading,
539
+ session,
540
+ messages,
541
+ traceEvents: [...session?.trace ?? [], ...overlay.trace],
542
+ runningTools: overlay.runningTools,
543
+ connection: overlay.connection,
544
+ statusText,
545
+ readyForInput,
546
+ sending: overlay.sending,
547
+ error: detailValue.error?.message ?? overlay.error,
548
+ sendError: overlay.sendError
549
+ };
550
+ });
551
+ onMount(store, () => {
552
+ mounted = true;
553
+ logActive("store:mount");
554
+ updateSnapshot(detailStore.get().data);
555
+ detailUnsubscribe = detailStore.listen((value) => {
556
+ updateSnapshot(value.data);
557
+ if (destroyed) return;
558
+ if (!shouldKeepLiveConnection(snapshotStore.get())) {
559
+ logActive("stream:idle", { reason: "detail-store-update" });
560
+ stopActiveLoop();
561
+ overlayStore.set(reduceOverlayState(overlayStore.get(), { type: "stream-idle" }));
562
+ return;
563
+ }
564
+ ensureActiveLoop();
565
+ });
566
+ ensureActiveLoop();
567
+ return () => {
568
+ logActive("store:unmount");
569
+ mounted = false;
570
+ stopActiveLoop();
571
+ detailUnsubscribe?.();
572
+ detailUnsubscribe = null;
573
+ };
574
+ });
575
+ const sendMessage = (input) => {
576
+ const text = input.text.trim();
577
+ if (!text) return false;
578
+ const current = store.get();
579
+ if (current.sending || !current.readyForInput) return false;
580
+ const clientId = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
581
+ logActive("send:start", {
582
+ clientId,
583
+ text
584
+ });
585
+ overlayStore.set(reduceOverlayState(overlayStore.get(), {
586
+ type: "send-started",
587
+ clientId,
588
+ text
589
+ }));
590
+ ensureActiveLoop();
591
+ deps.sendMessage({
592
+ sessionId: args.sessionId,
593
+ text,
594
+ done: input.done,
595
+ steeringMode: input.steeringMode
596
+ }).then(() => {
597
+ logActive("send:acknowledged", { clientId });
598
+ overlayStore.set(reduceOverlayState(overlayStore.get(), { type: "send-acknowledged" }));
599
+ }).catch((error) => {
600
+ const message = error instanceof Error ? error.message : "Failed to send message.";
601
+ logActive("send:failed", {
602
+ clientId,
603
+ message
604
+ });
605
+ overlayStore.set(reduceOverlayState(overlayStore.get(), {
606
+ type: "send-failed",
607
+ clientId,
608
+ message
609
+ }));
610
+ });
611
+ return true;
612
+ };
613
+ const deactivate = () => {
614
+ logActive("store:dispose");
615
+ mounted = false;
616
+ stopActiveLoop();
617
+ detailUnsubscribe?.();
618
+ detailUnsubscribe = null;
619
+ };
620
+ const destroy = () => {
621
+ if (destroyed) return;
622
+ logActive("store:destroy");
623
+ destroyed = true;
624
+ deactivate();
625
+ };
626
+ return {
627
+ store,
628
+ sendMessage,
629
+ refetch,
630
+ deactivate,
631
+ destroy
632
+ };
633
+ }
634
+
635
+ //#endregion
636
+ export { createPiSessionStore };
637
+ //# sourceMappingURL=session-store.js.map