@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.
@@ -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
- try { if (existsSync(costPath)) unlinkSync(costPath); } catch { /* best effort */ }
1288
- try { if (existsSync(snapshotPath(sessionId))) unlinkSync(snapshotPath(sessionId)); } catch { /* best effort */ }
1289
- try { if (existsSync(sessionPath(sessionId))) unlinkSync(sessionPath(sessionId)); } catch { /* best effort */ }
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghl-ai/aw",
3
- "version": "0.1.37-beta.80",
3
+ "version": "0.1.37-beta.81",
4
4
  "description": "Agentic Workspace CLI — pull, push & manage agents, skills and commands from the registry",
5
5
  "type": "module",
6
6
  "bin": "bin.js",