@esotech/contextuate 2.0.0 → 2.1.1
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 +169 -1
- package/dist/commands/claude.d.ts +21 -0
- package/dist/commands/claude.js +213 -0
- package/dist/commands/context.d.ts +1 -0
- package/dist/commands/create.d.ts +3 -0
- package/dist/commands/index.d.ts +4 -0
- package/dist/commands/init.d.ts +7 -0
- package/dist/commands/init.js +67 -6
- package/dist/commands/install.d.ts +28 -0
- package/dist/commands/install.js +100 -11
- package/dist/commands/monitor.d.ts +55 -0
- package/dist/commands/monitor.js +1007 -0
- package/dist/commands/remove.d.ts +3 -0
- package/dist/commands/run.d.ts +6 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +113 -1
- package/dist/monitor/daemon/circuit-breaker.d.ts +121 -0
- package/dist/monitor/daemon/circuit-breaker.js +552 -0
- package/dist/monitor/daemon/cli.d.ts +8 -0
- package/dist/monitor/daemon/cli.js +82 -0
- package/dist/monitor/daemon/index.d.ts +137 -0
- package/dist/monitor/daemon/index.js +695 -0
- package/dist/monitor/daemon/notifier.d.ts +25 -0
- package/dist/monitor/daemon/notifier.js +98 -0
- package/dist/monitor/daemon/processor.d.ts +89 -0
- package/dist/monitor/daemon/processor.js +455 -0
- package/dist/monitor/daemon/state.d.ts +80 -0
- package/dist/monitor/daemon/state.js +162 -0
- package/dist/monitor/daemon/watcher.d.ts +47 -0
- package/dist/monitor/daemon/watcher.js +171 -0
- package/dist/monitor/daemon/wrapper-manager.d.ts +106 -0
- package/dist/monitor/daemon/wrapper-manager.js +374 -0
- package/dist/monitor/hooks/emit-event.js +652 -0
- package/dist/monitor/persistence/file-store.d.ts +88 -0
- package/dist/monitor/persistence/file-store.js +335 -0
- package/dist/monitor/persistence/index.d.ts +7 -0
- package/dist/monitor/persistence/index.js +10 -0
- package/dist/monitor/server/adapters/redis.d.ts +38 -0
- package/dist/monitor/server/adapters/redis.js +213 -0
- package/dist/monitor/server/adapters/unix-socket.d.ts +33 -0
- package/dist/monitor/server/adapters/unix-socket.js +182 -0
- package/dist/monitor/server/broker.d.ts +135 -0
- package/dist/monitor/server/broker.js +475 -0
- package/dist/monitor/server/cli.d.ts +8 -0
- package/dist/monitor/server/cli.js +98 -0
- package/dist/monitor/server/fastify.d.ts +16 -0
- package/dist/monitor/server/fastify.js +184 -0
- package/dist/monitor/server/index.d.ts +36 -0
- package/dist/monitor/server/index.js +153 -0
- package/dist/monitor/server/websocket.d.ts +80 -0
- package/dist/monitor/server/websocket.js +453 -0
- package/dist/monitor/ui/assets/index-4IssW9On.js +59 -0
- package/dist/monitor/ui/assets/index-vo9hLe5R.css +32 -0
- package/dist/monitor/ui/favicon.png +0 -0
- package/dist/monitor/ui/index.html +14 -0
- package/dist/monitor/ui/logo.png +0 -0
- package/dist/monitor/ui/logo.svg +1 -0
- package/dist/runtime/driver.d.ts +16 -0
- package/dist/runtime/tools.d.ts +10 -0
- package/dist/templates/README.md +33 -7
- package/dist/templates/agents/aegis.md +4 -0
- package/dist/templates/agents/archon.md +13 -22
- package/dist/templates/agents/atlas.md +4 -0
- package/dist/templates/agents/canvas.md +4 -0
- package/dist/templates/agents/chronicle.md +4 -0
- package/dist/templates/agents/chronos.md +4 -0
- package/dist/templates/agents/cipher.md +4 -0
- package/dist/templates/agents/crucible.md +4 -0
- package/dist/templates/agents/echo.md +4 -0
- package/dist/templates/agents/forge.md +4 -0
- package/dist/templates/agents/ledger.md +4 -0
- package/dist/templates/agents/meridian.md +4 -0
- package/dist/templates/agents/nexus.md +4 -0
- package/dist/templates/agents/pythia.md +217 -0
- package/dist/templates/agents/scribe.md +4 -0
- package/dist/templates/agents/sentinel.md +4 -0
- package/dist/templates/agents/{oracle.md → thoth.md} +11 -7
- package/dist/templates/agents/unity.md +4 -0
- package/dist/templates/agents/vox.md +4 -0
- package/dist/templates/agents/weaver.md +4 -0
- package/dist/templates/commands/consult.md +138 -0
- package/dist/templates/commands/orchestrate.md +173 -0
- package/dist/templates/framework-agents/documentation-expert.md +3 -3
- package/dist/templates/framework-agents/tools-expert.md +8 -8
- package/dist/templates/standards/agent-roles.md +68 -21
- package/dist/templates/standards/coding-standards.md +9 -26
- package/dist/templates/templates/context.md +17 -2
- package/dist/templates/templates/contextuate.md +21 -28
- package/dist/templates/tools/{agent-creator.tool.md → agent-creator.md} +3 -3
- package/dist/types/monitor.d.ts +660 -0
- package/dist/types/monitor.js +75 -0
- package/dist/utils/git.d.ts +9 -0
- package/dist/utils/tokens.d.ts +10 -0
- package/package.json +18 -5
- package/dist/templates/version.json +0 -8
- /package/dist/templates/templates/standards/{go.standards.md → go.md} +0 -0
- /package/dist/templates/templates/standards/{java.standards.md → java.md} +0 -0
- /package/dist/templates/templates/standards/{javascript.standards.md → javascript.md} +0 -0
- /package/dist/templates/templates/standards/{php.standards.md → php.md} +0 -0
- /package/dist/templates/templates/standards/{python.standards.md → python.md} +0 -0
- /package/dist/templates/tools/{quickref.tool.md → quickref.md} +0 -0
- /package/dist/templates/tools/{spawn.tool.md → spawn.md} +0 -0
- /package/dist/templates/tools/{standards-detector.tool.md → standards-detector.md} +0 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File-based Persistence Store
|
|
3
|
+
*
|
|
4
|
+
* Stores session data and events in JSON files.
|
|
5
|
+
* Uses JSON Lines format for event logs (one JSON object per line).
|
|
6
|
+
*/
|
|
7
|
+
import type { PersistenceStore, MonitorEvent, SessionMeta, SessionStatus } from '../../types/monitor';
|
|
8
|
+
export interface FileStoreOptions {
|
|
9
|
+
baseDir?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare class FileStore implements PersistenceStore {
|
|
12
|
+
private paths;
|
|
13
|
+
private baseDir;
|
|
14
|
+
private sessionsDir;
|
|
15
|
+
private processedDir;
|
|
16
|
+
constructor(options?: FileStoreOptions);
|
|
17
|
+
/**
|
|
18
|
+
* Initialize the store - create directories if needed
|
|
19
|
+
*/
|
|
20
|
+
init(): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Close the store (no-op for file store)
|
|
23
|
+
*/
|
|
24
|
+
close(): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Save an event to the session's event log
|
|
27
|
+
*/
|
|
28
|
+
saveEvent(event: MonitorEvent): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Get events for a session
|
|
31
|
+
*/
|
|
32
|
+
getEvents(sessionId: string, options?: {
|
|
33
|
+
limit?: number;
|
|
34
|
+
before?: number;
|
|
35
|
+
after?: number;
|
|
36
|
+
}): Promise<MonitorEvent[]>;
|
|
37
|
+
/**
|
|
38
|
+
* Get a single event by ID
|
|
39
|
+
*/
|
|
40
|
+
getEventById(sessionId: string, eventId: string): Promise<MonitorEvent | null>;
|
|
41
|
+
/**
|
|
42
|
+
* Save session metadata
|
|
43
|
+
*/
|
|
44
|
+
saveSession(session: SessionMeta): Promise<void>;
|
|
45
|
+
/**
|
|
46
|
+
* Get session metadata
|
|
47
|
+
*/
|
|
48
|
+
getSession(sessionId: string): Promise<SessionMeta | null>;
|
|
49
|
+
/**
|
|
50
|
+
* Get all sessions
|
|
51
|
+
*/
|
|
52
|
+
getSessions(options?: {
|
|
53
|
+
status?: SessionStatus;
|
|
54
|
+
limit?: number;
|
|
55
|
+
}): Promise<SessionMeta[]>;
|
|
56
|
+
/**
|
|
57
|
+
* Update session metadata
|
|
58
|
+
*/
|
|
59
|
+
updateSession(sessionId: string, updates: Partial<SessionMeta>): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* Delete a session
|
|
62
|
+
*/
|
|
63
|
+
deleteSession(sessionId: string): Promise<void>;
|
|
64
|
+
/**
|
|
65
|
+
* Delete all sessions
|
|
66
|
+
*/
|
|
67
|
+
deleteAllSessions(): Promise<void>;
|
|
68
|
+
/**
|
|
69
|
+
* Get all recent events across all sessions
|
|
70
|
+
*/
|
|
71
|
+
getAllRecentEvents(limit?: number): Promise<MonitorEvent[]>;
|
|
72
|
+
/**
|
|
73
|
+
* Delete old sessions
|
|
74
|
+
*/
|
|
75
|
+
pruneOldSessions(olderThan: number): Promise<number>;
|
|
76
|
+
/**
|
|
77
|
+
* Get event count for a session
|
|
78
|
+
*/
|
|
79
|
+
getEventCount(sessionId: string): Promise<number>;
|
|
80
|
+
/**
|
|
81
|
+
* Get the base directory path
|
|
82
|
+
*/
|
|
83
|
+
getBaseDir(): string;
|
|
84
|
+
/**
|
|
85
|
+
* Get list of processed event files (for recovery)
|
|
86
|
+
*/
|
|
87
|
+
getProcessedFiles(): Promise<string[]>;
|
|
88
|
+
}
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* File-based Persistence Store
|
|
4
|
+
*
|
|
5
|
+
* Stores session data and events in JSON files.
|
|
6
|
+
* Uses JSON Lines format for event logs (one JSON object per line).
|
|
7
|
+
*/
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
20
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
21
|
+
}) : function(o, v) {
|
|
22
|
+
o["default"] = v;
|
|
23
|
+
});
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.FileStore = void 0;
|
|
43
|
+
const fs = __importStar(require("fs"));
|
|
44
|
+
const path = __importStar(require("path"));
|
|
45
|
+
const readline = __importStar(require("readline"));
|
|
46
|
+
const monitor_js_1 = require("../../types/monitor.js");
|
|
47
|
+
class FileStore {
|
|
48
|
+
constructor(options) {
|
|
49
|
+
this.paths = (0, monitor_js_1.getDefaultMonitorPaths)();
|
|
50
|
+
this.baseDir = options?.baseDir || this.paths.baseDir;
|
|
51
|
+
this.sessionsDir = path.join(this.baseDir, 'sessions');
|
|
52
|
+
this.processedDir = path.join(this.baseDir, 'processed');
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Initialize the store - create directories if needed
|
|
56
|
+
*/
|
|
57
|
+
async init() {
|
|
58
|
+
await fs.promises.mkdir(this.sessionsDir, { recursive: true });
|
|
59
|
+
await fs.promises.mkdir(this.processedDir, { recursive: true });
|
|
60
|
+
console.log(`[FileStore] Initialized at ${this.baseDir}`);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Close the store (no-op for file store)
|
|
64
|
+
*/
|
|
65
|
+
async close() {
|
|
66
|
+
// Nothing to clean up
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Save an event to the session's event log
|
|
70
|
+
*/
|
|
71
|
+
async saveEvent(event) {
|
|
72
|
+
const sessionDir = path.join(this.sessionsDir, event.sessionId);
|
|
73
|
+
await fs.promises.mkdir(sessionDir, { recursive: true });
|
|
74
|
+
const eventsFile = path.join(sessionDir, 'events.jsonl');
|
|
75
|
+
const line = JSON.stringify(event) + '\n';
|
|
76
|
+
await fs.promises.appendFile(eventsFile, line);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Get events for a session
|
|
80
|
+
*/
|
|
81
|
+
async getEvents(sessionId, options) {
|
|
82
|
+
const eventsFile = path.join(this.sessionsDir, sessionId, 'events.jsonl');
|
|
83
|
+
if (!fs.existsSync(eventsFile)) {
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
const events = [];
|
|
87
|
+
const limit = options?.limit || 1000;
|
|
88
|
+
const fileStream = fs.createReadStream(eventsFile);
|
|
89
|
+
const rl = readline.createInterface({
|
|
90
|
+
input: fileStream,
|
|
91
|
+
crlfDelay: Infinity,
|
|
92
|
+
});
|
|
93
|
+
for await (const line of rl) {
|
|
94
|
+
if (!line.trim())
|
|
95
|
+
continue;
|
|
96
|
+
try {
|
|
97
|
+
const event = JSON.parse(line);
|
|
98
|
+
// Apply filters
|
|
99
|
+
if (options?.before && event.timestamp >= options.before)
|
|
100
|
+
continue;
|
|
101
|
+
if (options?.after && event.timestamp <= options.after)
|
|
102
|
+
continue;
|
|
103
|
+
events.push(event);
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
console.error('[FileStore] Failed to parse event line:', err);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// Sort by timestamp and limit
|
|
110
|
+
events.sort((a, b) => a.timestamp - b.timestamp);
|
|
111
|
+
if (events.length > limit) {
|
|
112
|
+
return events.slice(-limit);
|
|
113
|
+
}
|
|
114
|
+
return events;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Get a single event by ID
|
|
118
|
+
*/
|
|
119
|
+
async getEventById(sessionId, eventId) {
|
|
120
|
+
const eventsFile = path.join(this.sessionsDir, sessionId, 'events.jsonl');
|
|
121
|
+
if (!fs.existsSync(eventsFile)) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
const content = await fs.promises.readFile(eventsFile, 'utf8');
|
|
125
|
+
const lines = content.split('\n');
|
|
126
|
+
for (const line of lines) {
|
|
127
|
+
if (!line.trim())
|
|
128
|
+
continue;
|
|
129
|
+
try {
|
|
130
|
+
const event = JSON.parse(line);
|
|
131
|
+
if (event.id === eventId) {
|
|
132
|
+
return event;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
// Skip malformed lines
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Save session metadata
|
|
143
|
+
*/
|
|
144
|
+
async saveSession(session) {
|
|
145
|
+
const sessionDir = path.join(this.sessionsDir, session.sessionId);
|
|
146
|
+
await fs.promises.mkdir(sessionDir, { recursive: true });
|
|
147
|
+
const metaFile = path.join(sessionDir, 'meta.json');
|
|
148
|
+
await fs.promises.writeFile(metaFile, JSON.stringify(session, null, 2));
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Get session metadata
|
|
152
|
+
*/
|
|
153
|
+
async getSession(sessionId) {
|
|
154
|
+
const metaFile = path.join(this.sessionsDir, sessionId, 'meta.json');
|
|
155
|
+
if (!fs.existsSync(metaFile)) {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
const content = await fs.promises.readFile(metaFile, 'utf-8');
|
|
160
|
+
return JSON.parse(content);
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
console.error('[FileStore] Failed to read session:', err);
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Get all sessions
|
|
169
|
+
*/
|
|
170
|
+
async getSessions(options) {
|
|
171
|
+
if (!fs.existsSync(this.sessionsDir)) {
|
|
172
|
+
return [];
|
|
173
|
+
}
|
|
174
|
+
const sessions = [];
|
|
175
|
+
const entries = await fs.promises.readdir(this.sessionsDir, { withFileTypes: true });
|
|
176
|
+
for (const entry of entries) {
|
|
177
|
+
if (!entry.isDirectory())
|
|
178
|
+
continue;
|
|
179
|
+
const session = await this.getSession(entry.name);
|
|
180
|
+
if (!session)
|
|
181
|
+
continue;
|
|
182
|
+
// Apply status filter
|
|
183
|
+
if (options?.status && session.status !== options.status)
|
|
184
|
+
continue;
|
|
185
|
+
sessions.push(session);
|
|
186
|
+
}
|
|
187
|
+
// Sort by start time (newest first)
|
|
188
|
+
sessions.sort((a, b) => b.startTime - a.startTime);
|
|
189
|
+
// Apply limit
|
|
190
|
+
if (options?.limit && sessions.length > options.limit) {
|
|
191
|
+
return sessions.slice(0, options.limit);
|
|
192
|
+
}
|
|
193
|
+
return sessions;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Update session metadata
|
|
197
|
+
*/
|
|
198
|
+
async updateSession(sessionId, updates) {
|
|
199
|
+
const session = await this.getSession(sessionId);
|
|
200
|
+
if (!session) {
|
|
201
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
202
|
+
}
|
|
203
|
+
const updated = { ...session, ...updates };
|
|
204
|
+
await this.saveSession(updated);
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Delete a session
|
|
208
|
+
*/
|
|
209
|
+
async deleteSession(sessionId) {
|
|
210
|
+
const sessionDir = path.join(this.sessionsDir, sessionId);
|
|
211
|
+
if (!fs.existsSync(sessionDir)) {
|
|
212
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
213
|
+
}
|
|
214
|
+
try {
|
|
215
|
+
await fs.promises.rm(sessionDir, { recursive: true });
|
|
216
|
+
console.log(`[FileStore] Deleted session: ${sessionId}`);
|
|
217
|
+
}
|
|
218
|
+
catch (err) {
|
|
219
|
+
console.error(`[FileStore] Failed to delete session ${sessionId}:`, err);
|
|
220
|
+
throw err;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Delete all sessions
|
|
225
|
+
*/
|
|
226
|
+
async deleteAllSessions() {
|
|
227
|
+
if (!fs.existsSync(this.sessionsDir)) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
try {
|
|
231
|
+
const entries = await fs.promises.readdir(this.sessionsDir, { withFileTypes: true });
|
|
232
|
+
let deleted = 0;
|
|
233
|
+
for (const entry of entries) {
|
|
234
|
+
if (!entry.isDirectory())
|
|
235
|
+
continue;
|
|
236
|
+
const sessionDir = path.join(this.sessionsDir, entry.name);
|
|
237
|
+
try {
|
|
238
|
+
await fs.promises.rm(sessionDir, { recursive: true });
|
|
239
|
+
deleted++;
|
|
240
|
+
}
|
|
241
|
+
catch (err) {
|
|
242
|
+
console.error(`[FileStore] Failed to delete session ${entry.name}:`, err);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
console.log(`[FileStore] Deleted all sessions (${deleted} total)`);
|
|
246
|
+
}
|
|
247
|
+
catch (err) {
|
|
248
|
+
console.error('[FileStore] Failed to delete all sessions:', err);
|
|
249
|
+
throw err;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Get all recent events across all sessions
|
|
254
|
+
*/
|
|
255
|
+
async getAllRecentEvents(limit = 200) {
|
|
256
|
+
if (!fs.existsSync(this.sessionsDir)) {
|
|
257
|
+
return [];
|
|
258
|
+
}
|
|
259
|
+
const allEvents = [];
|
|
260
|
+
const entries = await fs.promises.readdir(this.sessionsDir, { withFileTypes: true });
|
|
261
|
+
// Read events from all sessions
|
|
262
|
+
for (const entry of entries) {
|
|
263
|
+
if (!entry.isDirectory())
|
|
264
|
+
continue;
|
|
265
|
+
const sessionEvents = await this.getEvents(entry.name, { limit: 100 });
|
|
266
|
+
allEvents.push(...sessionEvents);
|
|
267
|
+
}
|
|
268
|
+
// Sort by timestamp (newest first) and limit
|
|
269
|
+
allEvents.sort((a, b) => b.timestamp - a.timestamp);
|
|
270
|
+
return allEvents.slice(0, limit);
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Delete old sessions
|
|
274
|
+
*/
|
|
275
|
+
async pruneOldSessions(olderThan) {
|
|
276
|
+
const sessions = await this.getSessions();
|
|
277
|
+
let pruned = 0;
|
|
278
|
+
for (const session of sessions) {
|
|
279
|
+
const endTime = session.endTime || session.startTime;
|
|
280
|
+
if (endTime < olderThan) {
|
|
281
|
+
const sessionDir = path.join(this.sessionsDir, session.sessionId);
|
|
282
|
+
try {
|
|
283
|
+
await fs.promises.rm(sessionDir, { recursive: true });
|
|
284
|
+
pruned++;
|
|
285
|
+
}
|
|
286
|
+
catch (err) {
|
|
287
|
+
console.error(`[FileStore] Failed to prune session ${session.sessionId}:`, err);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
if (pruned > 0) {
|
|
292
|
+
console.log(`[FileStore] Pruned ${pruned} old sessions`);
|
|
293
|
+
}
|
|
294
|
+
return pruned;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Get event count for a session
|
|
298
|
+
*/
|
|
299
|
+
async getEventCount(sessionId) {
|
|
300
|
+
const eventsFile = path.join(this.sessionsDir, sessionId, 'events.jsonl');
|
|
301
|
+
if (!fs.existsSync(eventsFile)) {
|
|
302
|
+
return 0;
|
|
303
|
+
}
|
|
304
|
+
let count = 0;
|
|
305
|
+
const fileStream = fs.createReadStream(eventsFile);
|
|
306
|
+
const rl = readline.createInterface({
|
|
307
|
+
input: fileStream,
|
|
308
|
+
crlfDelay: Infinity,
|
|
309
|
+
});
|
|
310
|
+
for await (const line of rl) {
|
|
311
|
+
if (line.trim())
|
|
312
|
+
count++;
|
|
313
|
+
}
|
|
314
|
+
return count;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Get the base directory path
|
|
318
|
+
*/
|
|
319
|
+
getBaseDir() {
|
|
320
|
+
return this.baseDir;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Get list of processed event files (for recovery)
|
|
324
|
+
*/
|
|
325
|
+
async getProcessedFiles() {
|
|
326
|
+
try {
|
|
327
|
+
const files = await fs.promises.readdir(this.processedDir);
|
|
328
|
+
return files.filter(f => f.endsWith('.json')).sort();
|
|
329
|
+
}
|
|
330
|
+
catch {
|
|
331
|
+
return [];
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
exports.FileStore = FileStore;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Persistence Module
|
|
4
|
+
*
|
|
5
|
+
* Exports persistence store implementations.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.FileStore = void 0;
|
|
9
|
+
var file_store_1 = require("./file-store");
|
|
10
|
+
Object.defineProperty(exports, "FileStore", { enumerable: true, get: function () { return file_store_1.FileStore; } });
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redis Adapter
|
|
3
|
+
*
|
|
4
|
+
* Uses Redis pub/sub for distributed event handling.
|
|
5
|
+
* Allows multiple monitor instances and remote hook scripts.
|
|
6
|
+
*/
|
|
7
|
+
import type { IPCAdapter, EventHandler, MonitorEvent, RedisConfig } from '../../../types/monitor';
|
|
8
|
+
export interface RedisAdapterOptions {
|
|
9
|
+
config: RedisConfig;
|
|
10
|
+
}
|
|
11
|
+
export declare class RedisAdapter implements IPCAdapter {
|
|
12
|
+
private subscriber;
|
|
13
|
+
private publisher;
|
|
14
|
+
private handlers;
|
|
15
|
+
private running;
|
|
16
|
+
private config;
|
|
17
|
+
constructor(options: RedisAdapterOptions);
|
|
18
|
+
start(): Promise<void>;
|
|
19
|
+
stop(): Promise<void>;
|
|
20
|
+
onEvent(handler: EventHandler): void;
|
|
21
|
+
offEvent(handler: EventHandler): void;
|
|
22
|
+
isRunning(): boolean;
|
|
23
|
+
private emitEvent;
|
|
24
|
+
/**
|
|
25
|
+
* Publish an event to the Redis channel
|
|
26
|
+
* This is used by hook scripts in Redis mode
|
|
27
|
+
*/
|
|
28
|
+
publishEvent(event: MonitorEvent): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Get the Redis configuration for clients
|
|
31
|
+
*/
|
|
32
|
+
getConfig(): RedisConfig;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Create a standalone publisher for hook scripts
|
|
36
|
+
* This doesn't subscribe, just publishes events
|
|
37
|
+
*/
|
|
38
|
+
export declare function publishEventToRedis(config: RedisConfig, event: MonitorEvent): Promise<void>;
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Redis Adapter
|
|
4
|
+
*
|
|
5
|
+
* Uses Redis pub/sub for distributed event handling.
|
|
6
|
+
* Allows multiple monitor instances and remote hook scripts.
|
|
7
|
+
*/
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
20
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
21
|
+
}) : function(o, v) {
|
|
22
|
+
o["default"] = v;
|
|
23
|
+
});
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.RedisAdapter = void 0;
|
|
43
|
+
exports.publishEventToRedis = publishEventToRedis;
|
|
44
|
+
// Dynamic import for ioredis to make it optional
|
|
45
|
+
let Redis;
|
|
46
|
+
class RedisAdapter {
|
|
47
|
+
constructor(options) {
|
|
48
|
+
this.subscriber = null;
|
|
49
|
+
this.publisher = null;
|
|
50
|
+
this.handlers = new Set();
|
|
51
|
+
this.running = false;
|
|
52
|
+
this.config = options.config;
|
|
53
|
+
}
|
|
54
|
+
async start() {
|
|
55
|
+
if (this.running) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
// Dynamically import ioredis
|
|
59
|
+
try {
|
|
60
|
+
const ioredis = await Promise.resolve().then(() => __importStar(require('ioredis')));
|
|
61
|
+
Redis = ioredis.default;
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
throw new Error('Redis adapter requires ioredis package. Install with: npm install ioredis');
|
|
65
|
+
}
|
|
66
|
+
const redisOptions = {
|
|
67
|
+
host: this.config.host,
|
|
68
|
+
port: this.config.port,
|
|
69
|
+
password: this.config.password || undefined,
|
|
70
|
+
retryStrategy: (times) => {
|
|
71
|
+
const delay = Math.min(times * 100, 3000);
|
|
72
|
+
console.log(`[Redis] Reconnecting in ${delay}ms (attempt ${times}/10)...`);
|
|
73
|
+
if (times > 10) {
|
|
74
|
+
console.error('[Redis] Max reconnection attempts reached');
|
|
75
|
+
return null; // Stop retrying
|
|
76
|
+
}
|
|
77
|
+
return delay;
|
|
78
|
+
},
|
|
79
|
+
lazyConnect: true, // Don't auto-connect, we'll do it manually
|
|
80
|
+
};
|
|
81
|
+
// Create subscriber connection
|
|
82
|
+
this.subscriber = new Redis(redisOptions);
|
|
83
|
+
this.publisher = new Redis(redisOptions);
|
|
84
|
+
// Set up error handlers before connecting
|
|
85
|
+
this.subscriber.on('error', (err) => {
|
|
86
|
+
console.error('[Redis Subscriber] Connection error:', err.message);
|
|
87
|
+
});
|
|
88
|
+
this.publisher.on('error', (err) => {
|
|
89
|
+
console.error('[Redis Publisher] Connection error:', err.message);
|
|
90
|
+
});
|
|
91
|
+
this.subscriber.on('reconnecting', () => {
|
|
92
|
+
console.log('[Redis Subscriber] Reconnecting...');
|
|
93
|
+
});
|
|
94
|
+
this.publisher.on('reconnecting', () => {
|
|
95
|
+
console.log('[Redis Publisher] Reconnecting...');
|
|
96
|
+
});
|
|
97
|
+
// Connect both clients
|
|
98
|
+
try {
|
|
99
|
+
await Promise.all([
|
|
100
|
+
this.subscriber.connect(),
|
|
101
|
+
this.publisher.connect(),
|
|
102
|
+
]);
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
// Clean up on connection failure
|
|
106
|
+
this.subscriber.disconnect();
|
|
107
|
+
this.publisher.disconnect();
|
|
108
|
+
throw new Error(`Failed to connect to Redis: ${err instanceof Error ? err.message : String(err)}`);
|
|
109
|
+
}
|
|
110
|
+
// Subscribe to the channel
|
|
111
|
+
await this.subscriber.subscribe(this.config.channel);
|
|
112
|
+
// Handle incoming messages
|
|
113
|
+
this.subscriber.on('message', (channel, message) => {
|
|
114
|
+
if (channel === this.config.channel) {
|
|
115
|
+
try {
|
|
116
|
+
const event = JSON.parse(message);
|
|
117
|
+
console.log(`[Redis] Received event from ${event.machineId}/${event.sessionId}: ${event.eventType}`);
|
|
118
|
+
this.emitEvent(event);
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
console.error('[Redis] Failed to parse event:', err);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
this.running = true;
|
|
126
|
+
console.log(`[Redis] Connected to ${this.config.host}:${this.config.port}, channel: ${this.config.channel}`);
|
|
127
|
+
console.log('[Redis] Listening for events from multiple sources...');
|
|
128
|
+
}
|
|
129
|
+
async stop() {
|
|
130
|
+
if (!this.running) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (this.subscriber) {
|
|
134
|
+
await this.subscriber.unsubscribe(this.config.channel);
|
|
135
|
+
this.subscriber.disconnect();
|
|
136
|
+
this.subscriber = null;
|
|
137
|
+
}
|
|
138
|
+
if (this.publisher) {
|
|
139
|
+
this.publisher.disconnect();
|
|
140
|
+
this.publisher = null;
|
|
141
|
+
}
|
|
142
|
+
this.running = false;
|
|
143
|
+
console.log('[Redis] Disconnected');
|
|
144
|
+
}
|
|
145
|
+
onEvent(handler) {
|
|
146
|
+
this.handlers.add(handler);
|
|
147
|
+
}
|
|
148
|
+
offEvent(handler) {
|
|
149
|
+
this.handlers.delete(handler);
|
|
150
|
+
}
|
|
151
|
+
isRunning() {
|
|
152
|
+
return this.running;
|
|
153
|
+
}
|
|
154
|
+
emitEvent(event) {
|
|
155
|
+
for (const handler of this.handlers) {
|
|
156
|
+
try {
|
|
157
|
+
const result = handler(event);
|
|
158
|
+
if (result instanceof Promise) {
|
|
159
|
+
result.catch((err) => {
|
|
160
|
+
console.error('[Redis] Event handler error:', err);
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
catch (err) {
|
|
165
|
+
console.error('[Redis] Event handler error:', err);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Publish an event to the Redis channel
|
|
171
|
+
* This is used by hook scripts in Redis mode
|
|
172
|
+
*/
|
|
173
|
+
async publishEvent(event) {
|
|
174
|
+
if (!this.publisher) {
|
|
175
|
+
throw new Error('Redis adapter not started');
|
|
176
|
+
}
|
|
177
|
+
const message = JSON.stringify(event);
|
|
178
|
+
const subscriberCount = await this.publisher.publish(this.config.channel, message);
|
|
179
|
+
console.log(`[Redis] Published event ${event.eventType} from ${event.machineId}/${event.sessionId} (${subscriberCount} subscribers)`);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Get the Redis configuration for clients
|
|
183
|
+
*/
|
|
184
|
+
getConfig() {
|
|
185
|
+
return this.config;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
exports.RedisAdapter = RedisAdapter;
|
|
189
|
+
/**
|
|
190
|
+
* Create a standalone publisher for hook scripts
|
|
191
|
+
* This doesn't subscribe, just publishes events
|
|
192
|
+
*/
|
|
193
|
+
async function publishEventToRedis(config, event) {
|
|
194
|
+
let RedisClient;
|
|
195
|
+
try {
|
|
196
|
+
const ioredis = await Promise.resolve().then(() => __importStar(require('ioredis')));
|
|
197
|
+
RedisClient = ioredis.default;
|
|
198
|
+
}
|
|
199
|
+
catch (err) {
|
|
200
|
+
throw new Error('Redis requires ioredis package');
|
|
201
|
+
}
|
|
202
|
+
const client = new RedisClient({
|
|
203
|
+
host: config.host,
|
|
204
|
+
port: config.port,
|
|
205
|
+
password: config.password || undefined,
|
|
206
|
+
});
|
|
207
|
+
try {
|
|
208
|
+
await client.publish(config.channel, JSON.stringify(event));
|
|
209
|
+
}
|
|
210
|
+
finally {
|
|
211
|
+
client.disconnect();
|
|
212
|
+
}
|
|
213
|
+
}
|