pilotswarm-web 0.1.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 (57) hide show
  1. package/README.md +144 -0
  2. package/auth/authz/engine.js +139 -0
  3. package/auth/config.js +110 -0
  4. package/auth/index.js +153 -0
  5. package/auth/normalize/entra.js +22 -0
  6. package/auth/providers/entra.js +76 -0
  7. package/auth/providers/none.js +24 -0
  8. package/auth.js +10 -0
  9. package/bin/serve.js +53 -0
  10. package/config.js +20 -0
  11. package/dist/app.js +469 -0
  12. package/dist/assets/index-BSVg-lGb.css +1 -0
  13. package/dist/assets/index-BXD5YP7A.js +24 -0
  14. package/dist/assets/msal-CytV9RFv.js +7 -0
  15. package/dist/assets/pilotswarm-WX3NED6m.js +40 -0
  16. package/dist/assets/react-jg0oazEi.js +1 -0
  17. package/dist/index.html +16 -0
  18. package/node_modules/pilotswarm-ui-core/README.md +6 -0
  19. package/node_modules/pilotswarm-ui-core/package.json +32 -0
  20. package/node_modules/pilotswarm-ui-core/src/commands.js +72 -0
  21. package/node_modules/pilotswarm-ui-core/src/context-usage.js +212 -0
  22. package/node_modules/pilotswarm-ui-core/src/controller.js +3613 -0
  23. package/node_modules/pilotswarm-ui-core/src/formatting.js +872 -0
  24. package/node_modules/pilotswarm-ui-core/src/history.js +571 -0
  25. package/node_modules/pilotswarm-ui-core/src/index.js +13 -0
  26. package/node_modules/pilotswarm-ui-core/src/layout.js +196 -0
  27. package/node_modules/pilotswarm-ui-core/src/reducer.js +1027 -0
  28. package/node_modules/pilotswarm-ui-core/src/selectors.js +2786 -0
  29. package/node_modules/pilotswarm-ui-core/src/session-tree.js +109 -0
  30. package/node_modules/pilotswarm-ui-core/src/state.js +80 -0
  31. package/node_modules/pilotswarm-ui-core/src/store.js +23 -0
  32. package/node_modules/pilotswarm-ui-core/src/system-titles.js +24 -0
  33. package/node_modules/pilotswarm-ui-core/src/themes/catppuccin-mocha.js +56 -0
  34. package/node_modules/pilotswarm-ui-core/src/themes/cobalt2.js +56 -0
  35. package/node_modules/pilotswarm-ui-core/src/themes/dark-high-contrast.js +56 -0
  36. package/node_modules/pilotswarm-ui-core/src/themes/dracula.js +56 -0
  37. package/node_modules/pilotswarm-ui-core/src/themes/github-dark.js +56 -0
  38. package/node_modules/pilotswarm-ui-core/src/themes/gruvbox-dark.js +56 -0
  39. package/node_modules/pilotswarm-ui-core/src/themes/hacker-x-matrix.js +56 -0
  40. package/node_modules/pilotswarm-ui-core/src/themes/hacker-x-orion-prime.js +56 -0
  41. package/node_modules/pilotswarm-ui-core/src/themes/helpers.js +77 -0
  42. package/node_modules/pilotswarm-ui-core/src/themes/index.js +42 -0
  43. package/node_modules/pilotswarm-ui-core/src/themes/noctis-viola.js +56 -0
  44. package/node_modules/pilotswarm-ui-core/src/themes/noctis.js +56 -0
  45. package/node_modules/pilotswarm-ui-core/src/themes/nord.js +56 -0
  46. package/node_modules/pilotswarm-ui-core/src/themes/solarized-dark.js +56 -0
  47. package/node_modules/pilotswarm-ui-core/src/themes/tokyo-night.js +56 -0
  48. package/node_modules/pilotswarm-ui-react/README.md +5 -0
  49. package/node_modules/pilotswarm-ui-react/package.json +36 -0
  50. package/node_modules/pilotswarm-ui-react/src/components.js +1316 -0
  51. package/node_modules/pilotswarm-ui-react/src/index.js +4 -0
  52. package/node_modules/pilotswarm-ui-react/src/platform.js +15 -0
  53. package/node_modules/pilotswarm-ui-react/src/use-controller-state.js +38 -0
  54. package/node_modules/pilotswarm-ui-react/src/web-app.js +2661 -0
  55. package/package.json +64 -0
  56. package/runtime.js +146 -0
  57. package/server.js +311 -0
@@ -0,0 +1,571 @@
1
+ import { formatTimestamp, shortSessionId, stripTerminalMarkupTags, summarizeJson } from "./formatting.js";
2
+ import { formatCompactionActivityRuns } from "./context-usage.js";
3
+ import { canonicalSystemTitle } from "./system-titles.js";
4
+
5
+ export const DEFAULT_HISTORY_EVENT_LIMIT = 300;
6
+ export const HISTORY_EVENT_LIMIT_STEPS = [
7
+ DEFAULT_HISTORY_EVENT_LIMIT,
8
+ 1_000,
9
+ 3_000,
10
+ 10_000,
11
+ ];
12
+
13
+ function clampHistoryItems(items, maxItems) {
14
+ const list = Array.isArray(items) ? items.filter(Boolean) : [];
15
+ const safeMax = Math.max(DEFAULT_HISTORY_EVENT_LIMIT, Number(maxItems) || DEFAULT_HISTORY_EVENT_LIMIT);
16
+ return list.length > safeMax ? list.slice(-safeMax) : list;
17
+ }
18
+
19
+ function normalizeMessageText(text) {
20
+ return String(text || "").replace(/\r\n/g, "\n").trim();
21
+ }
22
+
23
+ export function parseAskedAndAnsweredExchange(text) {
24
+ const source = String(text || "").replace(/\r\n/g, "\n").trim();
25
+ const prefix = 'The user was asked: "';
26
+ const marker = '"\nThe user responded: "';
27
+
28
+ if (!source.startsWith(prefix) || !source.endsWith('"')) return null;
29
+ const markerIndex = source.indexOf(marker, prefix.length);
30
+ if (markerIndex === -1) return null;
31
+
32
+ const question = source.slice(prefix.length, markerIndex);
33
+ const answer = source.slice(markerIndex + marker.length, -1);
34
+ if (!question.trim() || !answer.trim()) return null;
35
+ return { question, answer };
36
+ }
37
+
38
+ function hasVisibleMessageText(text) {
39
+ return normalizeMessageText(stripTerminalMarkupTags(text)).length > 0;
40
+ }
41
+
42
+ function isInternalSystemLikeText(text) {
43
+ const normalized = normalizeMessageText(text);
44
+ if (!normalized) return false;
45
+
46
+ return /^\[SYSTEM:/i.test(normalized)
47
+ || /^\[CHILD_UPDATE\b/i.test(normalized)
48
+ || /^Buffered child updates arrived /i.test(normalized)
49
+ || /^Sub-agent spawned successfully\./i.test(normalized)
50
+ || /^Message sent to sub-agent /i.test(normalized)
51
+ || /^No sub-agents have been spawned yet\./i.test(normalized)
52
+ || /^Sub-agent status report \(/i.test(normalized)
53
+ || /^Active sessions \(/i.test(normalized)
54
+ || /^Sub-agents completed:/i.test(normalized)
55
+ || /^Sub-agent .* has been (completed gracefully|cancelled|deleted)\./i.test(normalized)
56
+ || /^(spawn_agent|message_agent|check_agents|wait_for_agents|complete_agent|cancel_agent|delete_agent) failed/i.test(normalized);
57
+ }
58
+
59
+ function deriveChatRole(event, fallbackRole, text) {
60
+ if (event?.eventType === "system.message") return "system";
61
+ if ((fallbackRole === "user" || fallbackRole === "assistant") && isInternalSystemLikeText(text)) return "system";
62
+ return fallbackRole;
63
+ }
64
+
65
+ function areMessagesEquivalent(left, right) {
66
+ if (!left || !right) return false;
67
+ if (left.role !== right.role) return false;
68
+
69
+ const leftText = normalizeMessageText(left.text);
70
+ const rightText = normalizeMessageText(right.text);
71
+ if (!leftText || !rightText || leftText !== rightText) return false;
72
+
73
+ const leftTime = Number(left.createdAt || 0);
74
+ const rightTime = Number(right.createdAt || 0);
75
+ if (left.optimistic || right.optimistic) return true;
76
+ if (!leftTime || !rightTime) return false;
77
+ return Math.abs(leftTime - rightTime) <= 5_000;
78
+ }
79
+
80
+ export function dedupeChatMessages(chat = []) {
81
+ const deduped = [];
82
+
83
+ for (const message of chat) {
84
+ if (!message) continue;
85
+ const previous = deduped[deduped.length - 1];
86
+ if (!areMessagesEquivalent(previous, message)) {
87
+ deduped.push(message);
88
+ continue;
89
+ }
90
+
91
+ if (previous?.optimistic && !message?.optimistic) {
92
+ deduped[deduped.length - 1] = message;
93
+ continue;
94
+ }
95
+ if (!previous?.optimistic && message?.optimistic) {
96
+ continue;
97
+ }
98
+
99
+ const previousTime = Number(previous?.createdAt || 0);
100
+ const currentTime = Number(message?.createdAt || 0);
101
+ deduped[deduped.length - 1] = currentTime >= previousTime ? message : previous;
102
+ }
103
+
104
+ return deduped;
105
+ }
106
+
107
+ function buildChatMessage(event, role) {
108
+ const text = String(messageTextFromEvent(event) || "").replace(/\r\n/g, "\n");
109
+ if (!hasVisibleMessageText(text)) return null;
110
+ return {
111
+ id: `${event.sessionId}:${event.seq}`,
112
+ role: deriveChatRole(event, role, text),
113
+ text,
114
+ time: formatTimestamp(event.createdAt),
115
+ createdAt: event.createdAt instanceof Date ? event.createdAt.getTime() : new Date(event.createdAt).getTime(),
116
+ };
117
+ }
118
+
119
+ function shouldRenderSystemMessageAsActivity(event) {
120
+ return event?.eventType === "system.message"
121
+ && isInternalSystemLikeText(messageTextFromEvent(event));
122
+ }
123
+
124
+ function reconcileOptimisticMessage(chat, incomingMessage) {
125
+ if (!Array.isArray(chat) || incomingMessage?.role !== "user") {
126
+ return [...(chat || [])];
127
+ }
128
+
129
+ const normalizedIncoming = normalizeMessageText(incomingMessage.text);
130
+ const parsedExchange = parseAskedAndAnsweredExchange(incomingMessage.text);
131
+ const normalizedAnsweredText = parsedExchange ? normalizeMessageText(parsedExchange.answer) : "";
132
+ let removed = false;
133
+
134
+ return chat.filter((message) => {
135
+ if (removed) return true;
136
+ if (!message?.optimistic || message.role !== incomingMessage.role) return true;
137
+ const normalizedMessageText = normalizeMessageText(message.text);
138
+ if (normalizedMessageText !== normalizedIncoming && normalizedMessageText !== normalizedAnsweredText) return true;
139
+ removed = true;
140
+ return false;
141
+ });
142
+ }
143
+
144
+ function messageTextFromEvent(event) {
145
+ const data = event?.data;
146
+ if (typeof data === "string") return data;
147
+ if (data && typeof data === "object") {
148
+ if (typeof data.content === "string") return data.content;
149
+ if (typeof data.text === "string") return data.text;
150
+ if (typeof data.message === "string") return data.message;
151
+ if (typeof data.question === "string") return data.question;
152
+ }
153
+ return summarizeJson(data);
154
+ }
155
+
156
+ function flattenRunsText(runs) {
157
+ return (runs || []).map((run) => run?.text || "").join("");
158
+ }
159
+
160
+ function summarizeActivityPreview(text, maxLen = 120) {
161
+ const compact = String(text || "")
162
+ .replace(/\s+/g, " ")
163
+ .trim();
164
+ if (!compact) return "";
165
+ return compact.length > maxLen
166
+ ? `${compact.slice(0, maxLen - 3)}...`
167
+ : compact;
168
+ }
169
+
170
+ function joinUniqueActivityDetail(parts = []) {
171
+ const seen = new Set();
172
+ const normalized = [];
173
+ for (const part of parts) {
174
+ const text = String(part || "").trim();
175
+ if (!text) continue;
176
+ const key = text.toLowerCase();
177
+ if (seen.has(key)) continue;
178
+ seen.add(key);
179
+ normalized.push(text);
180
+ }
181
+ return normalized.join(" | ");
182
+ }
183
+
184
+ function formatDehydrationActivityDetail(event, fallbackBody = "") {
185
+ return joinUniqueActivityDetail([
186
+ event?.data?.reason,
187
+ event?.data?.detail,
188
+ event?.data?.message,
189
+ event?.data?.error,
190
+ fallbackBody,
191
+ ]);
192
+ }
193
+
194
+ function formatLossyHandoffActivityDetail(event, fallbackBody = "") {
195
+ return joinUniqueActivityDetail([
196
+ event?.data?.message,
197
+ event?.data?.detail,
198
+ event?.data?.error,
199
+ fallbackBody,
200
+ ]);
201
+ }
202
+
203
+ function formatToolArgValue(value) {
204
+ if (value == null) return "null";
205
+ if (typeof value === "string") {
206
+ return JSON.stringify(value.length > 32 ? `${value.slice(0, 29)}...` : value);
207
+ }
208
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
209
+ if (Array.isArray(value)) return `[${value.length}]`;
210
+ if (typeof value === "object") return "{...}";
211
+ return String(value);
212
+ }
213
+
214
+ function formatToolArgsSummary(toolName, args) {
215
+ if (!args || typeof args !== "object") return "";
216
+
217
+ if (toolName === "wait") {
218
+ const seconds = args.seconds != null ? `${args.seconds}s` : "?";
219
+ const preserve = args.preserveWorkerAffinity === true ? " preserve=true" : "";
220
+ const reason = typeof args.reason === "string" && args.reason
221
+ ? ` reason=${JSON.stringify(args.reason)}`
222
+ : "";
223
+ return ` ${seconds}${preserve}${reason}`;
224
+ }
225
+
226
+ if (toolName === "cron") {
227
+ if (args.action === "cancel") return " cancel";
228
+ const seconds = args.seconds != null ? `${args.seconds}s` : "?";
229
+ const reason = typeof args.reason === "string" && args.reason
230
+ ? ` reason=${JSON.stringify(args.reason)}`
231
+ : "";
232
+ return ` ${seconds}${reason}`;
233
+ }
234
+
235
+ const entries = Object.entries(args)
236
+ .slice(0, 4)
237
+ .map(([key, value]) => `${key}=${formatToolArgValue(value)}`);
238
+ if (entries.length === 0) return "";
239
+ const suffix = Object.keys(args).length > entries.length ? ", ..." : "";
240
+ return ` ${entries.join(", ")}${suffix}`;
241
+ }
242
+
243
+ function buildActivityPrefix(time) {
244
+ return time ? [{ text: `[${time}] `, color: "white" }] : [];
245
+ }
246
+
247
+ function buildLabeledActivityRuns(time, label, labelColor, detail = "", detailColor = "white") {
248
+ return [
249
+ ...buildActivityPrefix(time),
250
+ { text: label, color: labelColor },
251
+ ...(detail ? [{ text: ` ${detail}`, color: detailColor }] : []),
252
+ ];
253
+ }
254
+
255
+ function formatEventSnippet(event, maxLen = 96) {
256
+ const body = summarizeActivityPreview(stripTerminalMarkupTags(messageTextFromEvent(event)), maxLen);
257
+ return body || "";
258
+ }
259
+
260
+ function formatToolActivityRuns(time, event, phase = "start") {
261
+ const toolName = event?.data?.toolName || event?.data?.name || "tool";
262
+ const args = event?.data?.arguments || event?.data?.args;
263
+ const durableSessionId = event?.data?.durableSessionId;
264
+ const summary = formatToolArgsSummary(toolName, args);
265
+
266
+ return [
267
+ ...buildActivityPrefix(time),
268
+ {
269
+ text: phase === "start" ? `▶ ${toolName}${summary}` : `✓ ${toolName}`,
270
+ color: phase === "start" ? "yellow" : "green",
271
+ },
272
+ ...(durableSessionId
273
+ ? [{ text: ` [${shortSessionId(durableSessionId)}]`, color: "gray" }]
274
+ : []),
275
+ ];
276
+ }
277
+
278
+ function formatActivity(event) {
279
+ const time = formatTimestamp(event.createdAt);
280
+ const body = formatEventSnippet(event);
281
+ let runs = null;
282
+
283
+ switch (event.eventType) {
284
+ case "assistant.usage":
285
+ case "session.info":
286
+ case "session.idle":
287
+ case "session.usage_info":
288
+ case "pending_messages.modified":
289
+ case "abort":
290
+ case "assistant.turn_end":
291
+ return null;
292
+
293
+ case "tool.execution_start":
294
+ runs = formatToolActivityRuns(time, event, "start");
295
+ break;
296
+
297
+ case "tool.execution_complete":
298
+ runs = formatToolActivityRuns(time, event, "complete");
299
+ break;
300
+
301
+ case "assistant.reasoning":
302
+ runs = buildLabeledActivityRuns(time, "[reasoning]", "gray", body || "…", "white");
303
+ break;
304
+
305
+ case "assistant.turn_start":
306
+ runs = buildLabeledActivityRuns(time, "[turn start]", "gray", body);
307
+ break;
308
+
309
+ case "session.turn_completed":
310
+ runs = buildLabeledActivityRuns(
311
+ time,
312
+ "[turn completed]",
313
+ "gray",
314
+ event?.data?.iteration != null ? `iter ${event.data.iteration}` : body,
315
+ );
316
+ break;
317
+
318
+ case "session.lossy_handoff":
319
+ runs = buildLabeledActivityRuns(
320
+ time,
321
+ "[lossy handoff]",
322
+ "yellow",
323
+ formatLossyHandoffActivityDetail(event, body) || "handoff to a new worker",
324
+ );
325
+ break;
326
+
327
+ case "session.dehydrated":
328
+ runs = buildLabeledActivityRuns(
329
+ time,
330
+ "[dehydrated]",
331
+ "cyan",
332
+ formatDehydrationActivityDetail(event, body),
333
+ );
334
+ break;
335
+
336
+ case "session.hydrated":
337
+ case "session.rehydrated":
338
+ runs = buildLabeledActivityRuns(time, "[rehydrated]", "green", body);
339
+ break;
340
+
341
+ case "session.wait_started": {
342
+ const seconds = event?.data?.seconds != null ? `${event.data.seconds}s` : "?";
343
+ const reason = typeof event?.data?.reason === "string" && event.data.reason
344
+ ? ` reason=${JSON.stringify(event.data.reason)}`
345
+ : "";
346
+ const preserve = event?.data?.preserveAffinity ? " preserve=true" : "";
347
+ runs = buildLabeledActivityRuns(time, "[wait]", "yellow", `${seconds}${preserve}${reason}`.trim());
348
+ break;
349
+ }
350
+
351
+ case "session.input_required_started":
352
+ runs = buildLabeledActivityRuns(time, "[input]", "yellow", body);
353
+ break;
354
+
355
+ case "session.agent_spawned":
356
+ runs = buildLabeledActivityRuns(
357
+ time,
358
+ "[spawn]",
359
+ "cyan",
360
+ event?.data?.agentId || shortSessionId(event?.data?.childSessionId),
361
+ "white",
362
+ );
363
+ break;
364
+
365
+ case "session.cron_started":
366
+ runs = buildLabeledActivityRuns(
367
+ time,
368
+ "[cron]",
369
+ "magenta",
370
+ `started${event?.data?.seconds != null ? ` ${event.data.seconds}s` : ""}${event?.data?.reason ? ` reason=${JSON.stringify(event.data.reason)}` : ""}`,
371
+ );
372
+ break;
373
+
374
+ case "session.cron_fired":
375
+ runs = buildLabeledActivityRuns(
376
+ time,
377
+ "[cron]",
378
+ "magenta",
379
+ `fired${event?.data?.reason ? ` reason=${JSON.stringify(event.data.reason)}` : ""}`,
380
+ );
381
+ break;
382
+
383
+ case "session.cron_cancelled":
384
+ runs = buildLabeledActivityRuns(
385
+ time,
386
+ "[cron]",
387
+ "magenta",
388
+ `cancelled${event?.data?.reason ? ` reason=${JSON.stringify(event.data.reason)}` : ""}`,
389
+ );
390
+ break;
391
+
392
+ case "session.command_received":
393
+ runs = buildLabeledActivityRuns(
394
+ time,
395
+ "[command]",
396
+ "magenta",
397
+ `/${event?.data?.cmd || "?"}${body ? ` ${body}` : ""}`,
398
+ );
399
+ break;
400
+
401
+ case "session.command_completed":
402
+ runs = buildLabeledActivityRuns(
403
+ time,
404
+ "[command]",
405
+ "magenta",
406
+ `/${event?.data?.cmd || "?"} ok${body ? ` ${body}` : ""}`,
407
+ );
408
+ break;
409
+
410
+ case "session.compaction_start":
411
+ case "session.compaction_complete":
412
+ runs = formatCompactionActivityRuns(time, event.eventType, event.data || {});
413
+ break;
414
+
415
+ case "session.error":
416
+ runs = buildLabeledActivityRuns(time, "[error]", "red", body || "session error", "white");
417
+ break;
418
+
419
+ case "system.message":
420
+ runs = buildLabeledActivityRuns(time, "[system]", "gray", body || "system message");
421
+ break;
422
+
423
+ default:
424
+ runs = [
425
+ ...buildActivityPrefix(time),
426
+ { text: `[${event.eventType}]`, color: "gray" },
427
+ ...(body ? [{ text: ` ${body}`, color: "white" }] : []),
428
+ ];
429
+ break;
430
+ }
431
+
432
+ return {
433
+ id: `${event.sessionId}:${event.seq}`,
434
+ eventType: event.eventType,
435
+ time,
436
+ text: flattenRunsText(runs),
437
+ line: runs,
438
+ };
439
+ }
440
+
441
+ export function getNextHistoryEventLimit(currentLimit = DEFAULT_HISTORY_EVENT_LIMIT) {
442
+ const safeCurrent = Math.max(DEFAULT_HISTORY_EVENT_LIMIT, Number(currentLimit) || DEFAULT_HISTORY_EVENT_LIMIT);
443
+ const nextLimit = HISTORY_EVENT_LIMIT_STEPS.find((limit) => limit > safeCurrent);
444
+ return nextLimit || Math.min(100_000, safeCurrent * 2);
445
+ }
446
+
447
+ export function buildHistoryModel(events = [], options = {}) {
448
+ const requestedLimit = Math.max(
449
+ DEFAULT_HISTORY_EVENT_LIMIT,
450
+ Number(options.requestedLimit ?? options.eventLimit ?? DEFAULT_HISTORY_EVENT_LIMIT) || DEFAULT_HISTORY_EVENT_LIMIT,
451
+ );
452
+ const chat = [];
453
+ const activity = [];
454
+ const storedEvents = [];
455
+
456
+ for (const event of events) {
457
+ storedEvents.push(event);
458
+ if (event.eventType === "user.message") {
459
+ const message = buildChatMessage(event, "user");
460
+ if (message) chat.push(message);
461
+ continue;
462
+ }
463
+ if (event.eventType === "assistant.message") {
464
+ const message = buildChatMessage(event, "assistant");
465
+ if (message) chat.push(message);
466
+ continue;
467
+ }
468
+ if (event.eventType === "system.message") {
469
+ if (shouldRenderSystemMessageAsActivity(event)) {
470
+ const activityItem = formatActivity(event);
471
+ if (activityItem) activity.push(activityItem);
472
+ } else {
473
+ const message = buildChatMessage(event, "system");
474
+ if (message) chat.push(message);
475
+ }
476
+ continue;
477
+ }
478
+ const activityItem = formatActivity(event);
479
+ if (activityItem) activity.push(activityItem);
480
+ }
481
+
482
+ return {
483
+ chat: dedupeChatMessages(chat),
484
+ activity,
485
+ events: storedEvents.slice(-requestedLimit),
486
+ loadedEventLimit: requestedLimit,
487
+ loadedEventCount: storedEvents.length,
488
+ hasOlderEvents: storedEvents.length >= requestedLimit,
489
+ };
490
+ }
491
+
492
+ export function appendEventToHistory(history, event) {
493
+ const existingEvents = Array.isArray(history?.events) ? history.events : [];
494
+ const loadedEventLimit = Math.max(
495
+ DEFAULT_HISTORY_EVENT_LIMIT,
496
+ Number(history?.loadedEventLimit ?? DEFAULT_HISTORY_EVENT_LIMIT) || DEFAULT_HISTORY_EVENT_LIMIT,
497
+ );
498
+ const nextEvents = existingEvents.length > 0 && existingEvents[existingEvents.length - 1]?.seq === event?.seq
499
+ ? existingEvents
500
+ : [...existingEvents, event].slice(-loadedEventLimit);
501
+ const next = {
502
+ chat: clampHistoryItems(history?.chat || [], loadedEventLimit),
503
+ activity: clampHistoryItems(history?.activity || [], loadedEventLimit),
504
+ events: nextEvents,
505
+ lastSeq: event.seq,
506
+ loadedEventLimit,
507
+ loadedEventCount: Math.max(Number(history?.loadedEventCount || 0), nextEvents.length),
508
+ hasOlderEvents: Boolean(history?.hasOlderEvents),
509
+ };
510
+
511
+ if (event.eventType === "user.message") {
512
+ const message = buildChatMessage(event, "user");
513
+ if (!message) return next;
514
+ next.chat = reconcileOptimisticMessage(next.chat, message);
515
+ next.chat.push(message);
516
+ next.chat = clampHistoryItems(dedupeChatMessages(next.chat), loadedEventLimit);
517
+ return next;
518
+ }
519
+ if (event.eventType === "assistant.message") {
520
+ const message = buildChatMessage(event, "assistant");
521
+ if (!message) return next;
522
+ next.chat.push(message);
523
+ next.chat = clampHistoryItems(dedupeChatMessages(next.chat), loadedEventLimit);
524
+ return next;
525
+ }
526
+ if (event.eventType === "system.message") {
527
+ if (shouldRenderSystemMessageAsActivity(event)) {
528
+ const activityItem = formatActivity(event);
529
+ if (activityItem) {
530
+ next.activity.push(activityItem);
531
+ next.activity = clampHistoryItems(next.activity, loadedEventLimit);
532
+ }
533
+ } else {
534
+ const message = buildChatMessage(event, "system");
535
+ if (!message) return next;
536
+ next.chat.push(message);
537
+ next.chat = clampHistoryItems(dedupeChatMessages(next.chat), loadedEventLimit);
538
+ }
539
+ return next;
540
+ }
541
+ const activityItem = formatActivity(event);
542
+ if (activityItem) {
543
+ next.activity.push(activityItem);
544
+ next.activity = clampHistoryItems(next.activity, loadedEventLimit);
545
+ }
546
+ return next;
547
+ }
548
+
549
+ export function createSplashCard(branding, session = null) {
550
+ const splash = typeof session?.splash === "string" && session.splash.trim()
551
+ ? session.splash
552
+ : branding?.splash;
553
+ if (!splash) return [];
554
+ const title = session?.isSystem
555
+ ? canonicalSystemTitle(session, branding?.title || "PilotSwarm")
556
+ : (session?.title || branding?.title || "PilotSwarm");
557
+ const hint = "{gray-fg}Start interacting with this session to replace the splash screen.{/gray-fg}";
558
+ return [{
559
+ id: `splash:${title}`,
560
+ role: "system",
561
+ text: `${splash}\n\n${hint}`,
562
+ time: "",
563
+ splash: true,
564
+ }];
565
+ }
566
+
567
+ export function buildSessionLabel(session) {
568
+ const title = session.title || shortSessionId(session.sessionId);
569
+ const shortId = shortSessionId(session.sessionId);
570
+ return title.includes(shortId) ? title : `${title} (${shortId})`;
571
+ }
@@ -0,0 +1,13 @@
1
+ export * from "./commands.js";
2
+ export * from "./context-usage.js";
3
+ export * from "./formatting.js";
4
+ export * from "./history.js";
5
+ export * from "./layout.js";
6
+ export * from "./session-tree.js";
7
+ export * from "./state.js";
8
+ export * from "./reducer.js";
9
+ export * from "./store.js";
10
+ export * from "./selectors.js";
11
+ export * from "./controller.js";
12
+ export { isThemeLight } from "./themes/helpers.js";
13
+ export * from "./themes/index.js";