@quantiya/codevibe-codex-plugin 1.0.7 → 1.0.9

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.
@@ -1,90 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.sessionIdCache = void 0;
37
- const fs = __importStar(require("fs"));
38
- const path = __importStar(require("path"));
39
- const os = __importStar(require("os"));
40
- const logger_1 = require("./logger");
41
- /**
42
- * Persists a mapping of Codex log filenames to backend session IDs.
43
- * This ensures resumes (which reuse the same log file) attach to the same backend session.
44
- */
45
- class SessionIdCache {
46
- constructor() {
47
- this.map = {};
48
- const configDir = path.join(os.homedir(), '.codevibe-codex');
49
- if (!fs.existsSync(configDir)) {
50
- fs.mkdirSync(configDir, { recursive: true });
51
- }
52
- this.cachePath = path.join(configDir, 'session-id-map.json');
53
- this.load();
54
- }
55
- load() {
56
- try {
57
- if (fs.existsSync(this.cachePath)) {
58
- const raw = fs.readFileSync(this.cachePath, 'utf-8');
59
- this.map = JSON.parse(raw);
60
- }
61
- }
62
- catch (error) {
63
- logger_1.logger.warn('Failed to load session ID cache', { cachePath: this.cachePath, error });
64
- this.map = {};
65
- }
66
- }
67
- save() {
68
- try {
69
- fs.writeFileSync(this.cachePath, JSON.stringify(this.map, null, 2), 'utf-8');
70
- }
71
- catch (error) {
72
- logger_1.logger.warn('Failed to write session ID cache', { cachePath: this.cachePath, error });
73
- }
74
- }
75
- get(logFilePath) {
76
- if (!logFilePath)
77
- return null;
78
- const key = path.basename(logFilePath);
79
- return this.map[key] || null;
80
- }
81
- set(logFilePath, sessionId) {
82
- if (!logFilePath)
83
- return;
84
- const key = path.basename(logFilePath);
85
- this.map[key] = sessionId;
86
- this.save();
87
- }
88
- }
89
- exports.sessionIdCache = new SessionIdCache();
90
- //# sourceMappingURL=session-id-cache.js.map
@@ -1,372 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.SessionLogWatcher = void 0;
37
- const events_1 = require("events");
38
- const fs = __importStar(require("fs"));
39
- const path = __importStar(require("path"));
40
- const readline = __importStar(require("readline"));
41
- const chokidar_1 = require("chokidar");
42
- const codevibe_core_1 = require("@quantiya/codevibe-core");
43
- const logger_1 = require("./logger");
44
- /**
45
- * Watches Codex CLI session log files for new entries
46
- *
47
- * Codex writes JSONL logs to: ~/.codex/sessions/YYYY/MM/DD/rollout-{timestamp}-{uuid}.jsonl
48
- *
49
- * Events emitted:
50
- * - 'session-started': New session detected (CodexSessionMeta)
51
- * - 'log-entry': New log entry (CodexLogEntry)
52
- * - 'error': Error occurred
53
- */
54
- class SessionLogWatcher extends events_1.EventEmitter {
55
- constructor() {
56
- super();
57
- this.watcher = null;
58
- this.filePositions = new Map();
59
- this.activeLogFile = null;
60
- this.sessionId = null;
61
- this.isWatching = false;
62
- this.startTime = 0; // Track when watcher started
63
- this.sessionsDir = null;
64
- }
65
- /**
66
- * Start watching for Codex session logs
67
- */
68
- start() {
69
- if (this.isWatching) {
70
- logger_1.logger.warn('Session log watcher already running');
71
- return;
72
- }
73
- const sessionsDir = (0, codevibe_core_1.getConfig)().codex.sessionsDir;
74
- this.sessionsDir = sessionsDir;
75
- logger_1.logger.info('Starting Codex session log watcher', { sessionsDir });
76
- // Ensure sessions directory exists
77
- if (!fs.existsSync(sessionsDir)) {
78
- logger_1.logger.info('Codex sessions directory does not exist yet, creating...', { sessionsDir });
79
- fs.mkdirSync(sessionsDir, { recursive: true });
80
- }
81
- // Record start time to filter out pre-existing files
82
- this.startTime = Date.now();
83
- // Watch the entire sessions directory recursively
84
- // Chokidar glob patterns don't work well on macOS, so watch the directory
85
- // and filter for .jsonl files in the event handlers
86
- // Use ignoreInitial: true to avoid processing old session files
87
- this.watcher = (0, chokidar_1.watch)(sessionsDir, {
88
- persistent: true,
89
- ignoreInitial: true, // Only watch for NEW files, not existing ones
90
- awaitWriteFinish: {
91
- stabilityThreshold: 100,
92
- pollInterval: 50,
93
- },
94
- depth: 4, // YYYY/MM/DD depth
95
- ignored: (filePath) => {
96
- // Only watch .jsonl files that match the rollout pattern
97
- const basename = path.basename(filePath);
98
- const isDir = fs.existsSync(filePath) && fs.statSync(filePath).isDirectory();
99
- if (isDir)
100
- return false; // Don't ignore directories
101
- return !basename.startsWith('rollout-') || !basename.endsWith('.jsonl');
102
- },
103
- });
104
- this.watcher.on('add', (filePath) => {
105
- if (filePath.endsWith('.jsonl')) {
106
- this.onFileAdded(filePath);
107
- }
108
- });
109
- this.watcher.on('change', (filePath) => {
110
- if (filePath.endsWith('.jsonl')) {
111
- this.onFileChanged(filePath);
112
- }
113
- });
114
- this.watcher.on('error', (error) => {
115
- logger_1.logger.error('Watcher error:', error);
116
- this.emit('error', error);
117
- });
118
- this.watcher.on('ready', () => {
119
- logger_1.logger.info('Session log watcher ready');
120
- this.bindRecentSessionFile();
121
- });
122
- this.isWatching = true;
123
- logger_1.logger.info('Session log watcher started');
124
- }
125
- /**
126
- * Stop watching for session logs
127
- */
128
- stop() {
129
- if (this.watcher) {
130
- this.watcher.close();
131
- this.watcher = null;
132
- }
133
- this.isWatching = false;
134
- this.filePositions.clear();
135
- this.activeLogFile = null;
136
- this.sessionId = null;
137
- this.sessionsDir = null;
138
- logger_1.logger.info('Session log watcher stopped');
139
- }
140
- /**
141
- * Get the current active session ID
142
- */
143
- getSessionId() {
144
- return this.sessionId;
145
- }
146
- /**
147
- * Get the current active log file
148
- */
149
- getActiveLogFile() {
150
- return this.activeLogFile;
151
- }
152
- /**
153
- * Handle new log file detected
154
- */
155
- onFileAdded(filePath) {
156
- // Safety check: only process files created after watcher started
157
- // This prevents processing old sessions if ignoreInitial doesn't work
158
- try {
159
- const stat = fs.statSync(filePath);
160
- const fileCreatedAt = stat.birthtimeMs || stat.ctimeMs;
161
- // Allow 5 second buffer for timing differences
162
- if (fileCreatedAt < this.startTime - 5000) {
163
- logger_1.logger.debug('Ignoring old session file', {
164
- filePath,
165
- fileCreatedAt: new Date(fileCreatedAt).toISOString(),
166
- startTime: new Date(this.startTime).toISOString()
167
- });
168
- return;
169
- }
170
- }
171
- catch (error) {
172
- logger_1.logger.warn('Could not check file creation time', { filePath, error });
173
- }
174
- if (this.activeLogFile) {
175
- logger_1.logger.debug('Ignoring additional Codex session log for this server instance', {
176
- activeLogFile: this.activeLogFile,
177
- ignoredFile: filePath,
178
- });
179
- return;
180
- }
181
- logger_1.logger.info('New Codex session log detected', { filePath });
182
- // This is now the active session
183
- this.activeLogFile = filePath;
184
- this.filePositions.set(filePath, 0);
185
- // Read all existing content
186
- this.readNewLines(filePath);
187
- }
188
- /**
189
- * Handle log file changes
190
- */
191
- onFileChanged(filePath) {
192
- // Each server instance must stay pinned to the first session log it binds to.
193
- // Otherwise a second Codex session can hijack the first server instance.
194
- if (this.activeLogFile && filePath !== this.activeLogFile) {
195
- logger_1.logger.debug('Ignoring change for non-active session log', {
196
- activeLogFile: this.activeLogFile,
197
- ignoredFile: filePath,
198
- });
199
- return;
200
- }
201
- if (!this.activeLogFile) {
202
- // Only bind to files created or modified after this watcher started.
203
- // Without this check, a second server instance can accidentally bind to
204
- // the first server's Codex log file when Codex 1 writes new content,
205
- // causing Codex 2's events to be lost entirely.
206
- try {
207
- const stat = fs.statSync(filePath);
208
- const fileCreatedAt = stat.birthtimeMs || stat.ctimeMs;
209
- const fileModifiedAt = stat.mtimeMs;
210
- // Allow files that were created recently OR modified recently (resumed sessions)
211
- if (fileCreatedAt < this.startTime - 5000 && fileModifiedAt < this.startTime - 5000) {
212
- logger_1.logger.debug('Ignoring change for pre-existing session file', {
213
- filePath,
214
- fileCreatedAt: new Date(fileCreatedAt).toISOString(),
215
- fileModifiedAt: new Date(fileModifiedAt).toISOString(),
216
- startTime: new Date(this.startTime).toISOString(),
217
- });
218
- return;
219
- }
220
- }
221
- catch (error) {
222
- logger_1.logger.warn('Could not check file creation time during change event', { filePath, error });
223
- }
224
- this.activeLogFile = filePath;
225
- this.filePositions.set(filePath, 0);
226
- }
227
- this.readNewLines(filePath);
228
- }
229
- /**
230
- * Backfill any very recent session file that was created while chokidar was
231
- * still performing its initial scan. This avoids missing the first log file
232
- * for a newly started server instance.
233
- */
234
- bindRecentSessionFile() {
235
- if (this.activeLogFile || !this.sessionsDir) {
236
- return;
237
- }
238
- try {
239
- const candidates = this.collectRecentSessionFiles(this.sessionsDir)
240
- .sort((left, right) => right.modifiedAt - left.modifiedAt);
241
- const recent = candidates[0];
242
- if (!recent) {
243
- return;
244
- }
245
- logger_1.logger.info('Binding recent session file missed during initial watch scan', {
246
- filePath: recent.filePath,
247
- fileCreatedAt: new Date(recent.createdAt).toISOString(),
248
- fileModifiedAt: new Date(recent.modifiedAt).toISOString(),
249
- watcherStartTime: new Date(this.startTime).toISOString(),
250
- });
251
- this.activeLogFile = recent.filePath;
252
- this.filePositions.set(recent.filePath, 0);
253
- this.readNewLines(recent.filePath);
254
- }
255
- catch (error) {
256
- logger_1.logger.warn('Failed to backfill recent session file', { error });
257
- }
258
- }
259
- collectRecentSessionFiles(rootDir) {
260
- const results = [];
261
- const stack = [rootDir];
262
- while (stack.length > 0) {
263
- const currentDir = stack.pop();
264
- if (!currentDir) {
265
- continue;
266
- }
267
- let entries;
268
- try {
269
- entries = fs.readdirSync(currentDir, { withFileTypes: true });
270
- }
271
- catch {
272
- continue;
273
- }
274
- for (const entry of entries) {
275
- const fullPath = path.join(currentDir, entry.name);
276
- if (entry.isDirectory()) {
277
- stack.push(fullPath);
278
- continue;
279
- }
280
- if (!entry.isFile() || !entry.name.startsWith('rollout-') || !entry.name.endsWith('.jsonl')) {
281
- continue;
282
- }
283
- try {
284
- const stat = fs.statSync(fullPath);
285
- const createdAt = stat.birthtimeMs || stat.ctimeMs;
286
- const modifiedAt = stat.mtimeMs;
287
- // Include files that were created OR modified recently
288
- if (createdAt >= this.startTime || modifiedAt >= this.startTime - 5000) {
289
- results.push({ filePath: fullPath, createdAt, modifiedAt });
290
- }
291
- }
292
- catch {
293
- // Ignore files that disappear between readdir and stat
294
- }
295
- }
296
- }
297
- return results;
298
- }
299
- /**
300
- * Read new lines from the log file and emit events
301
- */
302
- async readNewLines(filePath) {
303
- const position = this.filePositions.get(filePath) || 0;
304
- try {
305
- const stat = fs.statSync(filePath);
306
- if (stat.size <= position) {
307
- return; // No new content
308
- }
309
- // Create read stream starting from last position
310
- const stream = fs.createReadStream(filePath, {
311
- start: position,
312
- encoding: 'utf-8',
313
- });
314
- const rl = readline.createInterface({
315
- input: stream,
316
- crlfDelay: Infinity,
317
- });
318
- let bytesRead = position;
319
- for await (const line of rl) {
320
- bytesRead += Buffer.byteLength(line, 'utf-8') + 1; // +1 for newline
321
- if (!line.trim())
322
- continue;
323
- try {
324
- const entry = JSON.parse(line);
325
- this.processLogEntry(entry);
326
- }
327
- catch (parseError) {
328
- logger_1.logger.warn('Failed to parse log line', {
329
- filePath,
330
- line: line.substring(0, 100),
331
- error: parseError
332
- });
333
- }
334
- }
335
- // Update position
336
- this.filePositions.set(filePath, bytesRead);
337
- }
338
- catch (error) {
339
- logger_1.logger.error('Error reading log file', { filePath, error });
340
- this.emit('error', error);
341
- }
342
- }
343
- /**
344
- * Process a single log entry
345
- */
346
- processLogEntry(entry) {
347
- logger_1.logger.debug('Processing log entry', { type: entry.type });
348
- // Handle session metadata (first entry in file)
349
- if (entry.type === 'session_meta') {
350
- const meta = entry.payload;
351
- this.sessionId = meta.id;
352
- logger_1.logger.info('Codex session started', {
353
- sessionId: meta.id,
354
- cwd: meta.cwd,
355
- cliVersion: meta.cli_version,
356
- });
357
- this.emit('session-started', meta);
358
- return;
359
- }
360
- // Emit all other entries
361
- this.emit('log-entry', entry);
362
- // Also emit specific events for convenience
363
- if (entry.type === 'event_msg' && entry.payload?.type) {
364
- this.emit(`event:${entry.payload.type}`, entry);
365
- }
366
- else if (entry.type === 'response_item' && entry.payload?.type) {
367
- this.emit(`response:${entry.payload.type}`, entry);
368
- }
369
- }
370
- }
371
- exports.SessionLogWatcher = SessionLogWatcher;
372
- //# sourceMappingURL=session-log-watcher.js.map