gitdone-agent 0.8.0 → 0.8.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/index.js +107 -25
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
// from ~/.gitdone-agent/config.json, so the autostart entry needs no args.
|
|
11
11
|
|
|
12
12
|
import { execSync, execFileSync, spawn } from 'node:child_process'
|
|
13
|
+
import readline from 'node:readline'
|
|
13
14
|
import {
|
|
14
15
|
existsSync, writeFileSync, readFileSync, unlinkSync, renameSync,
|
|
15
16
|
mkdirSync, copyFileSync, appendFileSync, readdirSync, rmSync, statSync,
|
|
@@ -28,7 +29,7 @@ import { randomUUID, createHash } from 'node:crypto'
|
|
|
28
29
|
// Reported to the server on every sync so the web UI can flag outdated agents.
|
|
29
30
|
// Keep in lockstep with packages/agent/package.json "version" AND
|
|
30
31
|
// src/lib/agentVersion.ts LATEST_AGENT_VERSION.
|
|
31
|
-
const AGENT_VERSION = '0.8.
|
|
32
|
+
const AGENT_VERSION = '0.8.1'
|
|
32
33
|
|
|
33
34
|
const AGENT_DIR = join(homedir(), '.gitdone-agent')
|
|
34
35
|
const CONFIG_PATH = join(AGENT_DIR, 'config.json')
|
|
@@ -2388,19 +2389,14 @@ async function readClaudeUsage() {
|
|
|
2388
2389
|
}
|
|
2389
2390
|
|
|
2390
2391
|
// ─── Codex (ChatGPT) plan usage (gd-492) ───────────────────────────────────
|
|
2391
|
-
// Codex
|
|
2392
|
-
//
|
|
2393
|
-
//
|
|
2394
|
-
//
|
|
2395
|
-
// where `token_count` lines carry:
|
|
2396
|
-
// payload.rate_limits = { primary: { used_percent, window_minutes, resets_at },
|
|
2397
|
-
// secondary: …|null, plan_type, credits, … }
|
|
2398
|
-
// So we read the newest rollout and take its LAST rate_limits record.
|
|
2392
|
+
// Ask Codex itself for the ChatGPT account rate limits, instead of guessing from
|
|
2393
|
+
// a previous run's transcript. The app-server API is the same local surface the
|
|
2394
|
+
// clients use for account/rateLimits/read; it returns primary/secondary windows
|
|
2395
|
+
// when the account has both a short and a weekly/general Codex bucket.
|
|
2399
2396
|
//
|
|
2400
|
-
//
|
|
2401
|
-
//
|
|
2402
|
-
//
|
|
2403
|
-
// then say how old they are rather than implying they're live.
|
|
2397
|
+
// If that live read fails, fall back to the last rollout file so old Codex
|
|
2398
|
+
// installs still show something. Rollout values are telemetry only and may not
|
|
2399
|
+
// include every limit the CLI enforces.
|
|
2404
2400
|
|
|
2405
2401
|
// Newest entry in a dir, by name (the sessions tree is zero-padded YYYY/MM/DD,
|
|
2406
2402
|
// so the lexicographic max IS the newest) or by mtime for the files themselves.
|
|
@@ -2435,8 +2431,20 @@ function readTail(file, bytes) {
|
|
|
2435
2431
|
return start > 0 ? text.slice(text.indexOf('\n') + 1) : text
|
|
2436
2432
|
}
|
|
2437
2433
|
|
|
2438
|
-
|
|
2439
|
-
|
|
2434
|
+
function codexAppWindow(w) {
|
|
2435
|
+
if (!w || typeof w !== 'object') return null
|
|
2436
|
+
const pct = Number(w.usedPercent)
|
|
2437
|
+
if (!Number.isFinite(pct)) return null
|
|
2438
|
+
const resets = Number(w.resetsAt)
|
|
2439
|
+
return {
|
|
2440
|
+
pct: Math.round(pct),
|
|
2441
|
+
windowMin: Number.isFinite(Number(w.windowDurationMins)) ? Math.round(Number(w.windowDurationMins)) : null,
|
|
2442
|
+
resetsAt: Number.isFinite(resets) && resets > 0 ? new Date(resets * 1000).toISOString() : null,
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
2445
|
+
|
|
2446
|
+
// One rollout window of a Codex rate limit → the shape the server stores.
|
|
2447
|
+
function codexRolloutWindow(w) {
|
|
2440
2448
|
if (!w || typeof w !== 'object') return null
|
|
2441
2449
|
const pct = Number(w.used_percent)
|
|
2442
2450
|
if (!Number.isFinite(pct)) return null
|
|
@@ -2449,10 +2457,77 @@ function codexWindow(w) {
|
|
|
2449
2457
|
}
|
|
2450
2458
|
}
|
|
2451
2459
|
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
const
|
|
2455
|
-
|
|
2460
|
+
function codexUsageFromSnapshot(snapshot) {
|
|
2461
|
+
if (!snapshot || typeof snapshot !== 'object') return null
|
|
2462
|
+
const primary = codexAppWindow(snapshot.primary)
|
|
2463
|
+
const secondary = codexAppWindow(snapshot.secondary)
|
|
2464
|
+
if (!primary && !secondary) return null
|
|
2465
|
+
return {
|
|
2466
|
+
primary,
|
|
2467
|
+
secondary,
|
|
2468
|
+
planType: typeof snapshot.planType === 'string' ? snapshot.planType : null,
|
|
2469
|
+
observedAt: new Date().toISOString(),
|
|
2470
|
+
}
|
|
2471
|
+
}
|
|
2472
|
+
|
|
2473
|
+
function readCodexUsageFromAppServer() {
|
|
2474
|
+
return new Promise((resolve) => {
|
|
2475
|
+
const { path: codexPath, shell, found } = findCodex()
|
|
2476
|
+
if (!found) return resolve(null)
|
|
2477
|
+
|
|
2478
|
+
let settled = false
|
|
2479
|
+
let child
|
|
2480
|
+
const finish = (value) => {
|
|
2481
|
+
if (settled) return
|
|
2482
|
+
settled = true
|
|
2483
|
+
clearTimeout(timer)
|
|
2484
|
+
try { child?.kill() } catch { /* best-effort */ }
|
|
2485
|
+
resolve(value)
|
|
2486
|
+
}
|
|
2487
|
+
const timer = setTimeout(() => finish(null), 12_000)
|
|
2488
|
+
|
|
2489
|
+
try {
|
|
2490
|
+
child = spawn(codexPath, ['app-server', '--stdio'], {
|
|
2491
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
2492
|
+
shell,
|
|
2493
|
+
windowsHide: true,
|
|
2494
|
+
})
|
|
2495
|
+
} catch {
|
|
2496
|
+
return finish(null)
|
|
2497
|
+
}
|
|
2498
|
+
|
|
2499
|
+
child.on('error', () => finish(null))
|
|
2500
|
+
child.on('exit', () => finish(null))
|
|
2501
|
+
|
|
2502
|
+
const rl = readline.createInterface({ input: child.stdout })
|
|
2503
|
+
rl.on('line', (line) => {
|
|
2504
|
+
let msg
|
|
2505
|
+
try { msg = JSON.parse(line) } catch { return }
|
|
2506
|
+
if (msg.id !== 2) return
|
|
2507
|
+
if (msg.error) return finish(null)
|
|
2508
|
+
const result = msg.result
|
|
2509
|
+
const byId = result?.rateLimitsByLimitId
|
|
2510
|
+
const snapshot = byId?.codex || result?.rateLimits
|
|
2511
|
+
finish(codexUsageFromSnapshot(snapshot))
|
|
2512
|
+
})
|
|
2513
|
+
|
|
2514
|
+
const send = (message) => {
|
|
2515
|
+
try { child.stdin.write(`${JSON.stringify(message)}\n`) } catch { finish(null) }
|
|
2516
|
+
}
|
|
2517
|
+
send({
|
|
2518
|
+
method: 'initialize',
|
|
2519
|
+
id: 1,
|
|
2520
|
+
params: {
|
|
2521
|
+
clientInfo: { name: 'gitdone-agent', title: 'gitDone Agent', version: AGENT_VERSION },
|
|
2522
|
+
capabilities: { experimentalApi: true },
|
|
2523
|
+
},
|
|
2524
|
+
})
|
|
2525
|
+
send({ method: 'initialized', params: {} })
|
|
2526
|
+
send({ method: 'account/rateLimits/read', id: 2 })
|
|
2527
|
+
})
|
|
2528
|
+
}
|
|
2529
|
+
|
|
2530
|
+
function readCodexUsageFromRollout() {
|
|
2456
2531
|
try {
|
|
2457
2532
|
const root = join(homedir(), '.codex', 'sessions')
|
|
2458
2533
|
if (!existsSync(root)) return null
|
|
@@ -2477,18 +2552,16 @@ function readCodexUsage() {
|
|
|
2477
2552
|
try { ev = JSON.parse(line) } catch { continue }
|
|
2478
2553
|
const rl = ev?.payload?.rate_limits
|
|
2479
2554
|
if (!rl || typeof rl !== 'object') continue
|
|
2480
|
-
const primary =
|
|
2481
|
-
const secondary =
|
|
2555
|
+
const primary = codexRolloutWindow(rl.primary)
|
|
2556
|
+
const secondary = codexRolloutWindow(rl.secondary)
|
|
2482
2557
|
if (!primary && !secondary) continue
|
|
2483
|
-
|
|
2558
|
+
return {
|
|
2484
2559
|
primary,
|
|
2485
2560
|
secondary,
|
|
2486
2561
|
planType: typeof rl.plan_type === 'string' ? rl.plan_type : null,
|
|
2487
2562
|
// When these numbers were actually true — the run that produced them.
|
|
2488
2563
|
observedAt: typeof ev.timestamp === 'string' ? ev.timestamp : new Date(statSync(file).mtimeMs).toISOString(),
|
|
2489
2564
|
}
|
|
2490
|
-
codexUsageCache = { at: now, data }
|
|
2491
|
-
return data
|
|
2492
2565
|
}
|
|
2493
2566
|
}
|
|
2494
2567
|
}
|
|
@@ -2498,6 +2571,15 @@ function readCodexUsage() {
|
|
|
2498
2571
|
}
|
|
2499
2572
|
}
|
|
2500
2573
|
|
|
2574
|
+
let codexUsageCache = { at: 0, data: null }
|
|
2575
|
+
async function readCodexUsage() {
|
|
2576
|
+
const now = Date.now()
|
|
2577
|
+
if (codexUsageCache.data && now - codexUsageCache.at < 60_000) return codexUsageCache.data
|
|
2578
|
+
const data = await readCodexUsageFromAppServer() || readCodexUsageFromRollout()
|
|
2579
|
+
if (data) codexUsageCache = { at: now, data }
|
|
2580
|
+
return data
|
|
2581
|
+
}
|
|
2582
|
+
|
|
2501
2583
|
// The discovered repo list barely ever changes, but re-upserting every repo's
|
|
2502
2584
|
// row on the server each tick is pure idle write load at scale. So we ship the
|
|
2503
2585
|
// full `repos` list only when the discovered set actually changed, or every
|
|
@@ -2513,7 +2595,7 @@ async function sync(cfg, discovered) {
|
|
|
2513
2595
|
const usage = await readClaudeUsage()
|
|
2514
2596
|
// gd-492: the ChatGPT side's own limits, so a Codex console shows ITS numbers
|
|
2515
2597
|
// instead of Claude's. Both ride along — one machine can run both engines.
|
|
2516
|
-
const codexUsage = readCodexUsage()
|
|
2598
|
+
const codexUsage = await readCodexUsage()
|
|
2517
2599
|
const sig = createHash('sha1').update(JSON.stringify(discovered)).digest('hex')
|
|
2518
2600
|
const full = sig !== syncRepoSig || syncFullCountdown <= 0
|
|
2519
2601
|
const data = await api(cfg, '/api/v1/agent/sync', {
|