@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,99 +0,0 @@
1
- /**
2
- * Diagnostics system — barrel re-exports and factory.
3
- *
4
- * This module provides the public API for the runtime diagnostics system.
5
- * Import from here to access types, providers, and the factory function.
6
- *
7
- * Usage:
8
- * ```ts
9
- * import { createDiagnosticsProvider } from '../runtime/diagnostics/index.ts';
10
- *
11
- * const provider = createDiagnosticsProvider({
12
- * eventBus,
13
- * healthAggregator,
14
- * domains: [...],
15
- * });
16
- * ```
17
- */
18
-
19
- // ── Types ────────────────────────────────────────────────────────────────────
20
- export type {
21
- DiagnosticFilter,
22
- DiagnosticLevel,
23
- PanelConfig,
24
- ToolCallEntry,
25
- ToolCallPhase,
26
- ToolCallPermission,
27
- AgentEntry,
28
- AgentDiagnosticState,
29
- TaskEntry,
30
- EventEntry,
31
- DomainStateEntry,
32
- RuntimeStateSnapshot,
33
- DomainHealthSummary,
34
- HealthDashboardData,
35
- } from '@pellux/goodvibes-sdk/platform/runtime/diagnostics/types';
36
- export { DEFAULT_BUFFER_LIMIT, DEFAULT_PANEL_CONFIG, applyFilter, appendBounded } from '@pellux/goodvibes-sdk/platform/runtime/diagnostics/types';
37
-
38
- // ── Action system ────────────────────────────────────────────────────────────
39
- export type {
40
- DiagnosticActionType,
41
- DiagnosticActionPermission,
42
- DiagnosticActionPayload,
43
- DiagnosticAction,
44
- HighSeverityDiagnostic,
45
- ActionResult,
46
- NavigateToEntryCallback,
47
- PermissionChecker,
48
- DiagnosticActionDispatcherConfig,
49
- LoadReplayPayload,
50
- RunPolicySimulationPayload,
51
- JumpToTaskPayload,
52
- JumpToAgentPayload,
53
- JumpToToolCallPayload,
54
- RetryTaskPayload,
55
- CancelTaskPayload,
56
- CancelAgentPayload,
57
- } from '@pellux/goodvibes-sdk/platform/runtime/diagnostics/actions';
58
- export {
59
- DiagnosticActionDispatcher,
60
- buildLoadReplayAction,
61
- buildRunPolicySimulationAction,
62
- buildJumpToTaskAction,
63
- buildJumpToAgentAction,
64
- buildJumpToToolCallAction,
65
- buildRetryTaskAction,
66
- buildCancelTaskAction,
67
- buildCancelAgentAction,
68
- diagnosticFromTaskFailure,
69
- diagnosticFromAgentFailure,
70
- diagnosticFromToolContractViolation,
71
- diagnosticFromForensicsRun,
72
- } from '@pellux/goodvibes-sdk/platform/runtime/diagnostics/actions';
73
-
74
- // ── Panel data providers ─────────────────────────────────────────────────────
75
- export { ToolCallsPanel } from './panels/tool-calls.ts';
76
- export { AgentsPanel } from './panels/agents.ts';
77
- export { TasksPanel } from './panels/tasks.ts';
78
- export { EventsPanel } from './panels/events.ts';
79
- export { StateInspectorPanel } from '@pellux/goodvibes-sdk/platform/runtime/diagnostics/panels/state-inspector';
80
- export type { InspectableDomain } from '@pellux/goodvibes-sdk/platform/runtime/diagnostics/panels/state-inspector';
81
- export { HealthPanel } from './panels/health.ts';
82
-
83
- // ── Provider ─────────────────────────────────────────────────────────────────
84
- export { DiagnosticsProvider } from '@pellux/goodvibes-sdk/platform/runtime/diagnostics/provider';
85
- export type { DiagnosticsProviderConfig, DiagnosticPanelName } from '@pellux/goodvibes-sdk/platform/runtime/diagnostics/provider';
86
-
87
- // ── Factory ───────────────────────────────────────────────────────────────────
88
- import { DiagnosticsProvider, type DiagnosticsProviderConfig } from '@pellux/goodvibes-sdk/platform/runtime/diagnostics/provider';
89
-
90
- /**
91
- * Factory function that creates a fully wired DiagnosticsProvider.
92
- *
93
- * @param config - Configuration including the event bus, health aggregator,
94
- * optional domain adapters, and optional per-panel buffer config.
95
- * @returns A ready-to-use DiagnosticsProvider.
96
- */
97
- export function createDiagnosticsProvider(config: DiagnosticsProviderConfig): DiagnosticsProvider {
98
- return new DiagnosticsProvider(config);
99
- }
@@ -1,252 +0,0 @@
1
- /**
2
- * Agents diagnostic panel data provider.
3
- *
4
- * Subscribes to agent lifecycle events via the RuntimeEventBus and maintains
5
- * a bounded buffer of AgentEntry records. Provides filtered snapshots
6
- * for the agents/cohorts diagnostics panel.
7
- *
8
- * Tracks state, ownership, blockers, and completion status for each agent.
9
- */
10
- import type { RuntimeEventBus, EnvelopeListener } from '@pellux/goodvibes-sdk/platform/runtime/events/index';
11
- import type { AnyRuntimeEvent } from '@pellux/goodvibes-sdk/platform/runtime/events/domain-map';
12
- import type { RuntimeEventEnvelope } from '@pellux/goodvibes-sdk/platform/runtime/events/envelope';
13
- import {
14
- type AgentEntry,
15
- type AgentDiagnosticState,
16
- type DiagnosticFilter,
17
- type PanelConfig,
18
- DEFAULT_PANEL_CONFIG,
19
- applyFilter,
20
- appendBounded,
21
- } from '@pellux/goodvibes-sdk/platform/runtime/diagnostics/types';
22
-
23
- /** Internal mutable agent record for in-progress agents. */
24
- interface MutableAgentRecord {
25
- agentId: string;
26
- taskId?: string;
27
- task: string;
28
- state: AgentDiagnosticState;
29
- spawnedAt: number;
30
- completedAt?: number;
31
- durationMs?: number;
32
- error?: string;
33
- blockedOnCallId?: string;
34
- blockedOnTool?: string;
35
- traceId: string;
36
- sessionId: string;
37
- }
38
-
39
- /**
40
- * AgentsPanel — diagnostic data provider for agent lifecycle telemetry.
41
- *
42
- * Active agents are tracked in a live map; terminal agents are moved
43
- * to the history buffer for filtering and display.
44
- */
45
- export class AgentsPanel {
46
- private readonly _config: PanelConfig;
47
- private readonly _eventBus: RuntimeEventBus;
48
- /** Active agents keyed by agentId. */
49
- private readonly _active = new Map<string, MutableAgentRecord>();
50
- /** Completed agent history (oldest first). */
51
- private readonly _history: AgentEntry[] = [];
52
- /** Registered change notification callbacks. */
53
- private readonly _subscribers = new Set<() => void>();
54
- /** Unsubscribe function from the event bus. */
55
- private _unsub: (() => void) | null = null;
56
-
57
- constructor(eventBus: RuntimeEventBus, config: PanelConfig = DEFAULT_PANEL_CONFIG) {
58
- this._eventBus = eventBus;
59
- this._config = config;
60
- this._start();
61
- }
62
-
63
- private _start(): void {
64
- const handler: EnvelopeListener<AnyRuntimeEvent> = (
65
- envelope: RuntimeEventEnvelope<AnyRuntimeEvent['type'], AnyRuntimeEvent>
66
- ) => {
67
- this._handleEnvelope(envelope);
68
- };
69
- this._unsub = this._eventBus.onDomain('agents', handler as EnvelopeListener);
70
- }
71
-
72
- private _handleEnvelope(
73
- envelope: RuntimeEventEnvelope<AnyRuntimeEvent['type'], AnyRuntimeEvent>
74
- ): void {
75
- const p = envelope.payload;
76
- if (!('type' in p)) return;
77
- const type = (p as { type: string }).type;
78
- const traceId = envelope.traceId;
79
- const sessionId = envelope.sessionId;
80
-
81
- switch (type) {
82
- case 'AGENT_SPAWNING': {
83
- const evt = p as { type: 'AGENT_SPAWNING'; agentId: string; taskId?: string; task: string };
84
- this._active.set(evt.agentId, {
85
- agentId: evt.agentId,
86
- taskId: evt.taskId,
87
- task: evt.task,
88
- state: 'spawning',
89
- spawnedAt: envelope.ts,
90
- traceId,
91
- sessionId,
92
- });
93
- this._notify();
94
- break;
95
- }
96
- case 'AGENT_RUNNING': {
97
- const evt = p as { type: 'AGENT_RUNNING'; agentId: string };
98
- const record = this._active.get(evt.agentId);
99
- if (record) {
100
- record.state = 'running';
101
- record.blockedOnCallId = undefined;
102
- record.blockedOnTool = undefined;
103
- this._notify();
104
- }
105
- break;
106
- }
107
- case 'AGENT_AWAITING_MESSAGE': {
108
- const evt = p as { type: 'AGENT_AWAITING_MESSAGE'; agentId: string };
109
- const record = this._active.get(evt.agentId);
110
- if (record) {
111
- record.state = 'awaiting_message';
112
- this._notify();
113
- }
114
- break;
115
- }
116
- case 'AGENT_AWAITING_TOOL': {
117
- const evt = p as { type: 'AGENT_AWAITING_TOOL'; agentId: string; callId: string; tool: string };
118
- const record = this._active.get(evt.agentId);
119
- if (record) {
120
- record.state = 'awaiting_tool';
121
- record.blockedOnCallId = evt.callId;
122
- record.blockedOnTool = evt.tool;
123
- this._notify();
124
- }
125
- break;
126
- }
127
- case 'AGENT_FINALIZING': {
128
- const evt = p as { type: 'AGENT_FINALIZING'; agentId: string };
129
- const record = this._active.get(evt.agentId);
130
- if (record) {
131
- record.state = 'finalizing';
132
- this._notify();
133
- }
134
- break;
135
- }
136
- case 'AGENT_COMPLETED': {
137
- const evt = p as { type: 'AGENT_COMPLETED'; agentId: string; durationMs: number };
138
- const record = this._active.get(evt.agentId);
139
- if (record) {
140
- record.state = 'completed';
141
- record.completedAt = envelope.ts;
142
- record.durationMs = evt.durationMs;
143
- this._finalize(record);
144
- }
145
- break;
146
- }
147
- case 'AGENT_FAILED': {
148
- const evt = p as { type: 'AGENT_FAILED'; agentId: string; error: string; durationMs: number };
149
- const record = this._active.get(evt.agentId);
150
- if (record) {
151
- record.state = 'failed';
152
- record.completedAt = envelope.ts;
153
- record.durationMs = evt.durationMs;
154
- record.error = evt.error;
155
- this._finalize(record);
156
- }
157
- break;
158
- }
159
- case 'AGENT_CANCELLED': {
160
- const evt = p as { type: 'AGENT_CANCELLED'; agentId: string; reason?: string };
161
- const record = this._active.get(evt.agentId);
162
- if (record) {
163
- record.state = 'cancelled';
164
- record.completedAt = envelope.ts;
165
- record.error = evt.reason;
166
- this._finalize(record);
167
- }
168
- break;
169
- }
170
- default:
171
- break;
172
- }
173
- }
174
-
175
- private _finalize(record: MutableAgentRecord): void {
176
- this._active.delete(record.agentId);
177
- const entry: AgentEntry = {
178
- agentId: record.agentId,
179
- taskId: record.taskId,
180
- task: record.task,
181
- state: record.state,
182
- spawnedAt: record.spawnedAt,
183
- completedAt: record.completedAt,
184
- durationMs: record.durationMs,
185
- error: record.error,
186
- blockedOnCallId: record.blockedOnCallId,
187
- blockedOnTool: record.blockedOnTool,
188
- traceId: record.traceId,
189
- sessionId: record.sessionId,
190
- };
191
- appendBounded(this._history, entry, this._config.bufferLimit);
192
- this._notify();
193
- }
194
-
195
- /**
196
- * Return a filtered snapshot combining active agents and completed history.
197
- * Ordered most-recent first.
198
- *
199
- * @param filter - Optional filter to restrict entries.
200
- */
201
- public getSnapshot(filter?: DiagnosticFilter): AgentEntry[] {
202
- const activeEntries: AgentEntry[] = [];
203
- for (const record of this._active.values()) {
204
- activeEntries.push({
205
- agentId: record.agentId,
206
- taskId: record.taskId,
207
- task: record.task,
208
- state: record.state,
209
- spawnedAt: record.spawnedAt,
210
- completedAt: record.completedAt,
211
- durationMs: record.durationMs,
212
- error: record.error,
213
- blockedOnCallId: record.blockedOnCallId,
214
- blockedOnTool: record.blockedOnTool,
215
- traceId: record.traceId,
216
- sessionId: record.sessionId,
217
- });
218
- }
219
- const combined: AgentEntry[] = [...this._history, ...activeEntries];
220
- return applyFilter(combined, filter, (e) => e.spawnedAt);
221
- }
222
-
223
- /**
224
- * Register a callback invoked whenever the data changes.
225
- * @returns An unsubscribe function.
226
- */
227
- public subscribe(callback: () => void): () => void {
228
- this._subscribers.add(callback);
229
- return () => this._subscribers.delete(callback);
230
- }
231
-
232
- /**
233
- * Release all event bus subscriptions and clear internal state.
234
- */
235
- public dispose(): void {
236
- if (this._unsub) {
237
- this._unsub();
238
- this._unsub = null;
239
- }
240
- this._subscribers.clear();
241
- }
242
-
243
- private _notify(): void {
244
- for (const cb of this._subscribers) {
245
- try {
246
- cb();
247
- } catch {
248
- // Non-fatal: subscriber errors must not crash the provider
249
- }
250
- }
251
- }
252
- }
@@ -1,188 +0,0 @@
1
- /**
2
- * Events diagnostic panel data provider.
3
- *
4
- * Subscribes to ALL runtime event domains via the RuntimeEventBus and maintains
5
- * a bounded buffer of EventEntry records. Provides the typed event timeline
6
- * with full trace/session/turn/task/agent correlation context.
7
- *
8
- * This panel powers the "Events/Diagnostics" view.
9
- */
10
- import type { RuntimeEventBus, EnvelopeListener } from '@pellux/goodvibes-sdk/platform/runtime/events/index';
11
- import type { AnyRuntimeEvent, RuntimeEventDomain } from '@pellux/goodvibes-sdk/platform/runtime/events/domain-map';
12
- import type { RuntimeEventEnvelope } from '@pellux/goodvibes-sdk/platform/runtime/events/envelope';
13
- import {
14
- type EventEntry,
15
- type DiagnosticFilter,
16
- type PanelConfig,
17
- DEFAULT_PANEL_CONFIG,
18
- appendBounded,
19
- } from '@pellux/goodvibes-sdk/platform/runtime/diagnostics/types';
20
-
21
- /** All domains observed by the events panel. */
22
- const ALL_DOMAINS: readonly RuntimeEventDomain[] = [
23
- 'session',
24
- 'turn',
25
- 'tools',
26
- 'tasks',
27
- 'agents',
28
- 'permissions',
29
- 'plugins',
30
- 'mcp',
31
- 'transport',
32
- 'compaction',
33
- 'ui',
34
- ];
35
-
36
- /**
37
- * Produce a condensed human-readable summary of an event payload.
38
- * Extracts the most diagnostic fields without exposing full payloads.
39
- */
40
- function summarizePayload(type: string, payload: unknown): string {
41
- if (typeof payload !== 'object' || payload === null) return type;
42
- const p = payload as Record<string, unknown>;
43
- // Extract common correlation fields for a concise summary line
44
- const parts: string[] = [];
45
- if (typeof p['tool'] === 'string') parts.push(`tool=${p['tool']}`);
46
- if (typeof p['callId'] === 'string') parts.push(`call=${p['callId'].slice(0, 8)}`);
47
- if (typeof p['agentId'] === 'string') parts.push(`agent=${p['agentId'].slice(0, 8)}`);
48
- if (typeof p['taskId'] === 'string') parts.push(`task=${p['taskId'].slice(0, 8)}`);
49
- if (typeof p['error'] === 'string') parts.push(`err=${p['error'].slice(0, 40)}`);
50
- if (typeof p['durationMs'] === 'number') parts.push(`${p['durationMs']}ms`);
51
- if (typeof p['progress'] === 'number') parts.push(`${p['progress']}%`);
52
- return parts.length > 0 ? `${type} ${parts.join(' ')}` : type;
53
- }
54
-
55
- /**
56
- * EventsPanel — diagnostic data provider for the full event timeline.
57
- *
58
- * Captures every envelope emitted across all domains, building a chronological
59
- * log with full tracing context. The buffer is bounded to prevent unbounded
60
- * memory growth in long-running sessions.
61
- */
62
- export class EventsPanel {
63
- private readonly _config: PanelConfig;
64
- private readonly _eventBus: RuntimeEventBus;
65
- /** Monotonic sequence counter for ordering within a session. */
66
- private _seq = 0;
67
- /** Event history buffer (oldest first). */
68
- private readonly _buffer: EventEntry[] = [];
69
- /** Registered change notification callbacks. */
70
- private readonly _subscribers = new Set<() => void>();
71
- /** Per-domain unsubscribe functions. */
72
- private readonly _unsubs: Array<() => void> = [];
73
-
74
- constructor(eventBus: RuntimeEventBus, config: PanelConfig = DEFAULT_PANEL_CONFIG) {
75
- this._eventBus = eventBus;
76
- this._config = config;
77
- this._start();
78
- }
79
-
80
- private _start(): void {
81
- for (const domain of ALL_DOMAINS) {
82
- const d = domain;
83
- const handler: EnvelopeListener<AnyRuntimeEvent> = (
84
- envelope: RuntimeEventEnvelope<AnyRuntimeEvent['type'], AnyRuntimeEvent>
85
- ) => {
86
- this._handleEnvelope(d, envelope);
87
- };
88
- const unsub = this._eventBus.onDomain(d, handler as EnvelopeListener);
89
- this._unsubs.push(unsub);
90
- }
91
- }
92
-
93
- private _handleEnvelope(
94
- domain: RuntimeEventDomain,
95
- envelope: RuntimeEventEnvelope<AnyRuntimeEvent['type'], AnyRuntimeEvent>
96
- ): void {
97
- const entry: EventEntry = {
98
- seq: this._seq++,
99
- type: envelope.type,
100
- domain,
101
- ts: envelope.ts,
102
- traceId: envelope.traceId,
103
- sessionId: envelope.sessionId,
104
- turnId: envelope.turnId,
105
- agentId: envelope.agentId,
106
- taskId: envelope.taskId,
107
- source: envelope.source,
108
- summary: summarizePayload(envelope.type, envelope.payload),
109
- };
110
- appendBounded(this._buffer, entry, this._config.bufferLimit);
111
- this._notify();
112
- }
113
-
114
- /**
115
- * Return a filtered snapshot of event timeline entries.
116
- * Ordered most-recent first.
117
- *
118
- * @param filter - Optional filter. Supports: domains (as `domains` strings),
119
- * since/until time range, traceId/sessionId/turnId/taskId correlation,
120
- * and limit.
121
- */
122
- public getSnapshot(filter?: DiagnosticFilter): EventEntry[] {
123
- let result = [...this._buffer];
124
-
125
- if (filter) {
126
- if (filter.domains && filter.domains.length > 0) {
127
- const domainSet = new Set(filter.domains);
128
- result = result.filter((e) => domainSet.has(e.domain));
129
- }
130
- if (filter.traceId !== undefined) {
131
- result = result.filter((e) => e.traceId === filter.traceId);
132
- }
133
- if (filter.sessionId !== undefined) {
134
- result = result.filter((e) => e.sessionId === filter.sessionId);
135
- }
136
- if (filter.turnId !== undefined) {
137
- result = result.filter((e) => e.turnId === filter.turnId);
138
- }
139
- if (filter.taskId !== undefined) {
140
- result = result.filter((e) => e.taskId === filter.taskId);
141
- }
142
- if (filter.since !== undefined) {
143
- const since = filter.since;
144
- result = result.filter((e) => e.ts >= since);
145
- }
146
- if (filter.until !== undefined) {
147
- const until = filter.until;
148
- result = result.filter((e) => e.ts <= until);
149
- }
150
- }
151
-
152
- // Most recent first
153
- result.reverse();
154
-
155
- const limit = filter?.limit ?? this._config.bufferLimit;
156
- return result.slice(0, limit);
157
- }
158
-
159
- /**
160
- * Register a callback invoked whenever a new event is captured.
161
- * @returns An unsubscribe function.
162
- */
163
- public subscribe(callback: () => void): () => void {
164
- this._subscribers.add(callback);
165
- return () => this._subscribers.delete(callback);
166
- }
167
-
168
- /**
169
- * Release all event bus subscriptions and clear internal state.
170
- */
171
- public dispose(): void {
172
- for (const unsub of this._unsubs) {
173
- unsub();
174
- }
175
- this._unsubs.length = 0;
176
- this._subscribers.clear();
177
- }
178
-
179
- private _notify(): void {
180
- for (const cb of this._subscribers) {
181
- try {
182
- cb();
183
- } catch {
184
- // Non-fatal: subscriber errors must not crash the provider
185
- }
186
- }
187
- }
188
- }