@nicofains1/agentwatch 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,127 @@
1
+ # @nicofains1/agentwatch
2
+
3
+ Observability for multi-agent systems. Track heartbeats, trace cross-agent actions, detect cascade failures, and replay what went wrong.
4
+
5
+ Built for teams running fleets of AI agents (CrewAI, AutoGen, LangGraph, custom) who need to understand why Agent B failed after Agent A timed out.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @nicofains1/agentwatch
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```typescript
16
+ import { AgentWatch } from '@nicofains1/agentwatch';
17
+
18
+ const aw = new AgentWatch(); // uses agentwatch.db by default
19
+
20
+ // Register heartbeats from your agents
21
+ aw.report('agent-a', 'healthy');
22
+ aw.report('agent-b', 'healthy');
23
+
24
+ // Trace cross-agent actions
25
+ const traceId = aw.createTraceId();
26
+
27
+ const e1 = aw.trace(traceId, 'agent-a', 'fetch-data', 'url=https://api.example.com', 'rows=150');
28
+ const e2 = aw.trace(traceId, 'agent-b', 'process', JSON.stringify({ rows: 150 }), '', {
29
+ parentEventId: e1.id,
30
+ status: 'error',
31
+ durationMs: 4200,
32
+ });
33
+
34
+ // Find the root cause
35
+ const chain = aw.correlate(e2.id);
36
+ console.log(chain?.root_cause); // -> agent-a / fetch-data
37
+
38
+ // Fleet dashboard
39
+ console.log(aw.dashboardText());
40
+ ```
41
+
42
+ ## Features
43
+
44
+ **Heartbeat registration** - Track agent health status over time. Detect stale or offline agents based on configurable thresholds.
45
+
46
+ **Cross-agent tracing** - Link actions across agents with trace IDs and parent event references. When agent-c fails because agent-b sent bad data that it got from agent-a, the trace shows the full chain.
47
+
48
+ **Cascade failure detection** - Walk backward from any failure to find the root cause across your agent fleet. `correlate(failureEventId)` returns the full chain from root cause to final failure.
49
+
50
+ **Alert de-duplication** - Same alert type from the same agent within a time window gets collapsed into one alert with an incrementing count. Severity auto-escalates: info (1x) -> warning (3x) -> critical (10x).
51
+
52
+ **Fleet dashboard** - One-line summary of your entire fleet: which agents are healthy, degraded, erroring, or offline. Uptime percentages and active alert counts per agent.
53
+
54
+ **Forensic replay** - Given a trace ID, replay all cascade chains to understand the full failure sequence.
55
+
56
+ ## CLI
57
+
58
+ ```bash
59
+ npx agentwatch dashboard # Fleet health overview
60
+ npx agentwatch cascade <event-id> # Trace cascade from a failure
61
+ npx agentwatch failures [agent] # List recent failures
62
+ npx agentwatch alerts [agent] # List active alerts
63
+ npx agentwatch replay <trace-id> # Replay all cascades in a trace
64
+ ```
65
+
66
+ Set `AGENTWATCH_DB` to point to your database file (default: `agentwatch.db`).
67
+
68
+ ## API
69
+
70
+ ### `new AgentWatch(config?)`
71
+
72
+ ```typescript
73
+ const aw = new AgentWatch({
74
+ db_path: 'agentwatch.db', // SQLite file path (default: agentwatch.db)
75
+ alert_window_minutes: 30, // De-dup window for alerts (default: 30)
76
+ heartbeat_stale_minutes: 30, // When to mark agents as offline (default: 30)
77
+ });
78
+ ```
79
+
80
+ ### Heartbeats
81
+
82
+ ```typescript
83
+ aw.report(agent: string, status: 'healthy' | 'degraded' | 'error' | 'offline', context?: string)
84
+ aw.getLatestHeartbeat(agent: string): Heartbeat | undefined
85
+ aw.getFleetHealth(): AgentHealth[]
86
+ ```
87
+
88
+ ### Tracing
89
+
90
+ ```typescript
91
+ aw.createTraceId(): string
92
+ aw.trace(traceId, agent, action, input, output, opts?): TraceEvent
93
+ aw.getTraceEvents(traceId: string): TraceEvent[]
94
+ aw.getRecentFailures(agent?: string, limit?: number): TraceEvent[]
95
+ ```
96
+
97
+ ### Cascade Detection
98
+
99
+ ```typescript
100
+ aw.correlate(failureEventId: number): CascadeChain | null
101
+ aw.replay(traceId: string): CascadeChain[]
102
+ ```
103
+
104
+ ### Alerts
105
+
106
+ ```typescript
107
+ aw.alert(agent, alertType, message): Alert
108
+ aw.resolveAlert(alertId: number): void
109
+ aw.activeAlerts(agent?: string): Alert[]
110
+ ```
111
+
112
+ ### Dashboard
113
+
114
+ ```typescript
115
+ aw.dashboard(): DashboardOutput
116
+ aw.dashboardText(): string
117
+ ```
118
+
119
+ ## Storage
120
+
121
+ Uses SQLite via `better-sqlite3`. The database file is created automatically on first use. WAL mode is enabled for concurrent reads.
122
+
123
+ Tables: `heartbeats`, `trace_events`, `alerts` - all with proper indexes for fast lookups.
124
+
125
+ ## License
126
+
127
+ MIT
@@ -0,0 +1,11 @@
1
+ import type Database from 'better-sqlite3';
2
+ import type { Alert } from './types.js';
3
+ /**
4
+ * Record an alert with de-duplication.
5
+ * If the same (agent, alert_type) exists within the window, increment count.
6
+ * Escalate severity based on count: 1 = info, 3+ = warning, 10+ = critical.
7
+ */
8
+ export declare function recordAlert(db: Database.Database, agent: string, alertType: string, message: string, windowMinutes?: number): Alert;
9
+ export declare function resolveAlert(db: Database.Database, alertId: number): void;
10
+ export declare function getActiveAlerts(db: Database.Database, agent?: string): Alert[];
11
+ //# sourceMappingURL=alerts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alerts.d.ts","sourceRoot":"","sources":["../src/alerts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAC3C,OAAO,KAAK,EAAE,KAAK,EAAiB,MAAM,YAAY,CAAC;AAOvD;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,aAAa,GAAE,MAAW,GACzB,KAAK,CAuCP;AAED,wBAAgB,YAAY,CAC1B,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,OAAO,EAAE,MAAM,GACd,IAAI,CAEN;AAED,wBAAgB,eAAe,CAC7B,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,KAAK,CAAC,EAAE,MAAM,GACb,KAAK,EAAE,CAWT"}
package/dist/alerts.js ADDED
@@ -0,0 +1,67 @@
1
+ function sqliteNow(offsetMs = 0) {
2
+ const d = new Date(Date.now() + offsetMs);
3
+ return d.toISOString().replace('T', ' ').replace(/\.\d{3}Z$/, '');
4
+ }
5
+ /**
6
+ * Record an alert with de-duplication.
7
+ * If the same (agent, alert_type) exists within the window, increment count.
8
+ * Escalate severity based on count: 1 = info, 3+ = warning, 10+ = critical.
9
+ */
10
+ export function recordAlert(db, agent, alertType, message, windowMinutes = 30) {
11
+ const windowStart = sqliteNow(-windowMinutes * 60 * 1000);
12
+ const existing = db.prepare(`
13
+ SELECT * FROM alerts
14
+ WHERE agent = ? AND alert_type = ? AND resolved = 0 AND last_seen > ?
15
+ ORDER BY last_seen DESC
16
+ LIMIT 1
17
+ `).get(agent, alertType, windowStart);
18
+ if (existing) {
19
+ const newCount = existing.count + 1;
20
+ const severity = escalateSeverity(newCount);
21
+ db.prepare(`
22
+ UPDATE alerts
23
+ SET count = ?, severity = ?, message = ?, last_seen = datetime('now')
24
+ WHERE id = ?
25
+ `).run(newCount, severity, message, existing.id);
26
+ return { ...existing, count: newCount, severity, message, last_seen: new Date().toISOString() };
27
+ }
28
+ const stmt = db.prepare(`
29
+ INSERT INTO alerts (agent, alert_type, severity, message)
30
+ VALUES (?, ?, 'info', ?)
31
+ `);
32
+ const result = stmt.run(agent, alertType, message);
33
+ return {
34
+ id: Number(result.lastInsertRowid),
35
+ agent,
36
+ alert_type: alertType,
37
+ severity: 'info',
38
+ message,
39
+ count: 1,
40
+ first_seen: new Date().toISOString(),
41
+ last_seen: new Date().toISOString(),
42
+ resolved: false,
43
+ };
44
+ }
45
+ export function resolveAlert(db, alertId) {
46
+ db.prepare(`UPDATE alerts SET resolved = 1 WHERE id = ?`).run(alertId);
47
+ }
48
+ export function getActiveAlerts(db, agent) {
49
+ if (agent) {
50
+ return db.prepare(`
51
+ SELECT * FROM alerts WHERE agent = ? AND resolved = 0
52
+ ORDER BY severity DESC, last_seen DESC
53
+ `).all(agent);
54
+ }
55
+ return db.prepare(`
56
+ SELECT * FROM alerts WHERE resolved = 0
57
+ ORDER BY severity DESC, last_seen DESC
58
+ `).all();
59
+ }
60
+ function escalateSeverity(count) {
61
+ if (count >= 10)
62
+ return 'critical';
63
+ if (count >= 3)
64
+ return 'warning';
65
+ return 'info';
66
+ }
67
+ //# sourceMappingURL=alerts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alerts.js","sourceRoot":"","sources":["../src/alerts.ts"],"names":[],"mappings":"AAGA,SAAS,SAAS,CAAC,QAAQ,GAAG,CAAC;IAC7B,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,CAAC,CAAC;IAC1C,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;AACpE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CACzB,EAAqB,EACrB,KAAa,EACb,SAAiB,EACjB,OAAe,EACf,gBAAwB,EAAE;IAE1B,MAAM,WAAW,GAAG,SAAS,CAAC,CAAC,aAAa,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IAE1D,MAAM,QAAQ,GAAG,EAAE,CAAC,OAAO,CAAC;;;;;GAK3B,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,SAAS,EAAE,WAAW,CAAsB,CAAC;IAE3D,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,QAAQ,GAAG,QAAQ,CAAC,KAAK,GAAG,CAAC,CAAC;QACpC,MAAM,QAAQ,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;QAC5C,EAAE,CAAC,OAAO,CAAC;;;;KAIV,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;QAEjD,OAAO,EAAE,GAAG,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC;IAClG,CAAC;IAED,MAAM,IAAI,GAAG,EAAE,CAAC,OAAO,CAAC;;;GAGvB,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;IAEnD,OAAO;QACL,EAAE,EAAE,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC;QAClC,KAAK;QACL,UAAU,EAAE,SAAS;QACrB,QAAQ,EAAE,MAAM;QAChB,OAAO;QACP,KAAK,EAAE,CAAC;QACR,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACpC,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,QAAQ,EAAE,KAAK;KAChB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,YAAY,CAC1B,EAAqB,EACrB,OAAe;IAEf,EAAE,CAAC,OAAO,CAAC,6CAA6C,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AACzE,CAAC;AAED,MAAM,UAAU,eAAe,CAC7B,EAAqB,EACrB,KAAc;IAEd,IAAI,KAAK,EAAE,CAAC;QACV,OAAO,EAAE,CAAC,OAAO,CAAC;;;KAGjB,CAAC,CAAC,GAAG,CAAC,KAAK,CAAY,CAAC;IAC3B,CAAC;IACD,OAAO,EAAE,CAAC,OAAO,CAAC;;;GAGjB,CAAC,CAAC,GAAG,EAAa,CAAC;AACtB,CAAC;AAED,SAAS,gBAAgB,CAAC,KAAa;IACrC,IAAI,KAAK,IAAI,EAAE;QAAE,OAAO,UAAU,CAAC;IACnC,IAAI,KAAK,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC;IACjC,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":""}
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env node
2
+ import { AgentWatch } from '../index.js';
3
+ const args = process.argv.slice(2);
4
+ const command = args[0];
5
+ const dbPath = process.env.AGENTWATCH_DB ?? 'agentwatch.db';
6
+ const aw = new AgentWatch({ db_path: dbPath });
7
+ try {
8
+ switch (command) {
9
+ case 'dashboard': {
10
+ console.log(aw.dashboardText());
11
+ break;
12
+ }
13
+ case 'cascade': {
14
+ const failureId = parseInt(args[1], 10);
15
+ if (isNaN(failureId)) {
16
+ console.error('Usage: agentwatch cascade <failure-event-id>');
17
+ process.exit(1);
18
+ }
19
+ const chain = aw.correlate(failureId);
20
+ if (!chain) {
21
+ console.error(`No trace event found with id ${failureId}`);
22
+ process.exit(1);
23
+ }
24
+ console.log(`Cascade for failure #${failureId}`);
25
+ console.log('='.repeat(60));
26
+ console.log(`Root cause: ${chain.root_cause.agent} / ${chain.root_cause.action}`);
27
+ console.log(`Chain length: ${chain.chain.length} steps`);
28
+ console.log('');
29
+ for (let i = 0; i < chain.chain.length; i++) {
30
+ const step = chain.chain[i];
31
+ const prefix = i === 0 ? 'ROOT' : i === chain.chain.length - 1 ? 'FAIL' : ` ${i} `;
32
+ console.log(`[${prefix}] ${step.agent} / ${step.action} (${step.status}, ${step.duration_ms}ms)`);
33
+ console.log(` Input: ${truncate(step.input, 120)}`);
34
+ console.log(` Output: ${truncate(step.output, 120)}`);
35
+ console.log(` Time: ${step.timestamp}`);
36
+ if (i < chain.chain.length - 1)
37
+ console.log(' |');
38
+ }
39
+ break;
40
+ }
41
+ case 'failures': {
42
+ const agent = args[1];
43
+ const limit = parseInt(args[2] ?? '20', 10);
44
+ const failures = aw.getRecentFailures(agent, limit);
45
+ if (failures.length === 0) {
46
+ console.log('No recent failures.');
47
+ break;
48
+ }
49
+ console.log(`Recent failures${agent ? ` for ${agent}` : ''} (${failures.length})`);
50
+ console.log('='.repeat(60));
51
+ for (const f of failures) {
52
+ console.log(`#${f.id} | ${f.agent} / ${f.action} | trace=${f.trace_id.slice(0, 8)} | ${f.created_at}`);
53
+ console.log(` Output: ${truncate(f.output, 100)}`);
54
+ }
55
+ break;
56
+ }
57
+ case 'alerts': {
58
+ const alertAgent = args[1];
59
+ const alerts = aw.activeAlerts(alertAgent);
60
+ if (alerts.length === 0) {
61
+ console.log('No active alerts.');
62
+ break;
63
+ }
64
+ console.log(`Active alerts (${alerts.length})`);
65
+ console.log('='.repeat(60));
66
+ for (const a of alerts) {
67
+ console.log(`[${a.severity.toUpperCase()}] ${a.agent} / ${a.alert_type} (x${a.count})`);
68
+ console.log(` ${a.message}`);
69
+ console.log(` First: ${a.first_seen} | Last: ${a.last_seen}`);
70
+ }
71
+ break;
72
+ }
73
+ case 'replay': {
74
+ const traceId = args[1];
75
+ if (!traceId) {
76
+ console.error('Usage: agentwatch replay <trace-id>');
77
+ process.exit(1);
78
+ }
79
+ const chains = aw.replay(traceId);
80
+ if (chains.length === 0) {
81
+ console.log(`No failures found in trace ${traceId}`);
82
+ break;
83
+ }
84
+ console.log(`Trace ${traceId} - ${chains.length} cascade(s) found`);
85
+ console.log('='.repeat(60));
86
+ for (const chain of chains) {
87
+ console.log('');
88
+ console.log(`Cascade -> failure #${chain.failure_id} (${chain.chain.length} steps)`);
89
+ for (let i = 0; i < chain.chain.length; i++) {
90
+ const step = chain.chain[i];
91
+ const arrow = i < chain.chain.length - 1 ? ' ->' : ' X';
92
+ console.log(` ${step.agent}/${step.action} [${step.status}]${arrow}`);
93
+ console.log(` in: ${truncate(step.input, 80)}`);
94
+ console.log(` out: ${truncate(step.output, 80)}`);
95
+ }
96
+ }
97
+ break;
98
+ }
99
+ default: {
100
+ console.log('AgentWatch - Multi-agent observability');
101
+ console.log('');
102
+ console.log('Usage:');
103
+ console.log(' agentwatch dashboard Fleet health overview');
104
+ console.log(' agentwatch cascade <event-id> Trace cascade from failure');
105
+ console.log(' agentwatch failures [agent] List recent failures');
106
+ console.log(' agentwatch alerts [agent] List active alerts');
107
+ console.log(' agentwatch replay <trace-id> Replay all cascades in a trace');
108
+ console.log('');
109
+ console.log('Environment:');
110
+ console.log(' AGENTWATCH_DB Path to SQLite database (default: agentwatch.db)');
111
+ }
112
+ }
113
+ }
114
+ finally {
115
+ aw.close();
116
+ }
117
+ function truncate(s, max) {
118
+ const oneline = s.replace(/\n/g, ' ');
119
+ return oneline.length > max ? oneline.slice(0, max - 3) + '...' : oneline;
120
+ }
121
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type Database from 'better-sqlite3';
2
+ import type { AgentHealth } from './types.js';
3
+ export interface DashboardOutput {
4
+ timestamp: string;
5
+ agents: AgentHealth[];
6
+ total_agents: number;
7
+ healthy_count: number;
8
+ degraded_count: number;
9
+ error_count: number;
10
+ offline_count: number;
11
+ total_active_alerts: number;
12
+ }
13
+ export declare function getDashboard(db: Database.Database, staleMinutes?: number): DashboardOutput;
14
+ export declare function formatDashboard(dashboard: DashboardOutput): string;
15
+ //# sourceMappingURL=dashboard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboard.d.ts","sourceRoot":"","sources":["../src/dashboard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAC3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAI9C,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAED,wBAAgB,YAAY,CAC1B,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,YAAY,GAAE,MAAW,GACxB,eAAe,CAcjB;AAED,wBAAgB,eAAe,CAAC,SAAS,EAAE,eAAe,GAAG,MAAM,CA0BlE"}
@@ -0,0 +1,40 @@
1
+ import { getFleetHealth } from './heartbeat.js';
2
+ import { getActiveAlerts } from './alerts.js';
3
+ export function getDashboard(db, staleMinutes = 30) {
4
+ const agents = getFleetHealth(db, staleMinutes);
5
+ const alerts = getActiveAlerts(db);
6
+ return {
7
+ timestamp: new Date().toISOString(),
8
+ agents,
9
+ total_agents: agents.length,
10
+ healthy_count: agents.filter(a => a.status === 'healthy').length,
11
+ degraded_count: agents.filter(a => a.status === 'degraded').length,
12
+ error_count: agents.filter(a => a.status === 'error').length,
13
+ offline_count: agents.filter(a => a.status === 'offline').length,
14
+ total_active_alerts: alerts.length,
15
+ };
16
+ }
17
+ export function formatDashboard(dashboard) {
18
+ const lines = [];
19
+ lines.push(`AgentWatch Fleet Dashboard - ${dashboard.timestamp}`);
20
+ lines.push('='.repeat(60));
21
+ lines.push('');
22
+ lines.push(`Agents: ${dashboard.total_agents} total | ${dashboard.healthy_count} healthy | ${dashboard.degraded_count} degraded | ${dashboard.error_count} error | ${dashboard.offline_count} offline`);
23
+ lines.push(`Active Alerts: ${dashboard.total_active_alerts}`);
24
+ lines.push('');
25
+ lines.push('Agent'.padEnd(15) + 'Status'.padEnd(12) + 'Uptime'.padEnd(10) + 'Alerts'.padEnd(10) + 'Last Heartbeat');
26
+ lines.push('-'.repeat(60));
27
+ for (const agent of dashboard.agents) {
28
+ const statusIcon = agent.status === 'healthy' ? 'OK'
29
+ : agent.status === 'degraded' ? 'WARN'
30
+ : agent.status === 'error' ? 'ERR'
31
+ : 'OFF';
32
+ lines.push(agent.agent.padEnd(15) +
33
+ statusIcon.padEnd(12) +
34
+ `${agent.uptime_pct}%`.padEnd(10) +
35
+ String(agent.active_alerts).padEnd(10) +
36
+ (agent.last_heartbeat || 'never'));
37
+ }
38
+ return lines.join('\n');
39
+ }
40
+ //# sourceMappingURL=dashboard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboard.js","sourceRoot":"","sources":["../src/dashboard.ts"],"names":[],"mappings":"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"}
package/dist/db.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import Database from 'better-sqlite3';
2
+ export declare function createDb(path: string): Database.Database;
3
+ //# sourceMappingURL=db.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AA2CtC,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAC,QAAQ,CAMxD"}
package/dist/db.js ADDED
@@ -0,0 +1,49 @@
1
+ import Database from 'better-sqlite3';
2
+ const SCHEMA = `
3
+ CREATE TABLE IF NOT EXISTS heartbeats (
4
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
5
+ agent TEXT NOT NULL,
6
+ status TEXT NOT NULL CHECK(status IN ('healthy', 'degraded', 'error', 'offline')),
7
+ context TEXT NOT NULL DEFAULT '{}',
8
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
9
+ );
10
+
11
+ CREATE TABLE IF NOT EXISTS trace_events (
12
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
13
+ trace_id TEXT NOT NULL,
14
+ agent TEXT NOT NULL,
15
+ action TEXT NOT NULL,
16
+ input TEXT NOT NULL DEFAULT '',
17
+ output TEXT NOT NULL DEFAULT '',
18
+ parent_event_id INTEGER REFERENCES trace_events(id),
19
+ status TEXT NOT NULL DEFAULT 'ok' CHECK(status IN ('ok', 'error')),
20
+ duration_ms INTEGER NOT NULL DEFAULT 0,
21
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
22
+ );
23
+
24
+ CREATE TABLE IF NOT EXISTS alerts (
25
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
26
+ agent TEXT NOT NULL,
27
+ alert_type TEXT NOT NULL,
28
+ severity TEXT NOT NULL DEFAULT 'info' CHECK(severity IN ('info', 'warning', 'critical')),
29
+ message TEXT NOT NULL,
30
+ count INTEGER NOT NULL DEFAULT 1,
31
+ first_seen TEXT NOT NULL DEFAULT (datetime('now')),
32
+ last_seen TEXT NOT NULL DEFAULT (datetime('now')),
33
+ resolved INTEGER NOT NULL DEFAULT 0
34
+ );
35
+
36
+ CREATE INDEX IF NOT EXISTS idx_heartbeats_agent ON heartbeats(agent, created_at);
37
+ CREATE INDEX IF NOT EXISTS idx_trace_events_trace ON trace_events(trace_id);
38
+ CREATE INDEX IF NOT EXISTS idx_trace_events_agent ON trace_events(agent, created_at);
39
+ CREATE INDEX IF NOT EXISTS idx_trace_events_parent ON trace_events(parent_event_id);
40
+ CREATE INDEX IF NOT EXISTS idx_alerts_agent ON alerts(agent, alert_type, resolved);
41
+ `;
42
+ export function createDb(path) {
43
+ const db = new Database(path);
44
+ db.pragma('journal_mode = WAL');
45
+ db.pragma('foreign_keys = ON');
46
+ db.exec(SCHEMA);
47
+ return db;
48
+ }
49
+ //# sourceMappingURL=db.js.map
package/dist/db.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.js","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAEtC,MAAM,MAAM,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuCd,CAAC;AAEF,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,MAAM,EAAE,GAAG,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC9B,EAAE,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;IAChC,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC;IAC/B,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAChB,OAAO,EAAE,CAAC;AACZ,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type Database from 'better-sqlite3';
2
+ import type { AgentStatus, Heartbeat, AgentHealth } from './types.js';
3
+ export declare function report(db: Database.Database, agent: string, status: AgentStatus, context?: string): Heartbeat;
4
+ export declare function getLatestHeartbeat(db: Database.Database, agent: string): Heartbeat | undefined;
5
+ export declare function getFleetHealth(db: Database.Database, staleMinutes?: number): AgentHealth[];
6
+ //# sourceMappingURL=heartbeat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"heartbeat.d.ts","sourceRoot":"","sources":["../src/heartbeat.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAC3C,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAQtE,wBAAgB,MAAM,CACpB,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,WAAW,EACnB,OAAO,GAAE,MAAa,GACrB,SAAS,CAaX;AAED,wBAAgB,kBAAkB,CAChC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,KAAK,EAAE,MAAM,GACZ,SAAS,GAAG,SAAS,CAOvB;AAED,wBAAgB,cAAc,CAC5B,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,YAAY,GAAE,MAAW,GACxB,WAAW,EAAE,CA4Cf"}
@@ -0,0 +1,66 @@
1
+ /** Format a date as SQLite-compatible `YYYY-MM-DD HH:MM:SS` */
2
+ function sqliteNow(offsetMs = 0) {
3
+ const d = new Date(Date.now() + offsetMs);
4
+ return d.toISOString().replace('T', ' ').replace(/\.\d{3}Z$/, '');
5
+ }
6
+ export function report(db, agent, status, context = '{}') {
7
+ const stmt = db.prepare(`
8
+ INSERT INTO heartbeats (agent, status, context)
9
+ VALUES (?, ?, ?)
10
+ `);
11
+ const result = stmt.run(agent, status, context);
12
+ return {
13
+ id: Number(result.lastInsertRowid),
14
+ agent,
15
+ status,
16
+ context,
17
+ created_at: new Date().toISOString(),
18
+ };
19
+ }
20
+ export function getLatestHeartbeat(db, agent) {
21
+ return db.prepare(`
22
+ SELECT * FROM heartbeats
23
+ WHERE agent = ?
24
+ ORDER BY created_at DESC
25
+ LIMIT 1
26
+ `).get(agent);
27
+ }
28
+ export function getFleetHealth(db, staleMinutes = 30) {
29
+ const agents = db.prepare(`
30
+ SELECT DISTINCT agent FROM heartbeats
31
+ `).all();
32
+ return agents.map(({ agent }) => {
33
+ const latest = getLatestHeartbeat(db, agent);
34
+ if (!latest) {
35
+ return { agent, status: 'offline', last_heartbeat: '', uptime_pct: 0, active_alerts: 0 };
36
+ }
37
+ const staleThreshold = sqliteNow(-staleMinutes * 60 * 1000);
38
+ const isStale = latest.created_at < staleThreshold;
39
+ const status = isStale ? 'offline' : latest.status;
40
+ // Uptime: % of heartbeats in last 24h that were 'healthy'
41
+ const dayAgo = sqliteNow(-24 * 60 * 60 * 1000);
42
+ const totalCount = db.prepare(`
43
+ SELECT COUNT(*) as cnt FROM heartbeats
44
+ WHERE agent = ? AND created_at > ?
45
+ `).get(agent, dayAgo);
46
+ const healthyCount = db.prepare(`
47
+ SELECT COUNT(*) as cnt FROM heartbeats
48
+ WHERE agent = ? AND created_at > ? AND status = 'healthy'
49
+ `).get(agent, dayAgo);
50
+ const uptime = totalCount.cnt > 0
51
+ ? Math.round((healthyCount.cnt / totalCount.cnt) * 100)
52
+ : 0;
53
+ const alertCount = db.prepare(`
54
+ SELECT COUNT(*) as cnt FROM alerts
55
+ WHERE agent = ? AND resolved = 0
56
+ `).get(agent);
57
+ return {
58
+ agent,
59
+ status,
60
+ last_heartbeat: latest.created_at,
61
+ uptime_pct: uptime,
62
+ active_alerts: alertCount.cnt,
63
+ };
64
+ });
65
+ }
66
+ //# sourceMappingURL=heartbeat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"heartbeat.js","sourceRoot":"","sources":["../src/heartbeat.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import type { AgentWatchConfig, AgentStatus, TraceEvent, CascadeChain } from './types.js';
2
+ export declare class AgentWatch {
3
+ private db;
4
+ private alertWindowMinutes;
5
+ private heartbeatStaleMinutes;
6
+ constructor(config?: AgentWatchConfig);
7
+ report(agent: string, status: AgentStatus, context?: string): import("./types.js").Heartbeat;
8
+ trace(traceId: string, agent: string, action: string, input: string, output: string, opts?: {
9
+ parentEventId?: number;
10
+ status?: 'ok' | 'error';
11
+ durationMs?: number;
12
+ }): TraceEvent;
13
+ correlate(failureEventId: number): CascadeChain | null;
14
+ dashboard(): import("./dashboard.js").DashboardOutput;
15
+ dashboardText(): string;
16
+ alert(agent: string, alertType: string, message: string): import("./types.js").Alert;
17
+ resolveAlert(alertId: number): void;
18
+ activeAlerts(agent?: string): import("./types.js").Alert[];
19
+ replay(traceId: string): CascadeChain[];
20
+ createTraceId(): string;
21
+ getTraceEvents(traceId: string): TraceEvent[];
22
+ getRecentFailures(agent?: string, limit?: number): TraceEvent[];
23
+ getLatestHeartbeat(agent: string): import("./types.js").Heartbeat | undefined;
24
+ getFleetHealth(): import("./types.js").AgentHealth[];
25
+ close(): void;
26
+ }
27
+ export type { AgentStatus, AlertSeverity, Heartbeat, TraceEvent, Alert, AgentHealth, CascadeStep, CascadeChain, AgentWatchConfig, } from './types.js';
28
+ export { createTraceId } from './trace.js';
29
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1F,qBAAa,UAAU;IACrB,OAAO,CAAC,EAAE,CAAoB;IAC9B,OAAO,CAAC,kBAAkB,CAAS;IACnC,OAAO,CAAC,qBAAqB,CAAS;gBAE1B,MAAM,GAAE,gBAAqB;IAQzC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,GAAE,MAAa;IAKjE,KAAK,CACH,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,GAC9E,UAAU;IAWb,SAAS,CAAC,cAAc,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI;IAKtD,SAAS;IAIT,aAAa,IAAI,MAAM;IAKvB,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;IAIvD,YAAY,CAAC,OAAO,EAAE,MAAM;IAI5B,YAAY,CAAC,KAAK,CAAC,EAAE,MAAM;IAK3B,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,EAAE;IAKvC,aAAa,IAAI,MAAM;IAIvB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,EAAE;IAI7C,iBAAiB,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,UAAU,EAAE;IAI/D,kBAAkB,CAAC,KAAK,EAAE,MAAM;IAIhC,cAAc;IAId,KAAK;CAGN;AAGD,YAAY,EACV,WAAW,EACX,aAAa,EACb,SAAS,EACT,UAAU,EACV,KAAK,EACL,WAAW,EACX,WAAW,EACX,YAAY,EACZ,gBAAgB,GACjB,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,75 @@
1
+ import { createDb } from './db.js';
2
+ import { report, getLatestHeartbeat, getFleetHealth } from './heartbeat.js';
3
+ import { createTraceId, trace, correlate, getRecentFailures, getTraceEvents, replayTrace } from './trace.js';
4
+ import { recordAlert, resolveAlert, getActiveAlerts } from './alerts.js';
5
+ import { getDashboard, formatDashboard } from './dashboard.js';
6
+ export class AgentWatch {
7
+ db;
8
+ alertWindowMinutes;
9
+ heartbeatStaleMinutes;
10
+ constructor(config = {}) {
11
+ const dbPath = config.db_path ?? 'agentwatch.db';
12
+ this.db = createDb(dbPath);
13
+ this.alertWindowMinutes = config.alert_window_minutes ?? 30;
14
+ this.heartbeatStaleMinutes = config.heartbeat_stale_minutes ?? 30;
15
+ }
16
+ // Feature 1: Heartbeat registration
17
+ report(agent, status, context = '{}') {
18
+ return report(this.db, agent, status, context);
19
+ }
20
+ // Feature 2: Cross-agent event correlation
21
+ trace(traceId, agent, action, input, output, opts) {
22
+ const event = trace(this.db, traceId, agent, action, input, output, opts);
23
+ // Auto-alert on errors
24
+ if (opts?.status === 'error') {
25
+ recordAlert(this.db, agent, `trace_error:${action}`, output, this.alertWindowMinutes);
26
+ }
27
+ return event;
28
+ }
29
+ correlate(failureEventId) {
30
+ return correlate(this.db, failureEventId);
31
+ }
32
+ // Feature 3: Fleet health dashboard
33
+ dashboard() {
34
+ return getDashboard(this.db, this.heartbeatStaleMinutes);
35
+ }
36
+ dashboardText() {
37
+ return formatDashboard(this.dashboard());
38
+ }
39
+ // Feature 4: Alert de-duplication
40
+ alert(agent, alertType, message) {
41
+ return recordAlert(this.db, agent, alertType, message, this.alertWindowMinutes);
42
+ }
43
+ resolveAlert(alertId) {
44
+ return resolveAlert(this.db, alertId);
45
+ }
46
+ activeAlerts(agent) {
47
+ return getActiveAlerts(this.db, agent);
48
+ }
49
+ // Feature 5: Cascade replay
50
+ replay(traceId) {
51
+ return replayTrace(this.db, traceId);
52
+ }
53
+ // Utilities
54
+ createTraceId() {
55
+ return createTraceId();
56
+ }
57
+ getTraceEvents(traceId) {
58
+ return getTraceEvents(this.db, traceId);
59
+ }
60
+ getRecentFailures(agent, limit) {
61
+ return getRecentFailures(this.db, agent, limit);
62
+ }
63
+ getLatestHeartbeat(agent) {
64
+ return getLatestHeartbeat(this.db, agent);
65
+ }
66
+ getFleetHealth() {
67
+ return getFleetHealth(this.db, this.heartbeatStaleMinutes);
68
+ }
69
+ close() {
70
+ this.db.close();
71
+ }
72
+ }
73
+ // Re-export for direct use
74
+ export { createTraceId } from './trace.js';
75
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import type Database from 'better-sqlite3';
2
+ import type { TraceEvent, CascadeChain } from './types.js';
3
+ export declare function createTraceId(): string;
4
+ export declare function trace(db: Database.Database, traceId: string, agent: string, action: string, input: string, output: string, opts?: {
5
+ parentEventId?: number;
6
+ status?: 'ok' | 'error';
7
+ durationMs?: number;
8
+ }): TraceEvent;
9
+ export declare function getTraceEvents(db: Database.Database, traceId: string): TraceEvent[];
10
+ /**
11
+ * Walk backward from a failed event to find the root cause.
12
+ * Follows parent_event_id links to build the cascade chain.
13
+ */
14
+ export declare function correlate(db: Database.Database, failureEventId: number): CascadeChain | null;
15
+ /**
16
+ * Find recent failures for an agent or across all agents.
17
+ */
18
+ export declare function getRecentFailures(db: Database.Database, agent?: string, limit?: number): TraceEvent[];
19
+ /**
20
+ * Find all events in a trace that are errors, then correlate each.
21
+ * Returns all cascade chains for a given trace.
22
+ */
23
+ export declare function replayTrace(db: Database.Database, traceId: string): CascadeChain[];
24
+ //# sourceMappingURL=trace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trace.d.ts","sourceRoot":"","sources":["../src/trace.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAC3C,OAAO,KAAK,EAAE,UAAU,EAAe,YAAY,EAAE,MAAM,YAAY,CAAC;AAExE,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED,wBAAgB,KAAK,CACnB,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,IAAI,GAAE;IACJ,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;CAChB,GACL,UAAU,CA2BZ;AAED,wBAAgB,cAAc,CAC5B,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,OAAO,EAAE,MAAM,GACd,UAAU,EAAE,CAMd;AAED;;;GAGG;AACH,wBAAgB,SAAS,CACvB,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,cAAc,EAAE,MAAM,GACrB,YAAY,GAAG,IAAI,CAkCrB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,KAAK,CAAC,EAAE,MAAM,EACd,KAAK,GAAE,MAAW,GACjB,UAAU,EAAE,CAed;AAED;;;GAGG;AACH,wBAAgB,WAAW,CACzB,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,OAAO,EAAE,MAAM,GACd,YAAY,EAAE,CAUhB"}
package/dist/trace.js ADDED
@@ -0,0 +1,99 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ export function createTraceId() {
3
+ return randomUUID();
4
+ }
5
+ export function trace(db, traceId, agent, action, input, output, opts = {}) {
6
+ const stmt = db.prepare(`
7
+ INSERT INTO trace_events (trace_id, agent, action, input, output, parent_event_id, status, duration_ms)
8
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
9
+ `);
10
+ const result = stmt.run(traceId, agent, action, input, output, opts.parentEventId ?? null, opts.status ?? 'ok', opts.durationMs ?? 0);
11
+ return {
12
+ id: Number(result.lastInsertRowid),
13
+ trace_id: traceId,
14
+ agent,
15
+ action,
16
+ input,
17
+ output,
18
+ parent_event_id: opts.parentEventId ?? null,
19
+ status: opts.status ?? 'ok',
20
+ duration_ms: opts.durationMs ?? 0,
21
+ created_at: new Date().toISOString(),
22
+ };
23
+ }
24
+ export function getTraceEvents(db, traceId) {
25
+ return db.prepare(`
26
+ SELECT * FROM trace_events
27
+ WHERE trace_id = ?
28
+ ORDER BY created_at ASC
29
+ `).all(traceId);
30
+ }
31
+ /**
32
+ * Walk backward from a failed event to find the root cause.
33
+ * Follows parent_event_id links to build the cascade chain.
34
+ */
35
+ export function correlate(db, failureEventId) {
36
+ const failure = db.prepare(`
37
+ SELECT * FROM trace_events WHERE id = ?
38
+ `).get(failureEventId);
39
+ if (!failure)
40
+ return null;
41
+ const chain = [];
42
+ let current = failure;
43
+ while (current) {
44
+ chain.unshift({
45
+ event_id: current.id,
46
+ agent: current.agent,
47
+ action: current.action,
48
+ input: current.input,
49
+ output: current.output,
50
+ status: current.status,
51
+ duration_ms: current.duration_ms,
52
+ timestamp: current.created_at,
53
+ });
54
+ if (current.parent_event_id === null)
55
+ break;
56
+ current = db.prepare(`
57
+ SELECT * FROM trace_events WHERE id = ?
58
+ `).get(current.parent_event_id);
59
+ }
60
+ return {
61
+ failure_id: failureEventId,
62
+ root_cause: chain[0],
63
+ chain,
64
+ };
65
+ }
66
+ /**
67
+ * Find recent failures for an agent or across all agents.
68
+ */
69
+ export function getRecentFailures(db, agent, limit = 20) {
70
+ if (agent) {
71
+ return db.prepare(`
72
+ SELECT * FROM trace_events
73
+ WHERE agent = ? AND status = 'error'
74
+ ORDER BY created_at DESC
75
+ LIMIT ?
76
+ `).all(agent, limit);
77
+ }
78
+ return db.prepare(`
79
+ SELECT * FROM trace_events
80
+ WHERE status = 'error'
81
+ ORDER BY created_at DESC
82
+ LIMIT ?
83
+ `).all(limit);
84
+ }
85
+ /**
86
+ * Find all events in a trace that are errors, then correlate each.
87
+ * Returns all cascade chains for a given trace.
88
+ */
89
+ export function replayTrace(db, traceId) {
90
+ const failures = db.prepare(`
91
+ SELECT * FROM trace_events
92
+ WHERE trace_id = ? AND status = 'error'
93
+ ORDER BY created_at ASC
94
+ `).all(traceId);
95
+ return failures
96
+ .map(f => correlate(db, f.id))
97
+ .filter((c) => c !== null);
98
+ }
99
+ //# sourceMappingURL=trace.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trace.js","sourceRoot":"","sources":["../src/trace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAIzC,MAAM,UAAU,aAAa;IAC3B,OAAO,UAAU,EAAE,CAAC;AACtB,CAAC;AAED,MAAM,UAAU,KAAK,CACnB,EAAqB,EACrB,OAAe,EACf,KAAa,EACb,MAAc,EACd,KAAa,EACb,MAAc,EACd,OAII,EAAE;IAEN,MAAM,IAAI,GAAG,EAAE,CAAC,OAAO,CAAC;;;GAGvB,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CACrB,OAAO,EACP,KAAK,EACL,MAAM,EACN,KAAK,EACL,MAAM,EACN,IAAI,CAAC,aAAa,IAAI,IAAI,EAC1B,IAAI,CAAC,MAAM,IAAI,IAAI,EACnB,IAAI,CAAC,UAAU,IAAI,CAAC,CACrB,CAAC;IACF,OAAO;QACL,EAAE,EAAE,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC;QAClC,QAAQ,EAAE,OAAO;QACjB,KAAK;QACL,MAAM;QACN,KAAK;QACL,MAAM;QACN,eAAe,EAAE,IAAI,CAAC,aAAa,IAAI,IAAI;QAC3C,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,IAAI;QAC3B,WAAW,EAAE,IAAI,CAAC,UAAU,IAAI,CAAC;QACjC,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACrC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,cAAc,CAC5B,EAAqB,EACrB,OAAe;IAEf,OAAO,EAAE,CAAC,OAAO,CAAC;;;;GAIjB,CAAC,CAAC,GAAG,CAAC,OAAO,CAAiB,CAAC;AAClC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,SAAS,CACvB,EAAqB,EACrB,cAAsB;IAEtB,MAAM,OAAO,GAAG,EAAE,CAAC,OAAO,CAAC;;GAE1B,CAAC,CAAC,GAAG,CAAC,cAAc,CAA2B,CAAC;IAEjD,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAE1B,MAAM,KAAK,GAAkB,EAAE,CAAC;IAChC,IAAI,OAAO,GAA2B,OAAO,CAAC;IAE9C,OAAO,OAAO,EAAE,CAAC;QACf,KAAK,CAAC,OAAO,CAAC;YACZ,QAAQ,EAAE,OAAO,CAAC,EAAE;YACpB,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,WAAW,EAAE,OAAO,CAAC,WAAW;YAChC,SAAS,EAAE,OAAO,CAAC,UAAU;SAC9B,CAAC,CAAC;QAEH,IAAI,OAAO,CAAC,eAAe,KAAK,IAAI;YAAE,MAAM;QAE5C,OAAO,GAAG,EAAE,CAAC,OAAO,CAAC;;KAEpB,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,eAAe,CAA2B,CAAC;IAC5D,CAAC;IAED,OAAO;QACL,UAAU,EAAE,cAAc;QAC1B,UAAU,EAAE,KAAK,CAAC,CAAC,CAAC;QACpB,KAAK;KACN,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAC/B,EAAqB,EACrB,KAAc,EACd,QAAgB,EAAE;IAElB,IAAI,KAAK,EAAE,CAAC;QACV,OAAO,EAAE,CAAC,OAAO,CAAC;;;;;KAKjB,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAiB,CAAC;IACvC,CAAC;IACD,OAAO,EAAE,CAAC,OAAO,CAAC;;;;;GAKjB,CAAC,CAAC,GAAG,CAAC,KAAK,CAAiB,CAAC;AAChC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CACzB,EAAqB,EACrB,OAAe;IAEf,MAAM,QAAQ,GAAG,EAAE,CAAC,OAAO,CAAC;;;;GAI3B,CAAC,CAAC,GAAG,CAAC,OAAO,CAAiB,CAAC;IAEhC,OAAO,QAAQ;SACZ,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,SAAS,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;SAC7B,MAAM,CAAC,CAAC,CAAC,EAAqB,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;AAClD,CAAC"}
@@ -0,0 +1,60 @@
1
+ export type AgentStatus = 'healthy' | 'degraded' | 'error' | 'offline';
2
+ export type AlertSeverity = 'info' | 'warning' | 'critical';
3
+ export interface Heartbeat {
4
+ id: number;
5
+ agent: string;
6
+ status: AgentStatus;
7
+ context: string;
8
+ created_at: string;
9
+ }
10
+ export interface TraceEvent {
11
+ id: number;
12
+ trace_id: string;
13
+ agent: string;
14
+ action: string;
15
+ input: string;
16
+ output: string;
17
+ parent_event_id: number | null;
18
+ status: 'ok' | 'error';
19
+ duration_ms: number;
20
+ created_at: string;
21
+ }
22
+ export interface Alert {
23
+ id: number;
24
+ agent: string;
25
+ alert_type: string;
26
+ severity: AlertSeverity;
27
+ message: string;
28
+ count: number;
29
+ first_seen: string;
30
+ last_seen: string;
31
+ resolved: boolean;
32
+ }
33
+ export interface AgentHealth {
34
+ agent: string;
35
+ status: AgentStatus;
36
+ last_heartbeat: string;
37
+ uptime_pct: number;
38
+ active_alerts: number;
39
+ }
40
+ export interface CascadeStep {
41
+ event_id: number;
42
+ agent: string;
43
+ action: string;
44
+ input: string;
45
+ output: string;
46
+ status: 'ok' | 'error';
47
+ duration_ms: number;
48
+ timestamp: string;
49
+ }
50
+ export interface CascadeChain {
51
+ failure_id: number;
52
+ root_cause: CascadeStep;
53
+ chain: CascadeStep[];
54
+ }
55
+ export interface AgentWatchConfig {
56
+ db_path?: string;
57
+ alert_window_minutes?: number;
58
+ heartbeat_stale_minutes?: number;
59
+ }
60
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,UAAU,GAAG,OAAO,GAAG,SAAS,CAAC;AACvE,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,SAAS,GAAG,UAAU,CAAC;AAE5D,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,WAAW,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,MAAM,EAAE,IAAI,GAAG,OAAO,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,aAAa,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,WAAW,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,IAAI,GAAG,OAAO,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,WAAW,CAAC;IACxB,KAAK,EAAE,WAAW,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,uBAAuB,CAAC,EAAE,MAAM,CAAC;CAClC"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@nicofains1/agentwatch",
3
+ "version": "0.1.0",
4
+ "description": "Multi-agent observability: cascade failure detection, heartbeats, and forensic replay",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "bin": {
9
+ "agentwatch": "dist/cli/index.js"
10
+ },
11
+ "scripts": {
12
+ "build": "tsc",
13
+ "test": "vitest run",
14
+ "test:watch": "vitest",
15
+ "type-check": "tsc --noEmit",
16
+ "clean": "rm -rf dist"
17
+ },
18
+ "dependencies": {
19
+ "better-sqlite3": "^11.8.0"
20
+ },
21
+ "devDependencies": {
22
+ "@types/better-sqlite3": "^7.6.8"
23
+ },
24
+ "files": [
25
+ "dist"
26
+ ],
27
+ "keywords": [
28
+ "agents",
29
+ "observability",
30
+ "cascade-failure",
31
+ "multi-agent",
32
+ "monitoring",
33
+ "heartbeat",
34
+ "tracing",
35
+ "fleet-management",
36
+ "ai-agents",
37
+ "debugging"
38
+ ],
39
+ "author": "nicofains1",
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "https://github.com/nicofains1/agentwatch.git"
43
+ },
44
+ "homepage": "https://github.com/nicofains1/agentwatch",
45
+ "license": "MIT",
46
+ "publishConfig": {
47
+ "access": "public"
48
+ },
49
+ "engines": {
50
+ "node": ">=18"
51
+ }
52
+ }