@tutti-os/agent-activity-core 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.
package/dist/index.js ADDED
@@ -0,0 +1,1083 @@
1
+ // src/merge.ts
2
+ function mergeAgentActivityMessages(currentMessages, incomingMessages) {
3
+ if (incomingMessages.length === 0) {
4
+ return [...currentMessages];
5
+ }
6
+ const byMessageId = /* @__PURE__ */ new Map();
7
+ for (const message of currentMessages) {
8
+ byMessageId.set(message.messageId, cloneAgentActivityMessage(message));
9
+ }
10
+ for (const incoming of incomingMessages) {
11
+ const existing = byMessageId.get(incoming.messageId);
12
+ if (!existing || shouldReplaceAgentActivityMessage(existing, incoming)) {
13
+ byMessageId.set(
14
+ incoming.messageId,
15
+ existing ? mergeAgentActivityMessage(existing, incoming) : cloneAgentActivityMessage(incoming)
16
+ );
17
+ }
18
+ }
19
+ return [...byMessageId.values()].sort(compareAgentActivityMessages);
20
+ }
21
+ function compareAgentActivityMessages(left, right) {
22
+ return left.version - right.version || (left.id ?? 0) - (right.id ?? 0) || left.messageId.localeCompare(right.messageId);
23
+ }
24
+ function latestAgentActivityMessageVersion(messages) {
25
+ return messages.reduce(
26
+ (latestVersion, message) => Math.max(latestVersion, message.version),
27
+ 0
28
+ );
29
+ }
30
+ function areAgentActivityMessageArraysEqual(left, right) {
31
+ if (left.length !== right.length) {
32
+ return false;
33
+ }
34
+ for (let index = 0; index < left.length; index += 1) {
35
+ if (!areAgentActivityMessagesEqual(left[index], right[index])) {
36
+ return false;
37
+ }
38
+ }
39
+ return true;
40
+ }
41
+ function areAgentActivityMessagesEqual(left, right) {
42
+ return left.workspaceId === right.workspaceId && left.agentSessionId === right.agentSessionId && left.messageId === right.messageId && Object.is(left.id, right.id) && left.version === right.version && Object.is(left.turnId, right.turnId) && left.role === right.role && left.kind === right.kind && Object.is(left.status, right.status) && Object.is(left.occurredAtUnixMs, right.occurredAtUnixMs) && Object.is(left.startedAtUnixMs, right.startedAtUnixMs) && Object.is(left.completedAtUnixMs, right.completedAtUnixMs) && areJsonLikeValuesEqual(left.payload, right.payload);
43
+ }
44
+ function cloneAgentActivityMessage(message) {
45
+ return {
46
+ ...message,
47
+ payload: { ...message.payload }
48
+ };
49
+ }
50
+ function mergeAgentActivityMessage(existing, incoming) {
51
+ return {
52
+ ...existing,
53
+ ...incoming,
54
+ payload: {
55
+ ...existing.payload,
56
+ ...incoming.payload
57
+ }
58
+ };
59
+ }
60
+ function shouldReplaceAgentActivityMessage(existing, incoming) {
61
+ if (incoming.version !== existing.version) {
62
+ return incoming.version > existing.version;
63
+ }
64
+ return (incoming.id ?? 0) >= (existing.id ?? 0);
65
+ }
66
+ function areJsonLikeValuesEqual(left, right) {
67
+ if (Object.is(left, right)) {
68
+ return true;
69
+ }
70
+ if (Array.isArray(left) || Array.isArray(right)) {
71
+ if (!Array.isArray(left) || !Array.isArray(right)) {
72
+ return false;
73
+ }
74
+ if (left.length !== right.length) {
75
+ return false;
76
+ }
77
+ for (let index = 0; index < left.length; index += 1) {
78
+ if (!areJsonLikeValuesEqual(left[index], right[index])) {
79
+ return false;
80
+ }
81
+ }
82
+ return true;
83
+ }
84
+ const leftRecord = recordValue(left);
85
+ const rightRecord = recordValue(right);
86
+ if (!leftRecord || !rightRecord) {
87
+ return false;
88
+ }
89
+ const keys = /* @__PURE__ */ new Set([
90
+ ...Object.keys(leftRecord),
91
+ ...Object.keys(rightRecord)
92
+ ]);
93
+ for (const key of keys) {
94
+ if (!areJsonLikeValuesEqual(leftRecord[key], rightRecord[key])) {
95
+ return false;
96
+ }
97
+ }
98
+ return true;
99
+ }
100
+ function recordValue(value) {
101
+ return typeof value === "object" && value !== null ? value : null;
102
+ }
103
+
104
+ // src/controller.ts
105
+ function createAgentActivityController({
106
+ adapter,
107
+ autoRetainSessionEvents = true,
108
+ workspaceId
109
+ }) {
110
+ const listeners = /* @__PURE__ */ new Set();
111
+ const activeMessageSyncs = /* @__PURE__ */ new Map();
112
+ const activeComposerOptionsLoads = /* @__PURE__ */ new Map();
113
+ const composerOptionsLoadVersions = /* @__PURE__ */ new Map();
114
+ const autoRetainedStreamReleases = /* @__PURE__ */ new Map();
115
+ const retainedStreams = /* @__PURE__ */ new Map();
116
+ let snapshot = createEmptyAgentActivitySnapshot(workspaceId);
117
+ const emit = () => {
118
+ for (const listener of listeners) {
119
+ listener(snapshot);
120
+ }
121
+ };
122
+ const updateSnapshot = (updater) => {
123
+ const nextSnapshot = updater(snapshot);
124
+ if (nextSnapshot === snapshot) {
125
+ return snapshot;
126
+ }
127
+ snapshot = cloneAgentActivitySnapshot(nextSnapshot);
128
+ emit();
129
+ return snapshot;
130
+ };
131
+ return {
132
+ getSnapshot: () => snapshot,
133
+ subscribe(listener) {
134
+ listeners.add(listener);
135
+ listener(snapshot);
136
+ return () => {
137
+ listeners.delete(listener);
138
+ };
139
+ },
140
+ async load(signal) {
141
+ const response = await adapter.listSessions({ workspaceId, signal });
142
+ const nextSessions = response.sessions;
143
+ const nextPresences = response.presences ?? [];
144
+ const nextSnapshot = updateSnapshot((current) => {
145
+ if (areShallowObjectArraysEqual(current.sessions, nextSessions) && areShallowObjectArraysEqual(current.presences, nextPresences)) {
146
+ return current;
147
+ }
148
+ return {
149
+ ...current,
150
+ presences: nextPresences,
151
+ sessions: nextSessions
152
+ };
153
+ });
154
+ if (autoRetainSessionEvents) {
155
+ reconcileAutoRetainedSessionStreams(nextSnapshot.sessions, signal);
156
+ }
157
+ return nextSnapshot;
158
+ },
159
+ async loadComposerOptions(input) {
160
+ const provider = input.provider.trim();
161
+ if (!provider) {
162
+ throw new Error("Agent composer options provider is required.");
163
+ }
164
+ if (!input.force) {
165
+ const cached = snapshot.composerOptionsByProvider?.[provider];
166
+ if (cached) {
167
+ return cloneAgentActivityComposerOptions(cached);
168
+ }
169
+ }
170
+ const existingLoad = activeComposerOptionsLoads.get(provider);
171
+ if (existingLoad && !input.force) {
172
+ return existingLoad.then(cloneAgentActivityComposerOptions);
173
+ }
174
+ const loadVersion = (composerOptionsLoadVersions.get(provider) ?? 0) + 1;
175
+ composerOptionsLoadVersions.set(provider, loadVersion);
176
+ const load = adapter.loadComposerOptions({
177
+ workspaceId,
178
+ provider,
179
+ cwd: input.cwd,
180
+ settings: input.settings,
181
+ signal: input.signal
182
+ }).then((options) => {
183
+ const normalizedOptions = cloneAgentActivityComposerOptions({
184
+ ...options,
185
+ provider,
186
+ loadedAtUnixMs: options.loadedAtUnixMs || Date.now()
187
+ });
188
+ if (composerOptionsLoadVersions.get(provider) !== loadVersion) {
189
+ return cloneAgentActivityComposerOptions(normalizedOptions);
190
+ }
191
+ updateSnapshot((current) => {
192
+ const currentOptions = current.composerOptionsByProvider?.[provider];
193
+ if (currentOptions && areComposerOptionsEqual(currentOptions, normalizedOptions)) {
194
+ return current;
195
+ }
196
+ return {
197
+ ...current,
198
+ composerOptionsByProvider: {
199
+ ...current.composerOptionsByProvider,
200
+ [provider]: normalizedOptions
201
+ }
202
+ };
203
+ });
204
+ return cloneAgentActivityComposerOptions(normalizedOptions);
205
+ }).finally(() => {
206
+ if (activeComposerOptionsLoads.get(provider) === load) {
207
+ activeComposerOptionsLoads.delete(provider);
208
+ }
209
+ });
210
+ activeComposerOptionsLoads.set(provider, load);
211
+ return load.then(cloneAgentActivityComposerOptions);
212
+ },
213
+ async listSessionMessages({
214
+ agentSessionId,
215
+ afterVersion,
216
+ beforeVersion,
217
+ limit,
218
+ order,
219
+ signal
220
+ }) {
221
+ const response = await adapter.listSessionMessages({
222
+ workspaceId,
223
+ agentSessionId,
224
+ afterVersion,
225
+ beforeVersion,
226
+ limit,
227
+ order,
228
+ signal
229
+ });
230
+ updateSnapshot(
231
+ (current) => mergeSnapshotMessages(current, agentSessionId, response.messages)
232
+ );
233
+ return {
234
+ ...response,
235
+ messages: response.messages.map((message) => ({
236
+ ...message,
237
+ payload: { ...message.payload }
238
+ }))
239
+ };
240
+ },
241
+ retainSessionEvents: retainSessionEventsImpl,
242
+ removeSession(agentSessionId) {
243
+ updateSnapshot(
244
+ (current) => removeSnapshotSession(current, agentSessionId)
245
+ );
246
+ },
247
+ upsertSession(session) {
248
+ if (session.workspaceId && session.workspaceId !== snapshot.workspaceId) {
249
+ return;
250
+ }
251
+ updateSnapshot((current) => upsertSnapshotSession(current, session));
252
+ },
253
+ applyActivityUpdatedEvent(event) {
254
+ const result = applyActivityUpdatedEvent(snapshot, event);
255
+ if (result.snapshot !== snapshot) {
256
+ snapshot = result.snapshot;
257
+ emit();
258
+ }
259
+ return {
260
+ applied: result.applied,
261
+ messages: result.messages.map(cloneAgentActivityMessage),
262
+ session: result.session ? { ...result.session } : null,
263
+ statePatch: result.statePatch ? cloneAgentActivityStatePatch(result.statePatch) : null
264
+ };
265
+ },
266
+ applySessionEvent(event) {
267
+ updateSnapshot((current) => applySessionEvent(current, event));
268
+ }
269
+ };
270
+ function retainSessionEventsImpl({
271
+ agentSessionId,
272
+ afterVersion,
273
+ onRetainFailed,
274
+ onError
275
+ }) {
276
+ const normalizedAgentSessionId = agentSessionId.trim();
277
+ if (!normalizedAgentSessionId) {
278
+ return () => {
279
+ };
280
+ }
281
+ const existing = retainedStreams.get(normalizedAgentSessionId);
282
+ if (existing) {
283
+ existing.refCount += 1;
284
+ return createRetainedStreamRelease(normalizedAgentSessionId);
285
+ }
286
+ const abortController = new AbortController();
287
+ const stream = {
288
+ abortController,
289
+ refCount: 1,
290
+ unsubscribe: null
291
+ };
292
+ retainedStreams.set(normalizedAgentSessionId, stream);
293
+ const cachedMessages = snapshot.sessionMessagesById[normalizedAgentSessionId] ?? [];
294
+ const streamAfterVersion = afterVersion ?? latestAgentActivityMessageVersion(cachedMessages);
295
+ void adapter.subscribeSessionEvents({
296
+ workspaceId,
297
+ agentSessionId: normalizedAgentSessionId,
298
+ afterVersion: streamAfterVersion,
299
+ signal: abortController.signal,
300
+ onEvent(event) {
301
+ if (!abortController.signal.aborted) {
302
+ updateSnapshot((current) => applySessionEvent(current, event));
303
+ }
304
+ },
305
+ onError
306
+ }).then((unsubscribe) => {
307
+ const retained = retainedStreams.get(normalizedAgentSessionId);
308
+ if (!retained || retained.abortController.signal.aborted) {
309
+ unsubscribe();
310
+ return;
311
+ }
312
+ retained.unsubscribe = unsubscribe;
313
+ }).catch((error) => {
314
+ if (!abortController.signal.aborted) {
315
+ onError?.(error);
316
+ }
317
+ if (retainedStreams.get(normalizedAgentSessionId) === stream) {
318
+ retainedStreams.delete(normalizedAgentSessionId);
319
+ }
320
+ onRetainFailed?.();
321
+ abortController.abort();
322
+ stream.unsubscribe?.();
323
+ });
324
+ return createRetainedStreamRelease(normalizedAgentSessionId);
325
+ }
326
+ function reconcileAutoRetainedSessionStreams(sessions, signal) {
327
+ const activeSessionIds = new Set(
328
+ sessions.filter(shouldAutoRetainSessionEvents).map((session) => session.agentSessionId.trim()).filter(Boolean)
329
+ );
330
+ for (const [agentSessionId, release] of autoRetainedStreamReleases) {
331
+ if (!activeSessionIds.has(agentSessionId)) {
332
+ release();
333
+ autoRetainedStreamReleases.delete(agentSessionId);
334
+ }
335
+ }
336
+ for (const agentSessionId of activeSessionIds) {
337
+ if (!autoRetainedStreamReleases.has(agentSessionId)) {
338
+ autoRetainedStreamReleases.set(
339
+ agentSessionId,
340
+ retainSessionEventsImpl({
341
+ agentSessionId,
342
+ onRetainFailed() {
343
+ autoRetainedStreamReleases.delete(agentSessionId);
344
+ }
345
+ })
346
+ );
347
+ }
348
+ syncSessionMessages(agentSessionId, signal);
349
+ }
350
+ }
351
+ function syncSessionMessages(agentSessionId, signal) {
352
+ if (activeMessageSyncs.has(agentSessionId)) {
353
+ return;
354
+ }
355
+ const cachedMessages = snapshot.sessionMessagesById[agentSessionId] ?? [];
356
+ const afterVersion = latestAgentActivityMessageVersion(cachedMessages);
357
+ const sync = adapter.listSessionMessages({
358
+ workspaceId,
359
+ agentSessionId,
360
+ afterVersion,
361
+ signal
362
+ }).then((response) => {
363
+ if (signal?.aborted) {
364
+ return;
365
+ }
366
+ updateSnapshot(
367
+ (current) => mergeSnapshotMessages(current, agentSessionId, response.messages)
368
+ );
369
+ }).catch(() => {
370
+ }).finally(() => {
371
+ if (activeMessageSyncs.get(agentSessionId) === sync) {
372
+ activeMessageSyncs.delete(agentSessionId);
373
+ }
374
+ });
375
+ activeMessageSyncs.set(agentSessionId, sync);
376
+ }
377
+ function createRetainedStreamRelease(agentSessionId) {
378
+ let released = false;
379
+ return () => {
380
+ if (released) {
381
+ return;
382
+ }
383
+ released = true;
384
+ releaseRetainedStream(agentSessionId);
385
+ };
386
+ }
387
+ function releaseRetainedStream(agentSessionId) {
388
+ const stream = retainedStreams.get(agentSessionId);
389
+ if (!stream) {
390
+ return;
391
+ }
392
+ stream.refCount -= 1;
393
+ if (stream.refCount > 0) {
394
+ return;
395
+ }
396
+ retainedStreams.delete(agentSessionId);
397
+ stream.abortController.abort();
398
+ stream.unsubscribe?.();
399
+ }
400
+ }
401
+ function createEmptyAgentActivitySnapshot(workspaceId) {
402
+ return {
403
+ workspaceId,
404
+ sessions: [],
405
+ presences: [],
406
+ sessionMessagesById: {},
407
+ composerOptionsByProvider: {}
408
+ };
409
+ }
410
+ function cloneAgentActivitySnapshot(snapshot) {
411
+ return {
412
+ workspaceId: snapshot.workspaceId,
413
+ sessions: snapshot.sessions.map((session) => ({ ...session })),
414
+ presences: snapshot.presences.map((presence) => ({ ...presence })),
415
+ composerOptionsByProvider: Object.fromEntries(
416
+ Object.entries(snapshot.composerOptionsByProvider ?? {}).map(
417
+ ([provider, options]) => [
418
+ provider,
419
+ cloneAgentActivityComposerOptions(options)
420
+ ]
421
+ )
422
+ ),
423
+ sessionMessagesById: Object.fromEntries(
424
+ Object.entries(snapshot.sessionMessagesById).map(
425
+ ([agentSessionId, messages]) => [
426
+ agentSessionId,
427
+ messages.map((message) => ({
428
+ ...message,
429
+ payload: { ...message.payload }
430
+ }))
431
+ ]
432
+ )
433
+ )
434
+ };
435
+ }
436
+ function cloneAgentActivityComposerOptions(options) {
437
+ return {
438
+ provider: options.provider,
439
+ models: options.models.map((option) => ({ ...option })),
440
+ reasoningEfforts: options.reasoningEfforts.map((option) => ({
441
+ ...option
442
+ })),
443
+ permissionConfig: options.permissionConfig ? {
444
+ configurable: options.permissionConfig.configurable,
445
+ defaultValue: options.permissionConfig.defaultValue ?? null,
446
+ modes: options.permissionConfig.modes.map((mode) => ({ ...mode }))
447
+ } : options.permissionConfig ?? null,
448
+ runtimeContext: cloneJSONRecord(options.runtimeContext),
449
+ skills: options.skills.map((skill) => ({ ...skill })),
450
+ loadedAtUnixMs: options.loadedAtUnixMs
451
+ };
452
+ }
453
+ function areComposerOptionsEqual(left, right) {
454
+ const { loadedAtUnixMs: _leftLoadedAtUnixMs, ...leftComparable } = left;
455
+ const { loadedAtUnixMs: _rightLoadedAtUnixMs, ...rightComparable } = right;
456
+ return JSON.stringify(leftComparable) === JSON.stringify(rightComparable);
457
+ }
458
+ function cloneJSONRecord(value) {
459
+ if (value === void 0) {
460
+ return value;
461
+ }
462
+ return cloneJSONValue(value);
463
+ }
464
+ function cloneJSONValue(value) {
465
+ if (Array.isArray(value)) {
466
+ return value.map(cloneJSONValue);
467
+ }
468
+ if (value !== null && typeof value === "object") {
469
+ return Object.fromEntries(
470
+ Object.entries(value).map(([key, entry]) => [
471
+ key,
472
+ cloneJSONValue(entry)
473
+ ])
474
+ );
475
+ }
476
+ return value;
477
+ }
478
+ function areShallowObjectArraysEqual(left, right) {
479
+ if (left.length !== right.length) {
480
+ return false;
481
+ }
482
+ for (let index = 0; index < left.length; index += 1) {
483
+ if (!areShallowObjectsEqual(left[index], right[index])) {
484
+ return false;
485
+ }
486
+ }
487
+ return true;
488
+ }
489
+ function areShallowObjectsEqual(left, right) {
490
+ const leftRecord = left;
491
+ const rightRecord = right;
492
+ const keys = /* @__PURE__ */ new Set([
493
+ ...Object.keys(leftRecord),
494
+ ...Object.keys(rightRecord)
495
+ ]);
496
+ for (const key of keys) {
497
+ if (!Object.is(leftRecord[key], rightRecord[key])) {
498
+ return false;
499
+ }
500
+ }
501
+ return true;
502
+ }
503
+ function applyActivityUpdatedEvent(snapshot, event) {
504
+ if (event.workspaceId && event.workspaceId !== snapshot.workspaceId) {
505
+ return emptyActivityUpdatedApplyResult(snapshot);
506
+ }
507
+ const workspaceId = event.workspaceId || snapshot.workspaceId;
508
+ const agentSessionId = event.agentSessionId.trim();
509
+ if (!agentSessionId) {
510
+ return emptyActivityUpdatedApplyResult(snapshot);
511
+ }
512
+ if (event.eventType === "message_update") {
513
+ return applyActivityUpdatedMessages(snapshot, {
514
+ agentSessionId,
515
+ data: event.data,
516
+ workspaceId
517
+ });
518
+ }
519
+ if (event.eventType === "state_patch") {
520
+ return applyActivityUpdatedStatePatch(snapshot, {
521
+ agentSessionId,
522
+ data: event.data,
523
+ workspaceId
524
+ });
525
+ }
526
+ return emptyActivityUpdatedApplyResult(snapshot);
527
+ }
528
+ function applyActivityUpdatedMessages(snapshot, input) {
529
+ const inlineMessages = inlineMessagesFromActivityUpdateData(input.data);
530
+ if (inlineMessages.length === 0) {
531
+ return emptyActivityUpdatedApplyResult(snapshot);
532
+ }
533
+ const sessionMessages = inlineMessages.filter(
534
+ (message) => inlineMessageBelongsToSession(message, input.agentSessionId)
535
+ );
536
+ if (sessionMessages.length === 0) {
537
+ return emptyActivityUpdatedApplyResult(snapshot);
538
+ }
539
+ const messages = sessionMessages.map(
540
+ (message) => agentActivityMessageFromInlineMessage({
541
+ agentSessionId: input.agentSessionId,
542
+ message,
543
+ workspaceId: input.workspaceId
544
+ })
545
+ );
546
+ const nextSnapshot = mergeSnapshotMessages(
547
+ snapshot,
548
+ input.agentSessionId,
549
+ messages
550
+ );
551
+ if (nextSnapshot === snapshot) {
552
+ return {
553
+ applied: true,
554
+ messages: [],
555
+ session: null,
556
+ snapshot,
557
+ statePatch: null
558
+ };
559
+ }
560
+ return {
561
+ applied: true,
562
+ messages,
563
+ session: null,
564
+ snapshot: nextSnapshot,
565
+ statePatch: null
566
+ };
567
+ }
568
+ function applyActivityUpdatedStatePatch(snapshot, input) {
569
+ const statePatch = inlineStatePatchFromActivityUpdateData(input.data);
570
+ if (!statePatch || statePatch.agentSessionId !== input.agentSessionId) {
571
+ return emptyActivityUpdatedApplyResult(snapshot);
572
+ }
573
+ const existingSession = snapshot.sessions.find(
574
+ (session2) => session2.agentSessionId === input.agentSessionId
575
+ ) ?? null;
576
+ if (!existingSession || isStaleStatePatch(existingSession, statePatch)) {
577
+ return emptyActivityUpdatedApplyResult(snapshot);
578
+ }
579
+ const session = agentActivitySessionFromInlineStatePatch({
580
+ existingSession,
581
+ patch: statePatch,
582
+ workspaceId: input.workspaceId
583
+ });
584
+ return {
585
+ applied: true,
586
+ messages: [],
587
+ session,
588
+ snapshot: upsertSnapshotSession(snapshot, session),
589
+ statePatch
590
+ };
591
+ }
592
+ function emptyActivityUpdatedApplyResult(snapshot) {
593
+ return {
594
+ applied: false,
595
+ messages: [],
596
+ session: null,
597
+ snapshot,
598
+ statePatch: null
599
+ };
600
+ }
601
+ function applySessionEvent(snapshot, event) {
602
+ if (event.workspaceId && event.workspaceId !== snapshot.workspaceId) {
603
+ return snapshot;
604
+ }
605
+ const data = recordValue2(event.data) ?? {};
606
+ if (event.eventType === "message_update") {
607
+ const message = messageFromEvent(event, data);
608
+ return message ? mergeSnapshotMessages(snapshot, message.agentSessionId, [message]) : snapshot;
609
+ }
610
+ if (event.eventType === "session_update") {
611
+ const session = sessionFromEvent(snapshot.workspaceId, event, data);
612
+ return session ? upsertSnapshotSession(snapshot, session) : snapshot;
613
+ }
614
+ return snapshot;
615
+ }
616
+ function mergeSnapshotMessages(snapshot, agentSessionId, messages) {
617
+ const normalizedAgentSessionId = agentSessionId.trim();
618
+ if (!normalizedAgentSessionId || messages.length === 0) {
619
+ return snapshot;
620
+ }
621
+ const currentMessages = snapshot.sessionMessagesById[normalizedAgentSessionId] ?? [];
622
+ const mergedMessages = mergeAgentActivityMessages(currentMessages, messages);
623
+ if (areAgentActivityMessageArraysEqual(currentMessages, mergedMessages)) {
624
+ return snapshot;
625
+ }
626
+ return {
627
+ ...snapshot,
628
+ sessionMessagesById: {
629
+ ...snapshot.sessionMessagesById,
630
+ [normalizedAgentSessionId]: mergedMessages
631
+ }
632
+ };
633
+ }
634
+ function upsertSnapshotSession(snapshot, session) {
635
+ const index = snapshot.sessions.findIndex(
636
+ (item) => item.agentSessionId === session.agentSessionId
637
+ );
638
+ if (index < 0) {
639
+ return {
640
+ ...snapshot,
641
+ sessions: [...snapshot.sessions, session]
642
+ };
643
+ }
644
+ const sessions = [...snapshot.sessions];
645
+ sessions[index] = session;
646
+ return {
647
+ ...snapshot,
648
+ sessions
649
+ };
650
+ }
651
+ function removeSnapshotSession(snapshot, agentSessionId) {
652
+ const normalizedAgentSessionId = agentSessionId.trim();
653
+ if (!normalizedAgentSessionId) {
654
+ return snapshot;
655
+ }
656
+ const sessions = snapshot.sessions.filter(
657
+ (session) => session.agentSessionId !== normalizedAgentSessionId
658
+ );
659
+ if (sessions.length === snapshot.sessions.length && !snapshot.sessionMessagesById[normalizedAgentSessionId]) {
660
+ return snapshot;
661
+ }
662
+ const sessionMessagesById = { ...snapshot.sessionMessagesById };
663
+ delete sessionMessagesById[normalizedAgentSessionId];
664
+ return {
665
+ ...snapshot,
666
+ sessions,
667
+ sessionMessagesById
668
+ };
669
+ }
670
+ function shouldAutoRetainSessionEvents(session) {
671
+ if (!session.agentSessionId.trim()) {
672
+ return false;
673
+ }
674
+ switch (session.status.trim()) {
675
+ case "canceled":
676
+ case "completed":
677
+ case "failed":
678
+ return false;
679
+ default:
680
+ return true;
681
+ }
682
+ }
683
+ function messageFromEvent(event, data) {
684
+ const source = recordValue2(data.message) ?? data;
685
+ const agentSessionId = stringValue(source.agentSessionId) || event.agentSessionId;
686
+ const messageId = stringValue(source.messageId);
687
+ const role = stringValue(source.role);
688
+ const kind = stringValue(source.kind);
689
+ if (!agentSessionId || !messageId || !role || !kind) {
690
+ return null;
691
+ }
692
+ return {
693
+ workspaceId: stringValue(source.workspaceId) || event.workspaceId,
694
+ agentSessionId,
695
+ messageId,
696
+ id: numberValue(source.id),
697
+ version: numberValue(source.version) ?? 0,
698
+ turnId: nullableStringValue(source.turnId),
699
+ role,
700
+ kind,
701
+ status: nullableStringValue(source.status),
702
+ payload: recordValue2(source.payload) ?? {},
703
+ occurredAtUnixMs: numberValue(source.occurredAtUnixMs),
704
+ startedAtUnixMs: numberValue(source.startedAtUnixMs),
705
+ completedAtUnixMs: numberValue(source.completedAtUnixMs)
706
+ };
707
+ }
708
+ function sessionFromEvent(workspaceId, event, data) {
709
+ const source = recordValue2(data.session) ?? data;
710
+ const agentSessionId = stringValue(source.agentSessionId) || event.agentSessionId;
711
+ if (!agentSessionId) {
712
+ return null;
713
+ }
714
+ return {
715
+ workspaceId: stringValue(source.workspaceId) || workspaceId,
716
+ agentSessionId,
717
+ provider: stringValue(source.provider),
718
+ providerSessionId: nullableStringValue(source.providerSessionId),
719
+ model: nullableStringValue(source.model),
720
+ cwd: stringValue(source.cwd),
721
+ title: stringValue(source.title),
722
+ status: stringValue(source.status) || "unknown",
723
+ resumable: booleanValue(source.resumable),
724
+ currentPhase: nullableStringValue(source.currentPhase),
725
+ lastError: nullableStringValue(source.lastError),
726
+ messageVersion: numberValue(source.messageVersion),
727
+ lastEventUnixMs: numberValue(source.lastEventUnixMs),
728
+ startedAtUnixMs: numberValue(source.startedAtUnixMs),
729
+ endedAtUnixMs: numberValue(source.endedAtUnixMs),
730
+ createdAtUnixMs: numberValue(source.createdAtUnixMs),
731
+ updatedAtUnixMs: numberValue(source.updatedAtUnixMs)
732
+ };
733
+ }
734
+ function inlineMessagesFromActivityUpdateData(data) {
735
+ const source = recordValue2(data);
736
+ const messages = Array.isArray(source?.messages) ? source.messages : [];
737
+ return messages.flatMap((message) => {
738
+ const record = recordValue2(message);
739
+ return record ? [record] : [];
740
+ });
741
+ }
742
+ function inlineMessageBelongsToSession(message, agentSessionId) {
743
+ const messageAgentSessionId = stringValue(message.agentSessionId);
744
+ return !messageAgentSessionId || messageAgentSessionId === agentSessionId;
745
+ }
746
+ function agentActivityMessageFromInlineMessage(input) {
747
+ return {
748
+ workspaceId: stringValue(input.message.workspaceId) || input.workspaceId,
749
+ agentSessionId: stringValue(input.message.agentSessionId) || input.agentSessionId,
750
+ messageId: stringValue(input.message.messageId),
751
+ id: numberValue(input.message.id),
752
+ version: numberValue(input.message.version) ?? 0,
753
+ turnId: nullableStringValue(input.message.turnId),
754
+ role: stringValue(input.message.role),
755
+ kind: stringValue(input.message.kind),
756
+ status: nullableStringValue(input.message.status),
757
+ payload: recordValue2(input.message.payload) ?? {},
758
+ occurredAtUnixMs: numberValue(input.message.occurredAtUnixMs),
759
+ startedAtUnixMs: numberValue(input.message.startedAtUnixMs),
760
+ completedAtUnixMs: numberValue(input.message.completedAtUnixMs)
761
+ };
762
+ }
763
+ function inlineStatePatchFromActivityUpdateData(data) {
764
+ const source = recordValue2(data);
765
+ const agentSessionId = stringValue(source?.agentSessionId);
766
+ if (!source || !agentSessionId) {
767
+ return null;
768
+ }
769
+ const turn = recordValue2(source.turn);
770
+ return {
771
+ agentSessionId,
772
+ currentPhase: stringValue(source.currentPhase) || void 0,
773
+ cwd: stringValue(source.cwd) || void 0,
774
+ lastError: stringValue(source.lastError) || void 0,
775
+ lastEventUnixMs: numberValue(source.lastEventUnixMs),
776
+ lifecycleStatus: stringValue(source.lifecycleStatus) || void 0,
777
+ model: stringValue(source.model) || void 0,
778
+ occurredAtUnixMs: numberValue(source.occurredAtUnixMs),
779
+ provider: stringValue(source.provider) || void 0,
780
+ providerSessionId: stringValue(source.providerSessionId) || void 0,
781
+ startedAtUnixMs: numberValue(source.startedAtUnixMs),
782
+ endedAtUnixMs: numberValue(source.endedAtUnixMs),
783
+ title: stringValue(source.title) || void 0,
784
+ turn: turn ? {
785
+ completedAtUnixMs: numberValue(turn.completedAtUnixMs),
786
+ fileChanges: turn.fileChanges,
787
+ outcome: stringValue(turn.outcome) || void 0,
788
+ phase: stringValue(turn.phase) || void 0,
789
+ startedAtUnixMs: numberValue(turn.startedAtUnixMs),
790
+ turnId: stringValue(turn.turnId)
791
+ } : void 0,
792
+ workspaceId: stringValue(source.workspaceId) || void 0
793
+ };
794
+ }
795
+ function isStaleStatePatch(session, patch) {
796
+ const nextTime = patch.lastEventUnixMs ?? patch.occurredAtUnixMs;
797
+ const currentTime = session.lastEventUnixMs ?? session.updatedAtUnixMs;
798
+ return typeof nextTime === "number" && typeof currentTime === "number" && nextTime < currentTime;
799
+ }
800
+ function agentActivitySessionFromInlineStatePatch(input) {
801
+ return {
802
+ ...input.existingSession,
803
+ workspaceId: input.patch.workspaceId ?? input.workspaceId,
804
+ agentSessionId: input.patch.agentSessionId,
805
+ provider: input.patch.provider ?? input.existingSession.provider,
806
+ providerSessionId: input.patch.providerSessionId ?? input.existingSession.providerSessionId,
807
+ model: input.patch.model ?? input.existingSession.model,
808
+ cwd: input.patch.cwd ?? input.existingSession.cwd,
809
+ title: input.patch.title ?? input.existingSession.title,
810
+ status: input.patch.lifecycleStatus ?? input.existingSession.status,
811
+ currentPhase: input.patch.currentPhase ?? input.patch.turn?.phase ?? input.existingSession.currentPhase,
812
+ lastError: input.patch.lastError ?? input.existingSession.lastError,
813
+ lastEventUnixMs: input.patch.lastEventUnixMs ?? input.patch.occurredAtUnixMs ?? input.existingSession.lastEventUnixMs,
814
+ startedAtUnixMs: input.patch.startedAtUnixMs ?? input.existingSession.startedAtUnixMs,
815
+ endedAtUnixMs: input.patch.endedAtUnixMs ?? input.existingSession.endedAtUnixMs,
816
+ updatedAtUnixMs: input.patch.occurredAtUnixMs ?? input.patch.lastEventUnixMs ?? input.existingSession.updatedAtUnixMs
817
+ };
818
+ }
819
+ function cloneAgentActivityStatePatch(statePatch) {
820
+ return {
821
+ ...statePatch,
822
+ turn: statePatch.turn ? { ...statePatch.turn } : void 0
823
+ };
824
+ }
825
+ function recordValue2(value) {
826
+ return typeof value === "object" && value !== null ? value : null;
827
+ }
828
+ function stringValue(value) {
829
+ return typeof value === "string" ? value.trim() : "";
830
+ }
831
+ function nullableStringValue(value) {
832
+ return typeof value === "string" ? value : value === null ? null : void 0;
833
+ }
834
+ function numberValue(value) {
835
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
836
+ }
837
+ function booleanValue(value) {
838
+ return typeof value === "boolean" ? value : void 0;
839
+ }
840
+
841
+ // src/selectors.ts
842
+ var terminalMessageStatuses = /* @__PURE__ */ new Set([
843
+ "completed",
844
+ "canceled",
845
+ "failed",
846
+ "rejected",
847
+ "answered",
848
+ "resolved"
849
+ ]);
850
+ function resolveAgentActivityPromptImagesSupported(input) {
851
+ return promptImagesSupportedFromRuntimeContext(input.sessionRuntimeContext) ?? promptImagesSupportedFromRuntimeContext(
852
+ input.composerOptions?.runtimeContext
853
+ );
854
+ }
855
+ function selectNeedsAttentionCount(snapshot) {
856
+ return selectNeedsAttentionItems(snapshot).length;
857
+ }
858
+ function selectSessionDisplayStatuses(snapshot) {
859
+ const needsAttentionSessionIds = new Set(
860
+ selectNeedsAttentionItems(snapshot).map((item) => item.agentSessionId)
861
+ );
862
+ return new Map(
863
+ snapshot.sessions.map((session) => [
864
+ session.agentSessionId,
865
+ normalizeAgentActivityDisplayStatus(session.status, {
866
+ currentPhase: session.currentPhase,
867
+ needsAttention: needsAttentionSessionIds.has(session.agentSessionId)
868
+ })
869
+ ])
870
+ );
871
+ }
872
+ function normalizeAgentActivityDisplayStatus(status, options = {}) {
873
+ if (options.needsAttention) {
874
+ return "waiting";
875
+ }
876
+ const normalizedStatus = normalizeStatus(status);
877
+ const normalizedCurrentPhase = normalizeStatus(options.currentPhase);
878
+ switch (normalizedStatus) {
879
+ case "completed":
880
+ case "done":
881
+ case "success":
882
+ case "succeeded":
883
+ return "completed";
884
+ case "canceled":
885
+ case "cancelled":
886
+ return "canceled";
887
+ case "error":
888
+ case "failed":
889
+ return "failed";
890
+ default:
891
+ break;
892
+ }
893
+ switch (normalizedCurrentPhase) {
894
+ case "completed":
895
+ case "done":
896
+ case "success":
897
+ case "succeeded":
898
+ return "completed";
899
+ case "canceled":
900
+ case "cancelled":
901
+ return "canceled";
902
+ case "error":
903
+ case "failed":
904
+ return "failed";
905
+ case "awaiting_approval":
906
+ case "waiting":
907
+ case "waiting_approval":
908
+ case "waiting_input":
909
+ return "waiting";
910
+ case "running":
911
+ case "streaming":
912
+ case "working":
913
+ return "working";
914
+ default:
915
+ break;
916
+ }
917
+ switch (normalizedStatus) {
918
+ case "running":
919
+ case "streaming":
920
+ case "working":
921
+ return "working";
922
+ case "awaiting_approval":
923
+ case "waiting":
924
+ case "waiting_approval":
925
+ case "waiting_input":
926
+ return "waiting";
927
+ case "idle":
928
+ case "ready":
929
+ default:
930
+ return "idle";
931
+ }
932
+ }
933
+ function selectNeedsAttentionItems(snapshot) {
934
+ const sessionsById = new Map(
935
+ snapshot.sessions.map((session) => [session.agentSessionId, session])
936
+ );
937
+ const items = [];
938
+ for (const [agentSessionId, messages] of Object.entries(
939
+ snapshot.sessionMessagesById
940
+ )) {
941
+ const session = sessionsById.get(agentSessionId);
942
+ for (const message of messages) {
943
+ const kind = needsAttentionKindForMessage(message);
944
+ if (!kind) {
945
+ continue;
946
+ }
947
+ items.push(
948
+ needsAttentionItemFromMessage(snapshot, message, kind, session)
949
+ );
950
+ }
951
+ }
952
+ return items.sort(
953
+ (left, right) => right.occurredAtUnixMs - left.occurredAtUnixMs || left.id.localeCompare(right.id)
954
+ );
955
+ }
956
+ function needsAttentionKindForMessage(message) {
957
+ if (isTerminalMessageStatus(message.status)) {
958
+ return null;
959
+ }
960
+ const kind = normalizeKind(message.kind);
961
+ const payloadType = normalizeMetadataValue(message.payload.type);
962
+ const action = normalizeMetadataValue(message.payload.action);
963
+ const requestType = normalizeMetadataValue(message.payload.requestType);
964
+ const callType = normalizeMetadataValue(message.payload.callType);
965
+ const toolName = normalizeMetadataValue(message.payload.toolName);
966
+ const name = normalizeMetadataValue(message.payload.name);
967
+ const status = normalizeStatus(message.status);
968
+ const payloadStatus = normalizeMetadataValue(message.payload.status);
969
+ if (includesAny(
970
+ [
971
+ kind,
972
+ payloadType,
973
+ requestType,
974
+ callType,
975
+ toolName,
976
+ name,
977
+ status,
978
+ payloadStatus
979
+ ].join(" "),
980
+ ["permission", "approval"]
981
+ )) {
982
+ return "permission";
983
+ }
984
+ if (includesAny(
985
+ [
986
+ kind,
987
+ payloadType,
988
+ action,
989
+ callType,
990
+ toolName,
991
+ name,
992
+ status,
993
+ payloadStatus
994
+ ].join(" "),
995
+ ["ask_user", "ask-user", "askuserquestion", "question"]
996
+ )) {
997
+ return "question";
998
+ }
999
+ if (includesAny([kind, payloadType, action, toolName, name].join(" "), [
1000
+ "constraint"
1001
+ ])) {
1002
+ return "constraint";
1003
+ }
1004
+ if (isWaitingStatus(status, payloadStatus) && (message.role === "assistant" || message.role === "system")) {
1005
+ return "other";
1006
+ }
1007
+ return null;
1008
+ }
1009
+ function needsAttentionItemFromMessage(snapshot, message, kind, session) {
1010
+ return {
1011
+ id: `${message.agentSessionId}:${message.messageId}`,
1012
+ workspaceId: message.workspaceId || snapshot.workspaceId,
1013
+ agentSessionId: message.agentSessionId,
1014
+ provider: session?.provider ?? "",
1015
+ title: session?.title ?? "",
1016
+ cwd: session?.cwd ?? "",
1017
+ kind,
1018
+ summary: messageSummary(message),
1019
+ occurredAtUnixMs: message.occurredAtUnixMs ?? message.startedAtUnixMs ?? message.completedAtUnixMs ?? session?.updatedAtUnixMs ?? session?.lastEventUnixMs ?? 0
1020
+ };
1021
+ }
1022
+ function messageSummary(message) {
1023
+ return stringValue2(message.payload.summary) || stringValue2(message.payload.title) || stringValue2(message.payload.content) || stringValue2(message.payload.text) || message.kind;
1024
+ }
1025
+ function isTerminalMessageStatus(status) {
1026
+ return terminalMessageStatuses.has(normalizeStatus(status));
1027
+ }
1028
+ function normalizeStatus(status) {
1029
+ return status?.trim().toLowerCase() ?? "";
1030
+ }
1031
+ function isWaitingStatus(...values) {
1032
+ return values.some((value) => {
1033
+ const normalized = value.trim().toLowerCase();
1034
+ return normalized === "waiting" || normalized.startsWith("waiting_");
1035
+ });
1036
+ }
1037
+ function normalizeKind(kind) {
1038
+ return kind.trim().toLowerCase();
1039
+ }
1040
+ function normalizeMetadataValue(value) {
1041
+ return stringValue2(value).toLowerCase();
1042
+ }
1043
+ function includesAny(value, needles) {
1044
+ return needles.some((needle) => value.includes(needle));
1045
+ }
1046
+ function stringValue2(value) {
1047
+ return typeof value === "string" ? value.trim() : "";
1048
+ }
1049
+ function promptImagesSupportedFromRuntimeContext(runtimeContext) {
1050
+ const promptCapabilities = recordValue3(runtimeContext?.promptCapabilities);
1051
+ const value = promptCapabilities?.image;
1052
+ if (typeof value === "boolean") {
1053
+ return value;
1054
+ }
1055
+ if (typeof value === "string") {
1056
+ const normalized = value.trim().toLowerCase();
1057
+ if (normalized === "true") {
1058
+ return true;
1059
+ }
1060
+ if (normalized === "false") {
1061
+ return false;
1062
+ }
1063
+ }
1064
+ return null;
1065
+ }
1066
+ function recordValue3(value) {
1067
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
1068
+ }
1069
+ export {
1070
+ cloneAgentActivityMessage,
1071
+ cloneAgentActivitySnapshot,
1072
+ compareAgentActivityMessages,
1073
+ createAgentActivityController,
1074
+ createEmptyAgentActivitySnapshot,
1075
+ latestAgentActivityMessageVersion,
1076
+ mergeAgentActivityMessages,
1077
+ normalizeAgentActivityDisplayStatus,
1078
+ resolveAgentActivityPromptImagesSupported,
1079
+ selectNeedsAttentionCount,
1080
+ selectNeedsAttentionItems,
1081
+ selectSessionDisplayStatuses
1082
+ };
1083
+ //# sourceMappingURL=index.js.map