@pellux/goodvibes-tui 0.18.13 → 0.18.18

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 (71) hide show
  1. package/CHANGELOG.md +139 -0
  2. package/README.md +1 -1
  3. package/docs/foundation-artifacts/operator-contract.json +1 -1
  4. package/package.json +3 -2
  5. package/src/daemon/cli.ts +82 -6
  6. package/src/input/command-registry.ts +2 -0
  7. package/src/input/commands/control-room-runtime.ts +1 -1
  8. package/src/input/commands/health-runtime.ts +1 -1
  9. package/src/input/commands/local-setup-review.ts +1 -1
  10. package/src/input/commands/platform-access-runtime.ts +1 -1
  11. package/src/input/commands/qrcode-runtime.ts +20 -0
  12. package/src/input/commands/subscription-runtime.ts +1 -1
  13. package/src/input/commands.ts +2 -0
  14. package/src/input/handler-feed.ts +6 -0
  15. package/src/input/handler-modal-routes.ts +19 -2
  16. package/src/input/handler-modal-token-routes.ts +3 -0
  17. package/src/input/handler-picker-routes.ts +4 -2
  18. package/src/input/model-picker.ts +11 -0
  19. package/src/input/settings-modal.ts +31 -3
  20. package/src/panels/agent-logs-panel.ts +23 -24
  21. package/src/panels/base-panel.ts +6 -0
  22. package/src/panels/builtin/session.ts +66 -0
  23. package/src/panels/builtin/shared.ts +1 -1
  24. package/src/panels/provider-account-snapshot.ts +1 -1
  25. package/src/panels/provider-accounts-panel.ts +23 -27
  26. package/src/panels/qr-panel.ts +182 -0
  27. package/src/panels/scrollable-list-panel.ts +407 -0
  28. package/src/panels/services-panel.ts +1 -1
  29. package/src/panels/subscription-panel.ts +1 -1
  30. package/src/panels/types.ts +6 -0
  31. package/src/panels/worktree-panel.ts +20 -19
  32. package/src/renderer/buffer.ts +19 -0
  33. package/src/renderer/compositor.ts +19 -6
  34. package/src/renderer/panel-composite.ts +24 -3
  35. package/src/renderer/qr-renderer.ts +117 -0
  36. package/src/renderer/settings-modal-helpers.ts +122 -0
  37. package/src/renderer/settings-modal.ts +147 -111
  38. package/src/runtime/bootstrap-command-context.ts +1 -1
  39. package/src/runtime/bootstrap-command-parts.ts +31 -15
  40. package/src/runtime/bootstrap-core.ts +23 -1
  41. package/src/runtime/bootstrap.ts +6 -1
  42. package/src/runtime/diagnostics/panels/index.ts +5 -5
  43. package/src/runtime/services.ts +1 -1
  44. package/src/runtime/store/domains/domain-read-matrix.ts +0 -2
  45. package/src/runtime/ui-events.ts +1 -46
  46. package/src/runtime/ui-read-model-helpers.ts +1 -32
  47. package/src/runtime/ui-read-models-observability-maintenance.ts +1 -81
  48. package/src/runtime/ui-read-models-observability-options.ts +1 -5
  49. package/src/runtime/ui-read-models-observability-remote.ts +1 -73
  50. package/src/runtime/ui-read-models-observability-security.ts +1 -172
  51. package/src/runtime/ui-read-models-observability-system.ts +1 -217
  52. package/src/runtime/ui-read-models-observability.ts +1 -59
  53. package/src/runtime/ui-service-queries.ts +1 -114
  54. package/src/version.ts +1 -1
  55. package/src/config/service-registry.ts +0 -1
  56. package/src/config/subscription-providers.ts +0 -1
  57. package/src/runtime/diagnostics/actions.ts +0 -776
  58. package/src/runtime/diagnostics/index.ts +0 -99
  59. package/src/runtime/diagnostics/panels/agents.ts +0 -252
  60. package/src/runtime/diagnostics/panels/events.ts +0 -188
  61. package/src/runtime/diagnostics/panels/health.ts +0 -242
  62. package/src/runtime/diagnostics/panels/tasks.ts +0 -251
  63. package/src/runtime/diagnostics/panels/tool-calls.ts +0 -267
  64. package/src/runtime/diagnostics/provider.ts +0 -262
  65. package/src/runtime/store/domains/conversation.ts +0 -1
  66. package/src/runtime/store/domains/permissions.ts +0 -1
  67. package/src/runtime/store/helpers/reducers/conversation.ts +0 -1
  68. package/src/runtime/store/helpers/reducers/lifecycle.ts +0 -1
  69. package/src/runtime/store/helpers/reducers/shared.ts +0 -60
  70. package/src/runtime/store/helpers/reducers/sync.ts +0 -555
  71. package/src/runtime/store/helpers/reducers.ts +0 -30
@@ -1,555 +0,0 @@
1
- import type { CommunicationEvent } from '@pellux/goodvibes-sdk/platform/runtime/events/communication';
2
- import type { PluginEvent } from '@pellux/goodvibes-sdk/platform/runtime/events/plugins';
3
- import type { McpEvent } from '@pellux/goodvibes-sdk/platform/runtime/events/mcp';
4
- import type { TransportEvent } from '@pellux/goodvibes-sdk/platform/runtime/events/transport';
5
- import type { IntegrationDomainState, IntegrationRecord, IntegrationStatus } from '@pellux/goodvibes-sdk/platform/runtime/store/domains/integrations';
6
- import type { AutomationDomainState } from '@pellux/goodvibes-sdk/platform/runtime/store/domains/automation';
7
- import type { RoutesDomainState } from '@pellux/goodvibes-sdk/platform/runtime/store/domains/routes';
8
- import type { ControlPlaneDomainState, ControlPlaneClientRecord } from '@pellux/goodvibes-sdk/platform/runtime/store/domains/control-plane';
9
- import type { DeliveryDomainState } from '@pellux/goodvibes-sdk/platform/runtime/store/domains/deliveries';
10
- import type { WatcherDomainState, WatcherRecord } from '@pellux/goodvibes-sdk/platform/runtime/store/domains/watchers';
11
- import type { SurfaceDomainState, SurfaceRecord } from '@pellux/goodvibes-sdk/platform/runtime/store/domains/surfaces';
12
- import type { CommunicationDomainState, RuntimeCommunicationRecord } from '@pellux/goodvibes-sdk/platform/runtime/store/domains/communication';
13
- import type { PluginDomainState, RuntimePlugin, PluginLifecycleState } from '@pellux/goodvibes-sdk/platform/runtime/store/domains/plugins';
14
- import type { McpDomainState, McpServerRecord, McpServerLifecycleState } from '@pellux/goodvibes-sdk/platform/runtime/store/domains/mcp';
15
- import type { AcpDomainState, AcpTransportState } from '@pellux/goodvibes-sdk/platform/runtime/store/domains/acp';
16
- import type { DaemonDomainState, DaemonTransportState } from '@pellux/goodvibes-sdk/platform/runtime/store/domains/daemon';
17
- import type { AutomationJob } from '@pellux/goodvibes-sdk/platform/automation/jobs';
18
- import type { AutomationRun } from '@pellux/goodvibes-sdk/platform/automation/runs';
19
- import type { AutomationSourceRecord } from '@pellux/goodvibes-sdk/platform/automation/sources';
20
- import type { AutomationRouteBinding } from '@pellux/goodvibes-sdk/platform/automation/routes';
21
- import type { AutomationSurfaceKind } from '@pellux/goodvibes-sdk/platform/automation/types';
22
- import type { AutomationDeliveryAttempt } from '@pellux/goodvibes-sdk/platform/automation/delivery';
23
- import type { SessionDomainState } from '@pellux/goodvibes-sdk/platform/runtime/store/domains/session';
24
- import { now, uniq, updateDomainMetadata } from '@pellux/goodvibes-sdk/platform/runtime/store/helpers/reducers/shared';
25
-
26
- export function updateCommunicationState(
27
- domain: CommunicationDomainState,
28
- event: CommunicationEvent,
29
- ): CommunicationDomainState {
30
- const timestamp = now();
31
- const records = new Map(domain.records);
32
- const base: RuntimeCommunicationRecord | undefined =
33
- event.type === 'COMMUNICATION_SENT' || event.type === 'COMMUNICATION_BLOCKED'
34
- ? {
35
- id: event.messageId,
36
- fromId: event.fromId,
37
- toId: event.toId,
38
- scope: event.scope,
39
- kind: event.kind,
40
- content: 'content' in event ? event.content : '',
41
- timestamp,
42
- status: event.type === 'COMMUNICATION_BLOCKED' ? 'blocked' : 'sent',
43
- ...(event.fromRole !== undefined ? { fromRole: event.fromRole } : {}),
44
- ...(event.toRole !== undefined ? { toRole: event.toRole } : {}),
45
- ...(event.cohort !== undefined ? { cohort: event.cohort } : {}),
46
- ...(event.wrfcId !== undefined ? { wrfcId: event.wrfcId } : {}),
47
- ...(event.parentAgentId !== undefined ? { parentAgentId: event.parentAgentId } : {}),
48
- ...('reason' in event && event.reason !== undefined ? { reason: event.reason } : {}),
49
- }
50
- : undefined;
51
-
52
- if (base) {
53
- records.set(event.messageId, base);
54
- } else {
55
- const existing = records.get(event.messageId);
56
- if (!existing) return domain;
57
- records.set(event.messageId, {
58
- ...existing,
59
- status: event.type === 'COMMUNICATION_DELIVERED' ? 'delivered' : existing.status,
60
- });
61
- }
62
-
63
- const recentRecordIds = uniq([event.messageId, ...domain.recentRecordIds]).slice(0, 200);
64
- return {
65
- ...updateDomainMetadata(domain, event.type),
66
- records,
67
- recentRecordIds,
68
- totalSent: domain.totalSent + (event.type === 'COMMUNICATION_SENT' ? 1 : 0),
69
- totalDelivered: domain.totalDelivered + (event.type === 'COMMUNICATION_DELIVERED' ? 1 : 0),
70
- totalBlocked: domain.totalBlocked + (event.type === 'COMMUNICATION_BLOCKED' ? 1 : 0),
71
- };
72
- }
73
-
74
- function pluginStatusForEvent(event: PluginEvent): PluginLifecycleState {
75
- switch (event.type) {
76
- case 'PLUGIN_DISCOVERED':
77
- return 'discovered';
78
- case 'PLUGIN_LOADING':
79
- return 'loading';
80
- case 'PLUGIN_LOADED':
81
- return 'loaded';
82
- case 'PLUGIN_ACTIVE':
83
- return 'active';
84
- case 'PLUGIN_DEGRADED':
85
- return 'degraded';
86
- case 'PLUGIN_ERROR':
87
- return 'error';
88
- case 'PLUGIN_UNLOADING':
89
- return 'unloading';
90
- case 'PLUGIN_DISABLED':
91
- return 'disabled';
92
- }
93
- }
94
-
95
- export function updatePluginState(domain: PluginDomainState, event: PluginEvent): PluginDomainState {
96
- const plugins = new Map(domain.plugins);
97
- const existing = plugins.get(event.pluginId);
98
- const timestamp = now();
99
- const plugin: RuntimePlugin =
100
- existing ??
101
- {
102
- name: event.pluginId,
103
- displayName: event.pluginId,
104
- version: 'version' in event ? event.version : 'unknown',
105
- description: 'path' in event ? event.path : '',
106
- status: pluginStatusForEvent(event),
107
- enabled: true,
108
- active: false,
109
- toolCount: 'capabilities' in event ? event.capabilities.length : 0,
110
- config: {},
111
- hookInvocations: 0,
112
- };
113
- const next: RuntimePlugin = {
114
- ...plugin,
115
- status: pluginStatusForEvent(event),
116
- version: 'version' in event ? event.version : plugin.version,
117
- description: 'path' in event ? event.path : plugin.description,
118
- active: event.type === 'PLUGIN_ACTIVE' ? true : event.type === 'PLUGIN_DISABLED' ? false : plugin.active,
119
- enabled: event.type === 'PLUGIN_DISABLED' ? false : plugin.enabled,
120
- toolCount: 'capabilities' in event ? event.capabilities.length : plugin.toolCount,
121
- error:
122
- event.type === 'PLUGIN_ERROR'
123
- ? event.error
124
- : event.type === 'PLUGIN_DEGRADED'
125
- ? event.reason
126
- : plugin.error,
127
- loadedAt: event.type === 'PLUGIN_LOADED' || event.type === 'PLUGIN_ACTIVE' ? timestamp : plugin.loadedAt,
128
- errorAt: event.type === 'PLUGIN_ERROR' || event.type === 'PLUGIN_DEGRADED' ? timestamp : plugin.errorAt,
129
- };
130
- plugins.set(event.pluginId, next);
131
- const activePluginNames = [...plugins.values()].filter((value) => value.active).map((value) => value.name);
132
- const erroredPluginNames = [...plugins.values()]
133
- .filter((value) => value.status === 'error' || value.status === 'degraded')
134
- .map((value) => value.name);
135
- return {
136
- ...updateDomainMetadata(domain, event.type),
137
- plugins,
138
- activePluginNames,
139
- erroredPluginNames,
140
- totalDiscovered: domain.totalDiscovered + (event.type === 'PLUGIN_DISCOVERED' ? 1 : 0),
141
- totalActive: activePluginNames.length,
142
- totalToolsContributed: [...plugins.values()].reduce((sum, value) => sum + (value.active ? value.toolCount : 0), 0),
143
- initialLoadComplete: domain.initialLoadComplete || event.type === 'PLUGIN_LOADED' || event.type === 'PLUGIN_ACTIVE',
144
- reloadInProgress: event.type === 'PLUGIN_LOADING' || event.type === 'PLUGIN_UNLOADING',
145
- };
146
- }
147
-
148
- function mcpStatusForEvent(event: McpEvent): McpServerLifecycleState {
149
- switch (event.type) {
150
- case 'MCP_CONFIGURED':
151
- return 'configured';
152
- case 'MCP_CONNECTING':
153
- return 'connecting';
154
- case 'MCP_CONNECTED':
155
- return 'connected';
156
- case 'MCP_DEGRADED':
157
- case 'MCP_SCHEMA_QUARANTINED':
158
- case 'MCP_SCHEMA_QUARANTINE_APPROVED':
159
- return 'degraded';
160
- case 'MCP_AUTH_REQUIRED':
161
- return 'auth_required';
162
- case 'MCP_RECONNECTING':
163
- return 'reconnecting';
164
- case 'MCP_DISCONNECTED':
165
- return 'disconnected';
166
- case 'MCP_POLICY_UPDATED':
167
- return 'configured';
168
- }
169
- }
170
-
171
- export function updateMcpState(domain: McpDomainState, event: McpEvent): McpDomainState {
172
- const servers = new Map(domain.servers);
173
- const existing = servers.get(event.serverId);
174
- const timestamp = now();
175
- const server: McpServerRecord =
176
- existing ??
177
- {
178
- name: event.serverId,
179
- displayName: event.serverId,
180
- status: mcpStatusForEvent(event),
181
- transport: event.type === 'MCP_CONFIGURED' && event.transport === 'http' ? 'http' : 'stdio',
182
- toolCount: 0,
183
- toolNames: [],
184
- callCount: 0,
185
- errorCount: 0,
186
- reconnectAttempts: 0,
187
- trustMode:
188
- event.type === 'MCP_POLICY_UPDATED'
189
- ? event.trustMode
190
- : event.type === 'MCP_CONFIGURED'
191
- ? event.trustMode ?? 'ask-on-risk'
192
- : 'ask-on-risk',
193
- role:
194
- event.type === 'MCP_POLICY_UPDATED'
195
- ? event.role
196
- : event.type === 'MCP_CONFIGURED'
197
- ? event.role ?? 'general'
198
- : 'general',
199
- allowedPaths:
200
- event.type === 'MCP_POLICY_UPDATED'
201
- ? [...event.allowedPaths]
202
- : event.type === 'MCP_CONFIGURED'
203
- ? [...(event.allowedPaths ?? [])]
204
- : [],
205
- allowedHosts:
206
- event.type === 'MCP_POLICY_UPDATED'
207
- ? [...event.allowedHosts]
208
- : event.type === 'MCP_CONFIGURED'
209
- ? [...(event.allowedHosts ?? [])]
210
- : [],
211
- schemaFreshness:
212
- event.type === 'MCP_SCHEMA_QUARANTINED'
213
- ? 'quarantined'
214
- : event.type === 'MCP_SCHEMA_QUARANTINE_APPROVED'
215
- ? 'stale'
216
- : 'unknown',
217
- quarantineReason: event.type === 'MCP_SCHEMA_QUARANTINED' ? event.reason : undefined,
218
- quarantineDetail: event.type === 'MCP_SCHEMA_QUARANTINED' ? event.detail : undefined,
219
- quarantineApprovedBy: event.type === 'MCP_SCHEMA_QUARANTINE_APPROVED' ? event.operatorId : undefined,
220
- };
221
- servers.set(event.serverId, {
222
- ...server,
223
- status:
224
- event.type === 'MCP_POLICY_UPDATED' ||
225
- event.type === 'MCP_SCHEMA_QUARANTINED' ||
226
- event.type === 'MCP_SCHEMA_QUARANTINE_APPROVED'
227
- ? server.status
228
- : mcpStatusForEvent(event),
229
- transport:
230
- event.type === 'MCP_CONFIGURED'
231
- ? event.transport === 'sse' || event.transport === 'http'
232
- ? event.transport
233
- : 'stdio'
234
- : server.transport,
235
- toolCount: event.type === 'MCP_CONNECTED' ? event.toolCount : server.toolCount,
236
- connectedAt: event.type === 'MCP_CONNECTED' ? timestamp : server.connectedAt,
237
- reconnectAttempts: event.type === 'MCP_RECONNECTING' ? event.attempt : server.reconnectAttempts,
238
- trustMode: event.type === 'MCP_POLICY_UPDATED'
239
- ? event.trustMode
240
- : event.type === 'MCP_CONFIGURED'
241
- ? event.trustMode ?? server.trustMode
242
- : server.trustMode,
243
- role: event.type === 'MCP_POLICY_UPDATED'
244
- ? event.role
245
- : event.type === 'MCP_CONFIGURED'
246
- ? event.role ?? server.role
247
- : server.role,
248
- allowedPaths: event.type === 'MCP_POLICY_UPDATED'
249
- ? [...event.allowedPaths]
250
- : event.type === 'MCP_CONFIGURED'
251
- ? [...(event.allowedPaths ?? server.allowedPaths)]
252
- : server.allowedPaths,
253
- allowedHosts: event.type === 'MCP_POLICY_UPDATED'
254
- ? [...event.allowedHosts]
255
- : event.type === 'MCP_CONFIGURED'
256
- ? [...(event.allowedHosts ?? server.allowedHosts)]
257
- : server.allowedHosts,
258
- schemaFreshness:
259
- event.type === 'MCP_SCHEMA_QUARANTINED'
260
- ? 'quarantined'
261
- : event.type === 'MCP_SCHEMA_QUARANTINE_APPROVED'
262
- ? 'stale'
263
- : event.type === 'MCP_CONNECTED'
264
- ? 'fresh'
265
- : server.schemaFreshness,
266
- quarantineReason:
267
- event.type === 'MCP_SCHEMA_QUARANTINED'
268
- ? event.reason
269
- : server.quarantineReason,
270
- quarantineDetail:
271
- event.type === 'MCP_SCHEMA_QUARANTINED'
272
- ? event.detail
273
- : server.quarantineDetail,
274
- quarantineApprovedBy:
275
- event.type === 'MCP_SCHEMA_QUARANTINE_APPROVED'
276
- ? event.operatorId
277
- : server.quarantineApprovedBy,
278
- lastError:
279
- event.type === 'MCP_DEGRADED'
280
- ? event.reason
281
- : event.type === 'MCP_DISCONNECTED'
282
- ? event.reason
283
- : event.type === 'MCP_SCHEMA_QUARANTINED'
284
- ? event.detail ?? String(event.reason)
285
- : server.lastError,
286
- });
287
- const connectedServerNames = [...servers.values()].filter((value) => value.status === 'connected').map((value) => value.name);
288
- return {
289
- ...updateDomainMetadata(domain, event.type),
290
- servers,
291
- connectedServerNames,
292
- availableToolCount: [...servers.values()].reduce((sum, value) => sum + (value.status === 'connected' ? value.toolCount : 0), 0),
293
- totalErrors:
294
- domain.totalErrors +
295
- (event.type === 'MCP_DEGRADED' || event.type === 'MCP_DISCONNECTED' || event.type === 'MCP_SCHEMA_QUARANTINED' ? 1 : 0),
296
- };
297
- }
298
-
299
- function transportStateForEvent(event: TransportEvent): AcpTransportState | DaemonTransportState {
300
- switch (event.type) {
301
- case 'TRANSPORT_INITIALIZING':
302
- return 'initializing';
303
- case 'TRANSPORT_AUTHENTICATING':
304
- return 'authenticating';
305
- case 'TRANSPORT_CONNECTED':
306
- return 'connected';
307
- case 'TRANSPORT_SYNCING':
308
- return 'syncing';
309
- case 'TRANSPORT_DEGRADED':
310
- return 'degraded';
311
- case 'TRANSPORT_RECONNECTING':
312
- return 'reconnecting';
313
- case 'TRANSPORT_DISCONNECTED':
314
- return 'disconnected';
315
- case 'TRANSPORT_TERMINAL_FAILURE':
316
- return 'terminal_failure';
317
- }
318
- }
319
-
320
- export function updateTransportState(
321
- acp: AcpDomainState,
322
- daemon: DaemonDomainState,
323
- event: TransportEvent,
324
- ): Pick<import('../../state.ts').RuntimeState, 'acp' | 'daemon'> {
325
- const nextTransportState = transportStateForEvent(event);
326
- const isAcp = event.transportId.startsWith('acp');
327
- const nextAcp = isAcp
328
- ? {
329
- ...updateDomainMetadata(acp, event.type),
330
- managerTransportState: nextTransportState as AcpTransportState,
331
- initialized: acp.initialized || event.type === 'TRANSPORT_INITIALIZING',
332
- }
333
- : acp;
334
- const nextDaemon = !isAcp
335
- ? {
336
- ...updateDomainMetadata(daemon, event.type),
337
- transportState: nextTransportState as DaemonTransportState,
338
- isRunning:
339
- event.type === 'TRANSPORT_CONNECTED'
340
- ? true
341
- : event.type === 'TRANSPORT_DISCONNECTED'
342
- ? false
343
- : daemon.isRunning,
344
- reconnectAttempts: event.type === 'TRANSPORT_RECONNECTING' ? event.attempt : daemon.reconnectAttempts,
345
- lastConnectedAt: event.type === 'TRANSPORT_CONNECTED' ? now() : daemon.lastConnectedAt,
346
- lastError:
347
- event.type === 'TRANSPORT_DEGRADED'
348
- ? event.reason
349
- : event.type === 'TRANSPORT_DISCONNECTED'
350
- ? event.reason
351
- : event.type === 'TRANSPORT_TERMINAL_FAILURE'
352
- ? event.error
353
- : daemon.lastError,
354
- }
355
- : daemon;
356
- return { acp: nextAcp, daemon: nextDaemon };
357
- }
358
-
359
- export function updateIntegrationDomainFromRecord(domain: IntegrationDomainState, record: IntegrationRecord, source: string): IntegrationDomainState {
360
- const integrations = new Map(domain.integrations);
361
- const previous = integrations.get(record.id);
362
- integrations.set(record.id, record);
363
- const problemStatuses: IntegrationStatus[] = ['degraded', 'error'];
364
- const healthyIds = [...integrations.values()].filter((value) => value.status === 'healthy').map((value) => value.id);
365
- const problemIds = [...integrations.values()].filter((value) => problemStatuses.includes(value.status)).map((value) => value.id);
366
- return {
367
- ...updateDomainMetadata(domain, source),
368
- integrations,
369
- healthyIds,
370
- problemIds,
371
- totalOperations: domain.totalOperations + ((record.successCount ?? 0) - (previous?.successCount ?? 0)),
372
- totalErrors: domain.totalErrors + ((record.errorCount ?? 0) - (previous?.errorCount ?? 0)),
373
- };
374
- }
375
-
376
- export function updateAutomationDomainFromSource(domain: AutomationDomainState, sourceRecord: AutomationSourceRecord, source: string): AutomationDomainState {
377
- const sources = new Map(domain.sources);
378
- sources.set(sourceRecord.id, sourceRecord);
379
- const sourceIds = [...sources.values()].sort((a, b) => a.label.localeCompare(b.label) || a.createdAt - b.createdAt).map((record) => record.id);
380
- return { ...updateDomainMetadata(domain, source), sources, sourceIds };
381
- }
382
-
383
- export function updateAutomationDomainFromJob(domain: AutomationDomainState, job: AutomationJob, source: string): AutomationDomainState {
384
- const jobs = new Map(domain.jobs);
385
- const sources = new Map(domain.sources);
386
- jobs.set(job.id, job);
387
- sources.set(job.source.id, job.source);
388
- const allRuns = [...domain.runs.values()];
389
- const totalDeadLettered = allRuns.reduce(
390
- (count, run) => count + (run.deliveryAttempts?.filter((attempt) => attempt.status === 'dead_lettered').length ?? 0),
391
- 0,
392
- );
393
- return {
394
- ...updateDomainMetadata(domain, source),
395
- jobs,
396
- jobIds: [...jobs.values()].sort((a, b) => a.name.localeCompare(b.name) || a.createdAt - b.createdAt).map((record) => record.id),
397
- sources,
398
- sourceIds: [...sources.values()].sort((a, b) => a.label.localeCompare(b.label) || a.createdAt - b.createdAt).map((record) => record.id),
399
- totalJobs: jobs.size,
400
- totalRuns: allRuns.length,
401
- totalSucceeded: allRuns.filter((run) => run.status === 'completed').length,
402
- totalFailed: allRuns.filter((run) => run.status === 'failed').length,
403
- totalCancelled: allRuns.filter((run) => run.status === 'cancelled').length,
404
- totalDeadLettered,
405
- };
406
- }
407
-
408
- export function updateAutomationDomainFromRun(domain: AutomationDomainState, run: AutomationRun, source: string): AutomationDomainState {
409
- const runs = new Map(domain.runs);
410
- const sources = new Map(domain.sources);
411
- runs.set(run.id, run);
412
- sources.set(run.triggeredBy.id, run.triggeredBy);
413
- const allRuns = [...runs.values()];
414
- const totalDeadLettered = allRuns.reduce(
415
- (count, record) => count + (record.deliveryAttempts?.filter((attempt) => attempt.status === 'dead_lettered').length ?? 0),
416
- 0,
417
- );
418
- return {
419
- ...updateDomainMetadata(domain, source),
420
- runs,
421
- runIds: allRuns.sort((a, b) => b.queuedAt - a.queuedAt || a.id.localeCompare(b.id)).map((record) => record.id),
422
- activeRunIds: allRuns.filter((record) => record.status === 'queued' || record.status === 'running').map((record) => record.id),
423
- failedRunIds: allRuns.filter((record) => record.status === 'failed').map((record) => record.id),
424
- sources,
425
- sourceIds: [...sources.values()].sort((a, b) => a.label.localeCompare(b.label) || a.createdAt - b.createdAt).map((record) => record.id),
426
- totalJobs: domain.jobs.size,
427
- totalRuns: allRuns.length,
428
- totalSucceeded: allRuns.filter((record) => record.status === 'completed').length,
429
- totalFailed: allRuns.filter((record) => record.status === 'failed').length,
430
- totalCancelled: allRuns.filter((record) => record.status === 'cancelled').length,
431
- totalDeadLettered,
432
- };
433
- }
434
-
435
- function buildBindingIdsBySurface(bindings: readonly AutomationRouteBinding[]): Readonly<Record<string, readonly string[]>> {
436
- const grouped: Record<string, string[]> = {
437
- slack: [],
438
- discord: [],
439
- web: [],
440
- ntfy: [],
441
- webhook: [],
442
- tui: [],
443
- service: [],
444
- };
445
- for (const binding of bindings) {
446
- grouped[binding.surfaceKind] ??= [];
447
- grouped[binding.surfaceKind]!.push(binding.id);
448
- }
449
- return grouped;
450
- }
451
-
452
- export function updateRoutesDomainFromBinding(domain: RoutesDomainState, binding: AutomationRouteBinding, source: string): RoutesDomainState {
453
- const bindings = new Map(domain.bindings);
454
- bindings.set(binding.id, binding);
455
- const records = [...bindings.values()].sort((a, b) => b.lastSeenAt - a.lastSeenAt || a.id.localeCompare(b.id));
456
- return {
457
- ...updateDomainMetadata(domain, source),
458
- bindings,
459
- bindingIds: records.map((record) => record.id),
460
- bindingIdsBySurface: buildBindingIdsBySurface(records),
461
- activeBindingIds: records.map((record) => record.id),
462
- recentBindingIds: records.slice(0, 20).map((record) => record.id),
463
- totalBindings: records.length,
464
- totalResolved: records.filter((record) => record.sessionId || record.jobId || record.runId).length,
465
- };
466
- }
467
-
468
- export function updateRouteFailureState(domain: RoutesDomainState, _surfaceKind: AutomationSurfaceKind, _externalId: string, source: string): RoutesDomainState {
469
- return { ...updateDomainMetadata(domain, source), totalFailures: domain.totalFailures + 1 };
470
- }
471
-
472
- export function updateControlPlaneDomainFromClient(domain: ControlPlaneDomainState, client: ControlPlaneClientRecord, source: string): ControlPlaneDomainState {
473
- const clients = new Map(domain.clients);
474
- const previous = clients.get(client.id);
475
- clients.set(client.id, client);
476
- const records = [...clients.values()].sort((a, b) => (b.lastSeenAt ?? 0) - (a.lastSeenAt ?? 0) || a.id.localeCompare(b.id));
477
- const active = records.filter((record) => record.connected);
478
- return {
479
- ...updateDomainMetadata(domain, source),
480
- clients,
481
- activeClients: new Map(active.map((record) => [record.id, record])),
482
- clientIds: records.map((record) => record.id),
483
- activeClientIds: active.map((record) => record.id),
484
- isRunning: domain.isRunning || active.length > 0,
485
- connectionState: active.length > 0 ? 'connected' : domain.isRunning ? 'disconnected' : domain.connectionState,
486
- totalConnections: domain.totalConnections + (client.connected && !previous?.connected ? 1 : 0),
487
- totalDisconnects: domain.totalDisconnects + (!client.connected && previous?.connected ? 1 : 0),
488
- };
489
- }
490
-
491
- export function patchControlPlaneDomain(domain: ControlPlaneDomainState, patch: Partial<ControlPlaneDomainState>, source: string): ControlPlaneDomainState {
492
- return {
493
- ...updateDomainMetadata(domain, source),
494
- ...patch,
495
- totalFailures: patch.connectionState === 'terminal_failure' ? domain.totalFailures + 1 : patch.totalFailures ?? domain.totalFailures,
496
- };
497
- }
498
-
499
- export function updateDeliveryDomainFromAttempt(domain: DeliveryDomainState, attempt: AutomationDeliveryAttempt, source: string): DeliveryDomainState {
500
- const deliveryAttempts = new Map(domain.deliveryAttempts);
501
- deliveryAttempts.set(attempt.id, attempt);
502
- const attempts = [...deliveryAttempts.values()].sort((a, b) => (b.startedAt ?? 0) - (a.startedAt ?? 0) || a.id.localeCompare(b.id));
503
- return {
504
- ...updateDomainMetadata(domain, source),
505
- deliveryAttempts,
506
- attemptIds: attempts.map((record) => record.id),
507
- pendingAttemptIds: attempts.filter((record) => record.status === 'pending' || record.status === 'sending').map((record) => record.id),
508
- failedAttemptIds: attempts.filter((record) => record.status === 'failed').map((record) => record.id),
509
- deadLetterIds: attempts.filter((record) => record.status === 'dead_lettered').map((record) => record.id),
510
- totalQueued: attempts.length,
511
- totalStarted: attempts.filter((record) => record.startedAt !== undefined || record.status !== 'pending').length,
512
- totalSucceeded: attempts.filter((record) => record.status === 'sent').length,
513
- totalFailed: attempts.filter((record) => record.status === 'failed').length,
514
- totalDeadLettered: attempts.filter((record) => record.status === 'dead_lettered').length,
515
- };
516
- }
517
-
518
- export function updateSurfaceDomainFromRecord(domain: SurfaceDomainState, record: SurfaceRecord, source: string): SurfaceDomainState {
519
- const surfaces = new Map(domain.surfaces);
520
- surfaces.set(record.id, record);
521
- const records = [...surfaces.values()].sort((a, b) => a.label.localeCompare(b.label) || a.configuredAt - b.configuredAt);
522
- return {
523
- ...updateDomainMetadata(domain, source),
524
- surfaces,
525
- surfaceIds: records.map((entry) => entry.id),
526
- enabledSurfaceIds: records.filter((entry) => entry.enabled).map((entry) => entry.id),
527
- problemSurfaceIds: records.filter((entry) => entry.state === 'degraded' || entry.state === 'error').map((entry) => entry.id),
528
- totalHealthy: records.filter((entry) => entry.state === 'healthy').length,
529
- totalDegraded: records.filter((entry) => entry.state === 'degraded' || entry.state === 'error').length,
530
- totalDisabled: records.filter((entry) => !entry.enabled || entry.state === 'disabled').length,
531
- };
532
- }
533
-
534
- export function updateWatcherDomainFromRecord(domain: WatcherDomainState, record: WatcherRecord, source: string): WatcherDomainState {
535
- const watchers = new Map(domain.watchers);
536
- watchers.set(record.id, record);
537
- const records = [...watchers.values()].sort((a, b) => a.label.localeCompare(b.label) || a.id.localeCompare(b.id));
538
- return {
539
- ...updateDomainMetadata(domain, source),
540
- watchers,
541
- watcherIds: records.map((entry) => entry.id),
542
- activeWatcherIds: records.filter((entry) => entry.state === 'running' || entry.state === 'starting' || entry.state === 'degraded').map((entry) => entry.id),
543
- failedWatcherIds: records.filter((entry) => entry.state === 'failed').map((entry) => entry.id),
544
- totalStarted: records.filter((entry) => entry.state === 'running' || entry.state === 'starting').length,
545
- totalStopped: records.filter((entry) => entry.state === 'stopped').length,
546
- totalFailed: records.filter((entry) => entry.state === 'failed').length,
547
- totalHeartbeats: records.filter((entry) => entry.lastHeartbeatAt !== undefined).length,
548
- totalDegraded: records.filter((entry) => entry.state === 'degraded' || entry.sourceStatus === 'degraded').length,
549
- totalLagged: records.filter((entry) => entry.sourceStatus === 'lagging' || entry.sourceStatus === 'stale').length,
550
- };
551
- }
552
-
553
- export function syncSessionStatePatch(domain: SessionDomainState, patch: Partial<SessionDomainState>, source: string): SessionDomainState {
554
- return { ...updateDomainMetadata(domain, source), ...patch };
555
- }
@@ -1,30 +0,0 @@
1
- export { updateDomainMetadata } from '@pellux/goodvibes-sdk/platform/runtime/store/helpers/reducers/shared';
2
- export { updateConversationState } from '@pellux/goodvibes-sdk/platform/runtime/store/helpers/reducers/conversation';
3
- export {
4
- updateSessionState,
5
- updatePermissionState,
6
- updateTaskState,
7
- updateAgentState,
8
- updateOrchestrationState,
9
- transitionTaskDomainRecord,
10
- updateTaskDomainFromRecord,
11
- transitionAgentDomainRecord,
12
- } from '@pellux/goodvibes-sdk/platform/runtime/store/helpers/reducers/lifecycle';
13
- export {
14
- updateCommunicationState,
15
- updatePluginState,
16
- updateMcpState,
17
- updateTransportState,
18
- updateIntegrationDomainFromRecord,
19
- updateAutomationDomainFromSource,
20
- updateAutomationDomainFromJob,
21
- updateAutomationDomainFromRun,
22
- updateRoutesDomainFromBinding,
23
- updateRouteFailureState,
24
- updateControlPlaneDomainFromClient,
25
- patchControlPlaneDomain,
26
- updateDeliveryDomainFromAttempt,
27
- updateSurfaceDomainFromRecord,
28
- updateWatcherDomainFromRecord,
29
- syncSessionStatePatch,
30
- } from '@pellux/goodvibes-sdk/platform/runtime/store/helpers/reducers/sync';