aegis-bridge 2.2.2

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/LICENSE +21 -0
  2. package/README.md +244 -0
  3. package/dashboard/dist/assets/index-CijFoeRu.css +32 -0
  4. package/dashboard/dist/assets/index-QtT4j0ht.js +262 -0
  5. package/dashboard/dist/index.html +14 -0
  6. package/dist/auth.d.ts +76 -0
  7. package/dist/auth.js +219 -0
  8. package/dist/channels/index.d.ts +8 -0
  9. package/dist/channels/index.js +9 -0
  10. package/dist/channels/manager.d.ts +39 -0
  11. package/dist/channels/manager.js +101 -0
  12. package/dist/channels/telegram-style.d.ts +118 -0
  13. package/dist/channels/telegram-style.js +203 -0
  14. package/dist/channels/telegram.d.ts +76 -0
  15. package/dist/channels/telegram.js +1396 -0
  16. package/dist/channels/types.d.ts +77 -0
  17. package/dist/channels/types.js +9 -0
  18. package/dist/channels/webhook.d.ts +58 -0
  19. package/dist/channels/webhook.js +162 -0
  20. package/dist/cli.d.ts +8 -0
  21. package/dist/cli.js +223 -0
  22. package/dist/config.d.ts +60 -0
  23. package/dist/config.js +188 -0
  24. package/dist/dashboard/assets/index-CijFoeRu.css +32 -0
  25. package/dist/dashboard/assets/index-QtT4j0ht.js +262 -0
  26. package/dist/dashboard/index.html +14 -0
  27. package/dist/events.d.ts +86 -0
  28. package/dist/events.js +258 -0
  29. package/dist/hook-settings.d.ts +67 -0
  30. package/dist/hook-settings.js +138 -0
  31. package/dist/hook.d.ts +18 -0
  32. package/dist/hook.js +199 -0
  33. package/dist/hooks.d.ts +32 -0
  34. package/dist/hooks.js +279 -0
  35. package/dist/jsonl-watcher.d.ts +57 -0
  36. package/dist/jsonl-watcher.js +159 -0
  37. package/dist/mcp-server.d.ts +60 -0
  38. package/dist/mcp-server.js +788 -0
  39. package/dist/metrics.d.ts +104 -0
  40. package/dist/metrics.js +226 -0
  41. package/dist/monitor.d.ts +84 -0
  42. package/dist/monitor.js +553 -0
  43. package/dist/permission-guard.d.ts +51 -0
  44. package/dist/permission-guard.js +197 -0
  45. package/dist/pipeline.d.ts +84 -0
  46. package/dist/pipeline.js +218 -0
  47. package/dist/screenshot.d.ts +26 -0
  48. package/dist/screenshot.js +57 -0
  49. package/dist/server.d.ts +10 -0
  50. package/dist/server.js +1577 -0
  51. package/dist/session.d.ts +297 -0
  52. package/dist/session.js +1275 -0
  53. package/dist/sse-limiter.d.ts +47 -0
  54. package/dist/sse-limiter.js +62 -0
  55. package/dist/sse-writer.d.ts +31 -0
  56. package/dist/sse-writer.js +95 -0
  57. package/dist/ssrf.d.ts +57 -0
  58. package/dist/ssrf.js +169 -0
  59. package/dist/swarm-monitor.d.ts +114 -0
  60. package/dist/swarm-monitor.js +267 -0
  61. package/dist/terminal-parser.d.ts +16 -0
  62. package/dist/terminal-parser.js +343 -0
  63. package/dist/tmux.d.ts +161 -0
  64. package/dist/tmux.js +725 -0
  65. package/dist/transcript.d.ts +47 -0
  66. package/dist/transcript.js +244 -0
  67. package/dist/validation.d.ts +222 -0
  68. package/dist/validation.js +268 -0
  69. package/dist/ws-terminal.d.ts +32 -0
  70. package/dist/ws-terminal.js +297 -0
  71. package/package.json +71 -0
@@ -0,0 +1,104 @@
1
+ /**
2
+ * metrics.ts — Usage metrics and counters.
3
+ *
4
+ * Issue #40: Global and per-session metrics for monitoring.
5
+ * Counters are in-memory, persisted to disk on shutdown, loaded on startup.
6
+ */
7
+ export interface GlobalMetrics {
8
+ sessionsCreated: number;
9
+ sessionsCompleted: number;
10
+ sessionsFailed: number;
11
+ totalMessages: number;
12
+ totalToolCalls: number;
13
+ autoApprovals: number;
14
+ webhooksSent: number;
15
+ webhooksFailed: number;
16
+ screenshotsTaken: number;
17
+ pipelinesCreated: number;
18
+ batchesCreated: number;
19
+ promptsSent: number;
20
+ promptsDelivered: number;
21
+ promptsFailed: number;
22
+ }
23
+ export interface SessionMetrics {
24
+ durationSec: number;
25
+ messages: number;
26
+ toolCalls: number;
27
+ approvals: number;
28
+ autoApprovals: number;
29
+ statusChanges: string[];
30
+ }
31
+ /** Issue #87: Per-session latency samples (rolling window). */
32
+ export interface SessionLatency {
33
+ hook_latency_ms: number[];
34
+ state_change_detection_ms: number[];
35
+ permission_response_ms: number[];
36
+ channel_delivery_ms: number[];
37
+ }
38
+ /** Issue #87: Aggregated latency summary for a session. */
39
+ export interface SessionLatencySummary {
40
+ hook_latency_ms: {
41
+ min: number | null;
42
+ max: number | null;
43
+ avg: number | null;
44
+ count: number;
45
+ };
46
+ state_change_detection_ms: {
47
+ min: number | null;
48
+ max: number | null;
49
+ avg: number | null;
50
+ count: number;
51
+ };
52
+ permission_response_ms: {
53
+ min: number | null;
54
+ max: number | null;
55
+ avg: number | null;
56
+ count: number;
57
+ };
58
+ channel_delivery_ms: {
59
+ min: number | null;
60
+ max: number | null;
61
+ avg: number | null;
62
+ count: number;
63
+ };
64
+ }
65
+ export declare class MetricsCollector {
66
+ private metricsFile;
67
+ private global;
68
+ private perSession;
69
+ private latency;
70
+ private startTime;
71
+ /** Maximum samples per latency type per session (rolling window). */
72
+ static readonly MAX_LATENCY_SAMPLES = 100;
73
+ constructor(metricsFile: string);
74
+ load(): Promise<void>;
75
+ save(): Promise<void>;
76
+ sessionCreated(sessionId: string): void;
77
+ sessionCompleted(sessionId: string): void;
78
+ sessionFailed(sessionId: string): void;
79
+ messageReceived(sessionId: string): void;
80
+ toolCallReceived(sessionId: string): void;
81
+ approvalGranted(sessionId: string, auto?: boolean): void;
82
+ statusChanged(sessionId: string, status: string): void;
83
+ webhookSent(): void;
84
+ webhookFailed(): void;
85
+ screenshotTaken(): void;
86
+ pipelineCreated(): void;
87
+ batchCreated(): void;
88
+ promptSent(delivered: boolean): void;
89
+ private getOrCreateLatency;
90
+ private pushSample;
91
+ recordHookLatency(sessionId: string, latencyMs: number): void;
92
+ recordStateChangeDetection(sessionId: string, latencyMs: number): void;
93
+ recordPermissionResponse(sessionId: string, latencyMs: number): void;
94
+ recordChannelDelivery(sessionId: string, latencyMs: number): void;
95
+ private summarizeSamples;
96
+ getSessionLatency(sessionId: string): SessionLatencySummary | null;
97
+ /** Clean up latency data for a session (called on session kill). */
98
+ clearSessionLatency(sessionId: string): void;
99
+ /** #357: Clean up all per-session data (call on session destroy). */
100
+ cleanupSession(sessionId: string): void;
101
+ getGlobalMetrics(activeSessionCount: number): Record<string, unknown>;
102
+ getSessionMetrics(sessionId: string): SessionMetrics | null;
103
+ getTotalSessionsCreated(): number;
104
+ }
@@ -0,0 +1,226 @@
1
+ /**
2
+ * metrics.ts — Usage metrics and counters.
3
+ *
4
+ * Issue #40: Global and per-session metrics for monitoring.
5
+ * Counters are in-memory, persisted to disk on shutdown, loaded on startup.
6
+ */
7
+ import { readFile, writeFile, mkdir } from 'node:fs/promises';
8
+ import { existsSync } from 'node:fs';
9
+ import { dirname } from 'node:path';
10
+ import { metricsFileSchema } from './validation.js';
11
+ export class MetricsCollector {
12
+ metricsFile;
13
+ global = {
14
+ sessionsCreated: 0,
15
+ sessionsCompleted: 0,
16
+ sessionsFailed: 0,
17
+ totalMessages: 0,
18
+ totalToolCalls: 0,
19
+ autoApprovals: 0,
20
+ webhooksSent: 0,
21
+ webhooksFailed: 0,
22
+ screenshotsTaken: 0,
23
+ pipelinesCreated: 0,
24
+ batchesCreated: 0,
25
+ promptsSent: 0,
26
+ promptsDelivered: 0,
27
+ promptsFailed: 0,
28
+ };
29
+ perSession = new Map();
30
+ latency = new Map();
31
+ startTime = Date.now();
32
+ /** Maximum samples per latency type per session (rolling window). */
33
+ static MAX_LATENCY_SAMPLES = 100;
34
+ constructor(metricsFile) {
35
+ this.metricsFile = metricsFile;
36
+ }
37
+ async load() {
38
+ if (existsSync(this.metricsFile)) {
39
+ try {
40
+ const raw = await readFile(this.metricsFile, 'utf-8');
41
+ const parsed = metricsFileSchema.safeParse(JSON.parse(raw));
42
+ if (parsed.success && parsed.data.global) {
43
+ this.global = { ...this.global, ...parsed.data.global };
44
+ }
45
+ }
46
+ catch { /* ignore corrupt file */ }
47
+ }
48
+ }
49
+ async save() {
50
+ const dir = dirname(this.metricsFile);
51
+ if (!existsSync(dir)) {
52
+ await mkdir(dir, { recursive: true });
53
+ }
54
+ await writeFile(this.metricsFile, JSON.stringify({ global: this.global, savedAt: Date.now() }, null, 2));
55
+ }
56
+ sessionCreated(sessionId) {
57
+ this.global.sessionsCreated++;
58
+ this.perSession.set(sessionId, {
59
+ durationSec: 0, messages: 0, toolCalls: 0,
60
+ approvals: 0, autoApprovals: 0, statusChanges: [],
61
+ });
62
+ }
63
+ sessionCompleted(sessionId) {
64
+ this.global.sessionsCompleted++;
65
+ }
66
+ sessionFailed(sessionId) {
67
+ this.global.sessionsFailed++;
68
+ }
69
+ messageReceived(sessionId) {
70
+ this.global.totalMessages++;
71
+ const m = this.perSession.get(sessionId);
72
+ if (m)
73
+ m.messages++;
74
+ }
75
+ toolCallReceived(sessionId) {
76
+ this.global.totalToolCalls++;
77
+ const m = this.perSession.get(sessionId);
78
+ if (m)
79
+ m.toolCalls++;
80
+ }
81
+ approvalGranted(sessionId, auto = false) {
82
+ if (auto)
83
+ this.global.autoApprovals++;
84
+ const m = this.perSession.get(sessionId);
85
+ if (m) {
86
+ m.approvals++;
87
+ if (auto)
88
+ m.autoApprovals++;
89
+ }
90
+ }
91
+ statusChanged(sessionId, status) {
92
+ const m = this.perSession.get(sessionId);
93
+ if (m)
94
+ m.statusChanges.push(status);
95
+ }
96
+ webhookSent() { this.global.webhooksSent++; }
97
+ webhookFailed() { this.global.webhooksFailed++; }
98
+ screenshotTaken() { this.global.screenshotsTaken++; }
99
+ pipelineCreated() { this.global.pipelinesCreated++; }
100
+ batchCreated() { this.global.batchesCreated++; }
101
+ promptSent(delivered) {
102
+ this.global.promptsSent++;
103
+ if (delivered) {
104
+ this.global.promptsDelivered++;
105
+ }
106
+ else {
107
+ this.global.promptsFailed++;
108
+ }
109
+ }
110
+ // ── Issue #87: Latency metric recording ─────────────────────────────
111
+ getOrCreateLatency(sessionId) {
112
+ let lat = this.latency.get(sessionId);
113
+ if (!lat) {
114
+ lat = { hook_latency_ms: [], state_change_detection_ms: [], permission_response_ms: [], channel_delivery_ms: [] };
115
+ this.latency.set(sessionId, lat);
116
+ }
117
+ return lat;
118
+ }
119
+ pushSample(arr, value) {
120
+ arr.push(value);
121
+ if (arr.length > MetricsCollector.MAX_LATENCY_SAMPLES) {
122
+ arr.shift();
123
+ }
124
+ }
125
+ recordHookLatency(sessionId, latencyMs) {
126
+ this.pushSample(this.getOrCreateLatency(sessionId).hook_latency_ms, latencyMs);
127
+ }
128
+ recordStateChangeDetection(sessionId, latencyMs) {
129
+ this.pushSample(this.getOrCreateLatency(sessionId).state_change_detection_ms, latencyMs);
130
+ }
131
+ recordPermissionResponse(sessionId, latencyMs) {
132
+ this.pushSample(this.getOrCreateLatency(sessionId).permission_response_ms, latencyMs);
133
+ }
134
+ recordChannelDelivery(sessionId, latencyMs) {
135
+ this.pushSample(this.getOrCreateLatency(sessionId).channel_delivery_ms, latencyMs);
136
+ }
137
+ summarizeSamples(samples) {
138
+ if (samples.length === 0) {
139
+ return { min: null, max: null, avg: null, count: 0 };
140
+ }
141
+ let min = samples[0];
142
+ let max = samples[0];
143
+ let sum = 0;
144
+ for (const s of samples) {
145
+ if (s < min)
146
+ min = s;
147
+ if (s > max)
148
+ max = s;
149
+ sum += s;
150
+ }
151
+ return { min, max, avg: Math.round(sum / samples.length), count: samples.length };
152
+ }
153
+ getSessionLatency(sessionId) {
154
+ const lat = this.latency.get(sessionId);
155
+ if (!lat)
156
+ return null;
157
+ return {
158
+ hook_latency_ms: this.summarizeSamples(lat.hook_latency_ms),
159
+ state_change_detection_ms: this.summarizeSamples(lat.state_change_detection_ms),
160
+ permission_response_ms: this.summarizeSamples(lat.permission_response_ms),
161
+ channel_delivery_ms: this.summarizeSamples(lat.channel_delivery_ms),
162
+ };
163
+ }
164
+ /** Clean up latency data for a session (called on session kill). */
165
+ clearSessionLatency(sessionId) {
166
+ this.latency.delete(sessionId);
167
+ }
168
+ /** #357: Clean up all per-session data (call on session destroy). */
169
+ cleanupSession(sessionId) {
170
+ this.perSession.delete(sessionId);
171
+ this.latency.delete(sessionId);
172
+ }
173
+ getGlobalMetrics(activeSessionCount) {
174
+ const avgMessages = this.global.sessionsCreated > 0
175
+ ? Math.round(this.global.totalMessages / this.global.sessionsCreated) : 0;
176
+ // Issue #87: Aggregate latency across all sessions
177
+ const allHookLatency = [];
178
+ const allStateChange = [];
179
+ const allPermissionResponse = [];
180
+ const allChannelDelivery = [];
181
+ for (const lat of this.latency.values()) {
182
+ allHookLatency.push(...lat.hook_latency_ms);
183
+ allStateChange.push(...lat.state_change_detection_ms);
184
+ allPermissionResponse.push(...lat.permission_response_ms);
185
+ allChannelDelivery.push(...lat.channel_delivery_ms);
186
+ }
187
+ return {
188
+ uptime: Math.round((Date.now() - this.startTime) / 1000),
189
+ sessions: {
190
+ total_created: this.global.sessionsCreated,
191
+ currently_active: activeSessionCount,
192
+ completed: this.global.sessionsCompleted,
193
+ failed: this.global.sessionsFailed,
194
+ avg_duration_sec: 0,
195
+ avg_messages_per_session: avgMessages,
196
+ },
197
+ auto_approvals: this.global.autoApprovals,
198
+ webhooks_sent: this.global.webhooksSent,
199
+ webhooks_failed: this.global.webhooksFailed,
200
+ screenshots_taken: this.global.screenshotsTaken,
201
+ pipelines_created: this.global.pipelinesCreated,
202
+ batches_created: this.global.batchesCreated,
203
+ prompt_delivery: {
204
+ sent: this.global.promptsSent,
205
+ delivered: this.global.promptsDelivered,
206
+ failed: this.global.promptsFailed,
207
+ success_rate: this.global.promptsSent > 0
208
+ ? Math.round((this.global.promptsDelivered / this.global.promptsSent) * 100) : null,
209
+ },
210
+ // Issue #87: Aggregate latency metrics
211
+ latency: {
212
+ hook_latency_ms: this.summarizeSamples(allHookLatency),
213
+ state_change_detection_ms: this.summarizeSamples(allStateChange),
214
+ permission_response_ms: this.summarizeSamples(allPermissionResponse),
215
+ channel_delivery_ms: this.summarizeSamples(allChannelDelivery),
216
+ },
217
+ };
218
+ }
219
+ getSessionMetrics(sessionId) {
220
+ return this.perSession.get(sessionId) || null;
221
+ }
222
+ getTotalSessionsCreated() {
223
+ return this.global.sessionsCreated;
224
+ }
225
+ }
226
+ //# sourceMappingURL=metrics.js.map
@@ -0,0 +1,84 @@
1
+ /**
2
+ * monitor.ts — Background monitor that polls sessions and routes events to channels.
3
+ *
4
+ * Runs a polling loop that:
5
+ * 1. Checks each active session for new JSONL entries
6
+ * 2. Detects status changes (working → idle, permission prompts, etc.)
7
+ * 3. Routes events to the ChannelManager (which fans out to Telegram, webhooks, etc.)
8
+ */
9
+ import { type SessionManager } from './session.js';
10
+ import { type ChannelManager } from './channels/index.js';
11
+ import { type SessionEventBus } from './events.js';
12
+ import { type JsonlWatcher } from './jsonl-watcher.js';
13
+ export interface MonitorConfig {
14
+ pollIntervalMs: number;
15
+ fastPollIntervalMs: number;
16
+ hookQuietMs: number;
17
+ stallThresholdMs: number;
18
+ stallCheckIntervalMs: number;
19
+ deadCheckIntervalMs: number;
20
+ permissionStallMs: number;
21
+ unknownStallMs: number;
22
+ permissionTimeoutMs: number;
23
+ }
24
+ export declare const DEFAULT_MONITOR_CONFIG: MonitorConfig;
25
+ export declare class SessionMonitor {
26
+ private sessions;
27
+ private channels;
28
+ private config;
29
+ private running;
30
+ private lastStatus;
31
+ private lastBytesSeen;
32
+ private stallNotified;
33
+ private lastStallCheck;
34
+ private lastDeadCheck;
35
+ private idleNotified;
36
+ private idleSince;
37
+ private processedStopSignals;
38
+ private static readonly MAX_PROCESSED_STOP_SIGNALS;
39
+ private stateSince;
40
+ private deadNotified;
41
+ private prevStatusForStall;
42
+ private rateLimitedSessions;
43
+ /** Issue #89 L4: Debounce status change broadcasts per session.
44
+ * If multiple status changes happen within 500ms, only emit the last one.
45
+ * Prevents rapid-fire notifications during state transitions. */
46
+ private statusChangeDebounce;
47
+ /** Issue #32: Optional SSE event bus for real-time streaming. */
48
+ private eventBus?;
49
+ /** Issue #84: fs.watch-based JSONL watcher for near-instant message detection. */
50
+ private jsonlWatcher?;
51
+ constructor(sessions: SessionManager, channels: ChannelManager, config?: MonitorConfig);
52
+ /** Issue #32: Set the event bus for SSE streaming. */
53
+ setEventBus(bus: SessionEventBus): void;
54
+ /** Issue #84: Set the JSONL watcher for fs.watch-based message detection. */
55
+ setJsonlWatcher(watcher: JsonlWatcher): void;
56
+ start(): void;
57
+ stop(): void;
58
+ private loop;
59
+ /** Check if any active session hasn't received a hook recently. */
60
+ private needsFastPolling;
61
+ private poll;
62
+ /** Smart stall detection: multiple stall types with graduated thresholds.
63
+ *
64
+ * Detects 4 types of stalls:
65
+ * 1. JSONL stall: "working" but no new JSONL bytes for stallThresholdMs
66
+ * 2. Permission stall: permission_prompt/bash_approval for permissionStallMs
67
+ * 3. Unknown stall: unknown state for unknownStallMs (CC stuck in transition)
68
+ * 4. State duration stall: any non-idle state for 2x its threshold
69
+ */
70
+ private checkForStalls;
71
+ /** Issue #15: Check for Stop/StopFailure signals written by hook.ts. */
72
+ private checkStopSignals;
73
+ /** Issue #84: Handle new entries from the fs.watch-based JSONL watcher.
74
+ * Forwards messages to channels and updates stall tracking. */
75
+ private handleWatcherEvent;
76
+ private checkSession;
77
+ private forwardMessage;
78
+ private broadcastStatusChange;
79
+ private makePayload;
80
+ /** Check for dead tmux windows and notify via channels. */
81
+ private checkDeadSessions;
82
+ /** Clean up tracking for a killed session. */
83
+ removeSession(sessionId: string): void;
84
+ }