@ghl-ai/aw 0.1.37-beta.80 → 0.1.37-beta.81
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/commands/telemetry.mjs +112 -0
- package/hooks/capabilities/telemetry.mjs +18 -6
- package/package.json +1 -1
package/commands/telemetry.mjs
CHANGED
|
@@ -15,10 +15,17 @@ import { join } from 'node:path';
|
|
|
15
15
|
import { homedir } from 'node:os';
|
|
16
16
|
import { AW_TELEMETRY_URL, TOKEN_PRICING } from '../constants.mjs';
|
|
17
17
|
import { enableTelemetry, disableTelemetry, getStatus } from '../telemetry.mjs';
|
|
18
|
+
import {
|
|
19
|
+
extractFromCodexSession,
|
|
20
|
+
handleSessionEnd,
|
|
21
|
+
handleSessionStart,
|
|
22
|
+
handleStop,
|
|
23
|
+
} from '../hooks/capabilities/telemetry.mjs';
|
|
18
24
|
import * as fmt from '../fmt.mjs';
|
|
19
25
|
import { chalk } from '../fmt.mjs';
|
|
20
26
|
|
|
21
27
|
const TIMEOUT_MS = 5000;
|
|
28
|
+
const CODEX_SCAN_STATE_PATH = join(homedir(), '.aw', 'telemetry-codex-scan.json');
|
|
22
29
|
|
|
23
30
|
function resolveGithubLogin() {
|
|
24
31
|
const tokenPath = join(homedir(), '.aw_registry', '.token');
|
|
@@ -213,6 +220,8 @@ async function pushCommand(args) {
|
|
|
213
220
|
// ── telemetry flush ──────────────────────────────────────────────────────────
|
|
214
221
|
|
|
215
222
|
async function flushCommand(_args) {
|
|
223
|
+
await flushCodexSessionsForCwd(process.cwd());
|
|
224
|
+
|
|
216
225
|
const cwd = process.cwd();
|
|
217
226
|
const slug = cwd.replace(/\//g, '-');
|
|
218
227
|
const sessionDir = join(homedir(), '.claude', 'projects', slug);
|
|
@@ -333,6 +342,109 @@ async function flushCommand(_args) {
|
|
|
333
342
|
}
|
|
334
343
|
}
|
|
335
344
|
|
|
345
|
+
function readCodexScanState() {
|
|
346
|
+
if (!existsSync(CODEX_SCAN_STATE_PATH)) return {};
|
|
347
|
+
try { return JSON.parse(readFileSync(CODEX_SCAN_STATE_PATH, 'utf8')); } catch { return {}; }
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function writeCodexScanState(state) {
|
|
351
|
+
writeFileSync(CODEX_SCAN_STATE_PATH, JSON.stringify(state, null, 2) + '\n', 'utf8');
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function listCodexTranscriptFiles(root) {
|
|
355
|
+
if (!existsSync(root)) return [];
|
|
356
|
+
const stack = [root];
|
|
357
|
+
const files = [];
|
|
358
|
+
|
|
359
|
+
while (stack.length > 0) {
|
|
360
|
+
const dir = stack.pop();
|
|
361
|
+
let entries = [];
|
|
362
|
+
try {
|
|
363
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
364
|
+
} catch {
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
for (const entry of entries) {
|
|
369
|
+
const fullPath = join(dir, entry.name);
|
|
370
|
+
if (entry.isDirectory()) {
|
|
371
|
+
stack.push(fullPath);
|
|
372
|
+
} else if (entry.isFile() && entry.name.endsWith('.jsonl')) {
|
|
373
|
+
files.push(fullPath);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return files;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
async function flushCodexSessionsForCwd(cwd) {
|
|
382
|
+
const sessionsRoot = join(homedir(), '.codex', 'sessions');
|
|
383
|
+
const files = listCodexTranscriptFiles(sessionsRoot);
|
|
384
|
+
if (files.length === 0) return;
|
|
385
|
+
|
|
386
|
+
const state = readCodexScanState();
|
|
387
|
+
let flushed = 0;
|
|
388
|
+
|
|
389
|
+
for (const file of files) {
|
|
390
|
+
const match = file.match(/([0-9a-f]{8}-[0-9a-f-]{27,})\.jsonl$/i);
|
|
391
|
+
const sessionId = match?.[1];
|
|
392
|
+
if (!sessionId) continue;
|
|
393
|
+
|
|
394
|
+
let stat;
|
|
395
|
+
try {
|
|
396
|
+
stat = statSync(file);
|
|
397
|
+
} catch {
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Skip files that may still be actively written.
|
|
402
|
+
if (Date.now() - stat.mtimeMs < 15_000) continue;
|
|
403
|
+
if ((state[sessionId]?.mtimeMs || 0) >= stat.mtimeMs) continue;
|
|
404
|
+
|
|
405
|
+
const transcript = extractFromCodexSession(file);
|
|
406
|
+
if (!transcript || transcript.cwd !== cwd) continue;
|
|
407
|
+
|
|
408
|
+
await handleSessionStart({
|
|
409
|
+
session_id: sessionId,
|
|
410
|
+
platform: 'codex',
|
|
411
|
+
cwd,
|
|
412
|
+
transcript_path: file,
|
|
413
|
+
platform_version: transcript.platformVersion || null,
|
|
414
|
+
model: transcript.model || null,
|
|
415
|
+
start_ts: transcript.firstTimestamp || new Date(stat.mtimeMs).toISOString(),
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
await handleStop({
|
|
419
|
+
session_id: sessionId,
|
|
420
|
+
platform: 'codex',
|
|
421
|
+
cwd,
|
|
422
|
+
transcript_path: file,
|
|
423
|
+
platform_version: transcript.platformVersion || null,
|
|
424
|
+
model: transcript.model || null,
|
|
425
|
+
status: 'completed',
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
await handleSessionEnd({
|
|
429
|
+
session_id: sessionId,
|
|
430
|
+
keep_artifacts: true,
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
state[sessionId] = {
|
|
434
|
+
mtimeMs: stat.mtimeMs,
|
|
435
|
+
transcript_path: file,
|
|
436
|
+
flushed_at: new Date().toISOString(),
|
|
437
|
+
cwd,
|
|
438
|
+
};
|
|
439
|
+
flushed++;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (flushed > 0) {
|
|
443
|
+
writeCodexScanState(state);
|
|
444
|
+
fmt.logSuccess(`Telemetry flushed ✓ (${flushed} Codex session${flushed === 1 ? '' : 's'})`);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
336
448
|
/**
|
|
337
449
|
* Sum token usage from JSONL files matching the given branch and time window.
|
|
338
450
|
* Returns null if no matching data found or session is still active (<60s old).
|
|
@@ -210,7 +210,7 @@ function detectPlatform(input) {
|
|
|
210
210
|
return 'claude-code';
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
-
function findCodexTranscriptPath(sessionId) {
|
|
213
|
+
export function findCodexTranscriptPath(sessionId) {
|
|
214
214
|
if (!sessionId || sessionId === 'unknown') return null;
|
|
215
215
|
|
|
216
216
|
const sessionsRoot = join(HOME, '.codex', 'sessions');
|
|
@@ -475,7 +475,7 @@ function extractFromTranscript(transcriptPath) {
|
|
|
475
475
|
}
|
|
476
476
|
}
|
|
477
477
|
|
|
478
|
-
function extractFromCodexSession(transcriptPath) {
|
|
478
|
+
export function extractFromCodexSession(transcriptPath) {
|
|
479
479
|
if (!transcriptPath || !existsSync(transcriptPath)) return null;
|
|
480
480
|
|
|
481
481
|
try {
|
|
@@ -498,6 +498,8 @@ function extractFromCodexSession(transcriptPath) {
|
|
|
498
498
|
let toolFailed = 0;
|
|
499
499
|
let errorType = null;
|
|
500
500
|
let errorMessage = null;
|
|
501
|
+
let firstTimestamp = null;
|
|
502
|
+
let lastTimestamp = null;
|
|
501
503
|
|
|
502
504
|
for (const line of lines) {
|
|
503
505
|
let entry;
|
|
@@ -507,6 +509,11 @@ function extractFromCodexSession(transcriptPath) {
|
|
|
507
509
|
continue;
|
|
508
510
|
}
|
|
509
511
|
|
|
512
|
+
if (entry.timestamp) {
|
|
513
|
+
if (!firstTimestamp) firstTimestamp = entry.timestamp;
|
|
514
|
+
lastTimestamp = entry.timestamp;
|
|
515
|
+
}
|
|
516
|
+
|
|
510
517
|
if (entry.type === 'session_meta') {
|
|
511
518
|
const meta = entry.payload || {};
|
|
512
519
|
cwd = meta.cwd || cwd;
|
|
@@ -592,6 +599,8 @@ function extractFromCodexSession(transcriptPath) {
|
|
|
592
599
|
prUrls: [...prUrls],
|
|
593
600
|
errorType,
|
|
594
601
|
errorMessage,
|
|
602
|
+
firstTimestamp,
|
|
603
|
+
lastTimestamp,
|
|
595
604
|
};
|
|
596
605
|
} catch {
|
|
597
606
|
return null;
|
|
@@ -848,7 +857,7 @@ export async function handleSessionStart(rawInput) {
|
|
|
848
857
|
platform: detectPlatform(rawInput),
|
|
849
858
|
platform_version: input.platform_version || codexTranscript?.platformVersion || null,
|
|
850
859
|
namespace: resolveNamespace(),
|
|
851
|
-
start_ts: new Date().toISOString(),
|
|
860
|
+
start_ts: input.start_ts || new Date().toISOString(),
|
|
852
861
|
model: input.model || codexTranscript?.model || null,
|
|
853
862
|
cwd,
|
|
854
863
|
entry_id: 0, // monotonic counter for costs.jsonl entries
|
|
@@ -1198,6 +1207,7 @@ export async function handleSessionEnd(rawInput) {
|
|
|
1198
1207
|
const input = normalizeCursorInput(rawInput);
|
|
1199
1208
|
const sessionId = input.session_id || 'unknown';
|
|
1200
1209
|
const session = readSession(sessionId);
|
|
1210
|
+
const keepArtifacts = Boolean(rawInput?.keep_artifacts || rawInput?.keepArtifacts);
|
|
1201
1211
|
if (!session) return;
|
|
1202
1212
|
|
|
1203
1213
|
// Flush offline queue first
|
|
@@ -1284,7 +1294,9 @@ export async function handleSessionEnd(rawInput) {
|
|
|
1284
1294
|
}
|
|
1285
1295
|
|
|
1286
1296
|
// ── Cleanup ──────────────────────────────────────────────────────────────
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1297
|
+
if (!keepArtifacts) {
|
|
1298
|
+
try { if (existsSync(costPath)) unlinkSync(costPath); } catch { /* best effort */ }
|
|
1299
|
+
try { if (existsSync(snapshotPath(sessionId))) unlinkSync(snapshotPath(sessionId)); } catch { /* best effort */ }
|
|
1300
|
+
try { if (existsSync(sessionPath(sessionId))) unlinkSync(sessionPath(sessionId)); } catch { /* best effort */ }
|
|
1301
|
+
}
|
|
1290
1302
|
}
|