@vibescore/tracker 0.0.4 → 0.0.5
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 +20 -0
- package/README.zh-CN.md +21 -1
- package/package.json +1 -1
- package/src/cli.js +1 -1
- package/src/commands/status.js +9 -0
- package/src/commands/sync.js +150 -6
- package/src/lib/browser-auth.js +1 -1
- package/src/lib/diagnostics.js +15 -2
package/README.md
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
**QUANTIFY YOUR AI OUTPUT**
|
|
6
6
|
_Real-time AI Analytics for Codex CLI_
|
|
7
7
|
|
|
8
|
+
[**www.vibescore.space**](https://www.vibescore.space)
|
|
9
|
+
|
|
8
10
|
[](https://opensource.org/licenses/MIT)
|
|
9
11
|
[](https://nodejs.org/)
|
|
10
12
|
[](https://www.apple.com/macos/)
|
|
@@ -50,6 +52,24 @@ npx --yes @vibescore/tracker sync
|
|
|
50
52
|
npx --yes @vibescore/tracker status
|
|
51
53
|
```
|
|
52
54
|
|
|
55
|
+
## 🧰 Troubleshooting
|
|
56
|
+
|
|
57
|
+
### Streak shows 0 days while totals look correct
|
|
58
|
+
|
|
59
|
+
- Streak is defined as consecutive days ending today. If today's total is 0, streak will be 0.
|
|
60
|
+
- If you expect a non-zero streak, clear cached auth/heatmap data and sign in again:
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
localStorage.removeItem('vibescore.dashboard.auth.v1');
|
|
64
|
+
Object.keys(localStorage)
|
|
65
|
+
.filter((k) => k.startsWith('vibescore.heatmap.'))
|
|
66
|
+
.forEach((k) => localStorage.removeItem(k));
|
|
67
|
+
location.reload();
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
- Complete the landing page sign-in flow again after reload.
|
|
71
|
+
- Note: `insforge-auth-token` is not used by the dashboard; use `vibescore.dashboard.auth.v1`.
|
|
72
|
+
|
|
53
73
|
## 🏗️ Architecture
|
|
54
74
|
|
|
55
75
|
```mermaid
|
package/README.zh-CN.md
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
**量化你的 AI 产出**
|
|
6
6
|
_Codex CLI 实时 AI 分析工具_
|
|
7
7
|
|
|
8
|
+
[**www.vibescore.space**](https://www.vibescore.space)
|
|
9
|
+
|
|
8
10
|
[](https://opensource.org/licenses/MIT)
|
|
9
11
|
[](https://nodejs.org/)
|
|
10
12
|
[](https://www.apple.com/macos/)
|
|
@@ -50,6 +52,24 @@ npx --yes @vibescore/tracker sync
|
|
|
50
52
|
npx --yes @vibescore/tracker status
|
|
51
53
|
```
|
|
52
54
|
|
|
55
|
+
## 🧰 常见问题
|
|
56
|
+
|
|
57
|
+
### Streak 显示 0 天但总量正常
|
|
58
|
+
|
|
59
|
+
- Streak 的口径是“从今天开始连续使用的天数”,如果今天的 total 为 0,streak 就是 0。
|
|
60
|
+
- 如果你确认应该有 streak,请清理本地缓存并重新登录:
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
localStorage.removeItem('vibescore.dashboard.auth.v1');
|
|
64
|
+
Object.keys(localStorage)
|
|
65
|
+
.filter((k) => k.startsWith('vibescore.heatmap.'))
|
|
66
|
+
.forEach((k) => localStorage.removeItem(k));
|
|
67
|
+
location.reload();
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
- 刷新后重新走一遍 landing page 的登录流程。
|
|
71
|
+
- 说明:Dashboard 不使用 `insforge-auth-token`,实际存储在 `vibescore.dashboard.auth.v1`。
|
|
72
|
+
|
|
53
73
|
## 🏗️ 系统架构
|
|
54
74
|
|
|
55
75
|
```mermaid
|
|
@@ -92,6 +112,6 @@ npm run smoke
|
|
|
92
112
|
---
|
|
93
113
|
|
|
94
114
|
<div align="center">
|
|
95
|
-
<b>System_Ready //
|
|
115
|
+
<b>System_Ready // 2024 VibeScore OS</b><br/>
|
|
96
116
|
<i>"More Tokens. More Vibe."</i>
|
|
97
117
|
</div>
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -48,7 +48,7 @@ function printHelp() {
|
|
|
48
48
|
'',
|
|
49
49
|
'Notes:',
|
|
50
50
|
' - init installs a Codex notify hook and issues a device token (default: browser sign in/up).',
|
|
51
|
-
' - optional: set VIBESCORE_DASHBOARD_URL (or --dashboard-url) to use a hosted
|
|
51
|
+
' - optional: set VIBESCORE_DASHBOARD_URL (or --dashboard-url) to use a hosted landing page.',
|
|
52
52
|
' - sync parses ~/.codex/sessions/**/rollout-*.jsonl and uploads token_count deltas.',
|
|
53
53
|
' - --debug prints original backend errors when they are normalized.',
|
|
54
54
|
''
|
package/src/commands/status.js
CHANGED
|
@@ -24,6 +24,7 @@ async function cmdStatus(argv = []) {
|
|
|
24
24
|
const notifySignalPath = path.join(trackerDir, 'notify.signal');
|
|
25
25
|
const throttlePath = path.join(trackerDir, 'sync.throttle');
|
|
26
26
|
const uploadThrottlePath = path.join(trackerDir, 'upload.throttle.json');
|
|
27
|
+
const autoRetryPath = path.join(trackerDir, 'auto.retry.json');
|
|
27
28
|
const codexHome = process.env.CODEX_HOME || path.join(home, '.codex');
|
|
28
29
|
const codexConfigPath = path.join(codexHome, 'config.toml');
|
|
29
30
|
|
|
@@ -31,6 +32,7 @@ async function cmdStatus(argv = []) {
|
|
|
31
32
|
const cursors = await readJson(cursorsPath);
|
|
32
33
|
const queueState = (await readJson(queueStatePath)) || { offset: 0 };
|
|
33
34
|
const uploadThrottle = normalizeUploadState(await readJson(uploadThrottlePath));
|
|
35
|
+
const autoRetry = await readJson(autoRetryPath);
|
|
34
36
|
|
|
35
37
|
const queueSize = await safeStatSize(queuePath);
|
|
36
38
|
const pendingBytes = Math.max(0, queueSize - (queueState.offset || 0));
|
|
@@ -51,6 +53,12 @@ async function cmdStatus(argv = []) {
|
|
|
51
53
|
const lastUploadError = uploadThrottle.lastError
|
|
52
54
|
? `${uploadThrottle.lastErrorAt || 'unknown'} ${uploadThrottle.lastError}`
|
|
53
55
|
: null;
|
|
56
|
+
const autoRetryAt = parseEpochMsToIso(autoRetry?.retryAtMs || null);
|
|
57
|
+
const autoRetryLine = autoRetryAt
|
|
58
|
+
? `- Auto retry after: ${autoRetryAt} (${autoRetry?.reason || 'scheduled'}, pending ${Number(
|
|
59
|
+
autoRetry?.pendingBytes || 0
|
|
60
|
+
)} bytes)`
|
|
61
|
+
: null;
|
|
54
62
|
|
|
55
63
|
process.stdout.write(
|
|
56
64
|
[
|
|
@@ -65,6 +73,7 @@ async function cmdStatus(argv = []) {
|
|
|
65
73
|
`- Next upload after: ${nextUpload || 'never'}`,
|
|
66
74
|
`- Backoff until: ${backoffUntil || 'never'}`,
|
|
67
75
|
lastUploadError ? `- Last upload error: ${lastUploadError}` : null,
|
|
76
|
+
autoRetryLine,
|
|
68
77
|
`- Codex notify: ${notifyConfigured ? JSON.stringify(codexNotify) : 'unset'}`,
|
|
69
78
|
''
|
|
70
79
|
]
|
package/src/commands/sync.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const os = require('node:os');
|
|
2
2
|
const path = require('node:path');
|
|
3
3
|
const fs = require('node:fs/promises');
|
|
4
|
+
const cp = require('node:child_process');
|
|
4
5
|
|
|
5
6
|
const { ensureDir, readJson, writeJson, openLock } = require('../lib/fs');
|
|
6
7
|
const { listRolloutFiles, parseRolloutIncremental } = require('../lib/rollout');
|
|
@@ -39,6 +40,7 @@ async function cmdSync(argv) {
|
|
|
39
40
|
const config = await readJson(configPath);
|
|
40
41
|
const cursors = (await readJson(cursorsPath)) || { version: 1, files: {}, updatedAt: null };
|
|
41
42
|
const uploadThrottle = normalizeUploadState(await readJson(uploadThrottlePath));
|
|
43
|
+
let uploadThrottleState = uploadThrottle;
|
|
42
44
|
|
|
43
45
|
const codexHome = process.env.CODEX_HOME || path.join(home, '.codex');
|
|
44
46
|
const sessionsDir = path.join(codexHome, 'sessions');
|
|
@@ -72,6 +74,7 @@ async function cmdSync(argv) {
|
|
|
72
74
|
const baseUrl = config?.baseUrl || process.env.VIBESCORE_INSFORGE_BASE_URL || 'https://5tmappuk.us-east.insforge.app';
|
|
73
75
|
|
|
74
76
|
let uploadResult = null;
|
|
77
|
+
let uploadAttempted = false;
|
|
75
78
|
if (deviceToken) {
|
|
76
79
|
const beforeState = (await readJson(queueStatePath)) || { offset: 0 };
|
|
77
80
|
const queueSize = await safeStatSize(queuePath);
|
|
@@ -79,16 +82,27 @@ async function cmdSync(argv) {
|
|
|
79
82
|
let maxBatches = opts.auto ? 3 : opts.drain ? 10_000 : 10;
|
|
80
83
|
let batchSize = UPLOAD_DEFAULTS.batchSize;
|
|
81
84
|
let allowUpload = pendingBytes > 0;
|
|
85
|
+
let autoDecision = null;
|
|
82
86
|
|
|
83
87
|
if (opts.auto) {
|
|
84
|
-
|
|
88
|
+
autoDecision = decideAutoUpload({
|
|
85
89
|
nowMs: Date.now(),
|
|
86
90
|
pendingBytes,
|
|
87
91
|
state: uploadThrottle
|
|
88
92
|
});
|
|
89
|
-
allowUpload = allowUpload &&
|
|
90
|
-
maxBatches =
|
|
91
|
-
batchSize =
|
|
93
|
+
allowUpload = allowUpload && autoDecision.allowed;
|
|
94
|
+
maxBatches = autoDecision.allowed ? autoDecision.maxBatches : 0;
|
|
95
|
+
batchSize = autoDecision.batchSize;
|
|
96
|
+
if (!autoDecision.allowed && pendingBytes > 0 && autoDecision.blockedUntilMs > 0) {
|
|
97
|
+
const reason = deriveAutoSkipReason({ decision: autoDecision, state: uploadThrottle });
|
|
98
|
+
await scheduleAutoRetry({
|
|
99
|
+
trackerDir,
|
|
100
|
+
retryAtMs: autoDecision.blockedUntilMs,
|
|
101
|
+
reason,
|
|
102
|
+
pendingBytes,
|
|
103
|
+
source: 'auto-throttled'
|
|
104
|
+
});
|
|
105
|
+
}
|
|
92
106
|
}
|
|
93
107
|
|
|
94
108
|
if (progress?.enabled && pendingBytes > 0 && allowUpload) {
|
|
@@ -99,6 +113,7 @@ async function cmdSync(argv) {
|
|
|
99
113
|
}
|
|
100
114
|
|
|
101
115
|
if (allowUpload && maxBatches > 0) {
|
|
116
|
+
uploadAttempted = true;
|
|
102
117
|
try {
|
|
103
118
|
uploadResult = await drainQueueToCloud({
|
|
104
119
|
baseUrl,
|
|
@@ -118,12 +133,26 @@ async function cmdSync(argv) {
|
|
|
118
133
|
}
|
|
119
134
|
});
|
|
120
135
|
if (uploadResult.attempted > 0) {
|
|
121
|
-
const next = recordUploadSuccess({ nowMs: Date.now(), state:
|
|
136
|
+
const next = recordUploadSuccess({ nowMs: Date.now(), state: uploadThrottleState });
|
|
137
|
+
uploadThrottleState = next;
|
|
122
138
|
await writeJson(uploadThrottlePath, next);
|
|
123
139
|
}
|
|
124
140
|
} catch (e) {
|
|
125
|
-
const next = recordUploadFailure({ nowMs: Date.now(), state:
|
|
141
|
+
const next = recordUploadFailure({ nowMs: Date.now(), state: uploadThrottleState, error: e });
|
|
142
|
+
uploadThrottleState = next;
|
|
126
143
|
await writeJson(uploadThrottlePath, next);
|
|
144
|
+
if (opts.auto && pendingBytes > 0) {
|
|
145
|
+
const retryAtMs = Math.max(next.nextAllowedAtMs || 0, next.backoffUntilMs || 0);
|
|
146
|
+
if (retryAtMs > 0) {
|
|
147
|
+
await scheduleAutoRetry({
|
|
148
|
+
trackerDir,
|
|
149
|
+
retryAtMs,
|
|
150
|
+
reason: 'backoff',
|
|
151
|
+
pendingBytes,
|
|
152
|
+
source: 'auto-error'
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
127
156
|
throw e;
|
|
128
157
|
}
|
|
129
158
|
} else {
|
|
@@ -137,6 +166,21 @@ async function cmdSync(argv) {
|
|
|
137
166
|
const queueSize = await safeStatSize(queuePath);
|
|
138
167
|
const pendingBytes = Math.max(0, queueSize - Number(afterState.offset || 0));
|
|
139
168
|
|
|
169
|
+
if (pendingBytes <= 0) {
|
|
170
|
+
await clearAutoRetry(trackerDir);
|
|
171
|
+
} else if (opts.auto && uploadAttempted) {
|
|
172
|
+
const retryAtMs = Number(uploadThrottleState?.nextAllowedAtMs || 0);
|
|
173
|
+
if (retryAtMs > Date.now()) {
|
|
174
|
+
await scheduleAutoRetry({
|
|
175
|
+
trackerDir,
|
|
176
|
+
retryAtMs,
|
|
177
|
+
reason: 'backlog',
|
|
178
|
+
pendingBytes,
|
|
179
|
+
source: 'auto-backlog'
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
140
184
|
await maybeSendHeartbeat({
|
|
141
185
|
baseUrl,
|
|
142
186
|
deviceToken,
|
|
@@ -174,12 +218,14 @@ function parseArgs(argv) {
|
|
|
174
218
|
const out = {
|
|
175
219
|
auto: false,
|
|
176
220
|
fromNotify: false,
|
|
221
|
+
fromRetry: false,
|
|
177
222
|
drain: false
|
|
178
223
|
};
|
|
179
224
|
for (let i = 0; i < argv.length; i++) {
|
|
180
225
|
const a = argv[i];
|
|
181
226
|
if (a === '--auto') out.auto = true;
|
|
182
227
|
else if (a === '--from-notify') out.fromNotify = true;
|
|
228
|
+
else if (a === '--from-retry') out.fromRetry = true;
|
|
183
229
|
else if (a === '--drain') out.drain = true;
|
|
184
230
|
else throw new Error(`Unknown option: ${a}`);
|
|
185
231
|
}
|
|
@@ -219,5 +265,103 @@ async function maybeSendHeartbeat({ baseUrl, deviceToken, trackerDir, uploadResu
|
|
|
219
265
|
}
|
|
220
266
|
}
|
|
221
267
|
|
|
268
|
+
function deriveAutoSkipReason({ decision, state }) {
|
|
269
|
+
if (!decision || decision.reason !== 'throttled') return decision?.reason || 'unknown';
|
|
270
|
+
const backoffUntilMs = Number(state?.backoffUntilMs || 0);
|
|
271
|
+
const nextAllowedAtMs = Number(state?.nextAllowedAtMs || 0);
|
|
272
|
+
if (backoffUntilMs > 0 && backoffUntilMs >= nextAllowedAtMs) return 'backoff';
|
|
273
|
+
return 'throttled';
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
async function scheduleAutoRetry({ trackerDir, retryAtMs, reason, pendingBytes, source }) {
|
|
277
|
+
const retryMs = coerceRetryMs(retryAtMs);
|
|
278
|
+
if (!retryMs) return { scheduled: false, retryAtMs: 0 };
|
|
279
|
+
|
|
280
|
+
const retryPath = path.join(trackerDir, AUTO_RETRY_FILENAME);
|
|
281
|
+
const nowMs = Date.now();
|
|
282
|
+
const existing = await readJson(retryPath);
|
|
283
|
+
const existingMs = coerceRetryMs(existing?.retryAtMs);
|
|
284
|
+
if (existingMs && existingMs >= retryMs - 1000) {
|
|
285
|
+
return { scheduled: false, retryAtMs: existingMs };
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const payload = {
|
|
289
|
+
version: 1,
|
|
290
|
+
retryAtMs: retryMs,
|
|
291
|
+
retryAt: new Date(retryMs).toISOString(),
|
|
292
|
+
reason: typeof reason === 'string' && reason.length > 0 ? reason : 'throttled',
|
|
293
|
+
pendingBytes: Math.max(0, Number(pendingBytes || 0)),
|
|
294
|
+
scheduledAt: new Date(nowMs).toISOString(),
|
|
295
|
+
source: typeof source === 'string' ? source : 'auto'
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
await writeJson(retryPath, payload);
|
|
299
|
+
|
|
300
|
+
const delayMs = Math.min(AUTO_RETRY_MAX_DELAY_MS, Math.max(0, retryMs - nowMs));
|
|
301
|
+
if (delayMs <= 0) return { scheduled: false, retryAtMs: retryMs };
|
|
302
|
+
if (process.env.VIBESCORE_AUTO_RETRY_NO_SPAWN === '1') {
|
|
303
|
+
return { scheduled: false, retryAtMs: retryMs };
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
spawnAutoRetryProcess({
|
|
307
|
+
retryPath,
|
|
308
|
+
trackerBinPath: path.join(trackerDir, 'app', 'bin', 'tracker.js'),
|
|
309
|
+
fallbackPkg: '@vibescore/tracker',
|
|
310
|
+
delayMs
|
|
311
|
+
});
|
|
312
|
+
return { scheduled: true, retryAtMs: retryMs };
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
async function clearAutoRetry(trackerDir) {
|
|
316
|
+
const retryPath = path.join(trackerDir, AUTO_RETRY_FILENAME);
|
|
317
|
+
await fs.unlink(retryPath).catch(() => {});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function spawnAutoRetryProcess({ retryPath, trackerBinPath, fallbackPkg, delayMs }) {
|
|
321
|
+
const script = buildAutoRetryScript({ retryPath, trackerBinPath, fallbackPkg, delayMs });
|
|
322
|
+
try {
|
|
323
|
+
const child = cp.spawn(process.execPath, ['-e', script], {
|
|
324
|
+
detached: true,
|
|
325
|
+
stdio: 'ignore',
|
|
326
|
+
env: process.env
|
|
327
|
+
});
|
|
328
|
+
child.unref();
|
|
329
|
+
} catch (_e) {}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function buildAutoRetryScript({ retryPath, trackerBinPath, fallbackPkg, delayMs }) {
|
|
333
|
+
return `'use strict';\n` +
|
|
334
|
+
`const fs = require('node:fs');\n` +
|
|
335
|
+
`const cp = require('node:child_process');\n` +
|
|
336
|
+
`const retryPath = ${JSON.stringify(retryPath)};\n` +
|
|
337
|
+
`const trackerBinPath = ${JSON.stringify(trackerBinPath)};\n` +
|
|
338
|
+
`const fallbackPkg = ${JSON.stringify(fallbackPkg)};\n` +
|
|
339
|
+
`const delayMs = ${Math.max(0, Math.floor(delayMs || 0))};\n` +
|
|
340
|
+
`setTimeout(() => {\n` +
|
|
341
|
+
` let retryAtMs = 0;\n` +
|
|
342
|
+
` try {\n` +
|
|
343
|
+
` const raw = fs.readFileSync(retryPath, 'utf8');\n` +
|
|
344
|
+
` retryAtMs = Number(JSON.parse(raw).retryAtMs || 0);\n` +
|
|
345
|
+
` } catch (_) {}\n` +
|
|
346
|
+
` if (!retryAtMs || Date.now() + 1000 < retryAtMs) process.exit(0);\n` +
|
|
347
|
+
` const argv = ['sync', '--auto', '--from-retry'];\n` +
|
|
348
|
+
` const cmd = fs.existsSync(trackerBinPath)\n` +
|
|
349
|
+
` ? [process.execPath, trackerBinPath, ...argv]\n` +
|
|
350
|
+
` : ['npx', '--yes', fallbackPkg, ...argv];\n` +
|
|
351
|
+
` try {\n` +
|
|
352
|
+
` const child = cp.spawn(cmd[0], cmd.slice(1), { detached: true, stdio: 'ignore', env: process.env });\n` +
|
|
353
|
+
` child.unref();\n` +
|
|
354
|
+
` } catch (_) {}\n` +
|
|
355
|
+
`}, delayMs);\n`;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function coerceRetryMs(v) {
|
|
359
|
+
const n = Number(v);
|
|
360
|
+
if (!Number.isFinite(n) || n <= 0) return 0;
|
|
361
|
+
return Math.floor(n);
|
|
362
|
+
}
|
|
363
|
+
|
|
222
364
|
const HEARTBEAT_MIN_INTERVAL_MINUTES = 30;
|
|
223
365
|
const HEARTBEAT_MIN_INTERVAL_MS = HEARTBEAT_MIN_INTERVAL_MINUTES * 60 * 1000;
|
|
366
|
+
const AUTO_RETRY_FILENAME = 'auto.retry.json';
|
|
367
|
+
const AUTO_RETRY_MAX_DELAY_MS = 2 * 60 * 60 * 1000;
|
package/src/lib/browser-auth.js
CHANGED
|
@@ -10,7 +10,7 @@ async function beginBrowserAuth({ baseUrl, dashboardUrl, timeoutMs, open }) {
|
|
|
10
10
|
|
|
11
11
|
const { callbackUrl, waitForCallback } = await startLocalCallbackServer({ callbackPath, timeoutMs });
|
|
12
12
|
|
|
13
|
-
const authUrl = dashboardUrl ? new URL('/
|
|
13
|
+
const authUrl = dashboardUrl ? new URL('/', dashboardUrl) : new URL('/auth/sign-up', baseUrl);
|
|
14
14
|
authUrl.searchParams.set('redirect', callbackUrl);
|
|
15
15
|
if (dashboardUrl && baseUrl && baseUrl !== DEFAULT_BASE_URL) authUrl.searchParams.set('base_url', baseUrl);
|
|
16
16
|
|
package/src/lib/diagnostics.js
CHANGED
|
@@ -18,12 +18,14 @@ async function collectTrackerDiagnostics({
|
|
|
18
18
|
const notifySignalPath = path.join(trackerDir, 'notify.signal');
|
|
19
19
|
const throttlePath = path.join(trackerDir, 'sync.throttle');
|
|
20
20
|
const uploadThrottlePath = path.join(trackerDir, 'upload.throttle.json');
|
|
21
|
+
const autoRetryPath = path.join(trackerDir, 'auto.retry.json');
|
|
21
22
|
const codexConfigPath = path.join(codexHome, 'config.toml');
|
|
22
23
|
|
|
23
24
|
const config = await readJson(configPath);
|
|
24
25
|
const cursors = await readJson(cursorsPath);
|
|
25
26
|
const queueState = (await readJson(queueStatePath)) || { offset: 0 };
|
|
26
27
|
const uploadThrottle = normalizeUploadState(await readJson(uploadThrottlePath));
|
|
28
|
+
const autoRetry = await readJson(autoRetryPath);
|
|
27
29
|
|
|
28
30
|
const queueSize = await safeStatSize(queuePath);
|
|
29
31
|
const offsetBytes = Number(queueState.offset || 0);
|
|
@@ -37,6 +39,7 @@ async function collectTrackerDiagnostics({
|
|
|
37
39
|
const codexNotify = notifyConfigured ? codexNotifyRaw.map((v) => redactValue(v, home)) : null;
|
|
38
40
|
|
|
39
41
|
const lastSuccessAt = uploadThrottle.lastSuccessMs ? new Date(uploadThrottle.lastSuccessMs).toISOString() : null;
|
|
42
|
+
const autoRetryAt = parseEpochMsToIso(autoRetry?.retryAtMs);
|
|
40
43
|
|
|
41
44
|
return {
|
|
42
45
|
ok: true,
|
|
@@ -84,7 +87,18 @@ async function collectTrackerDiagnostics({
|
|
|
84
87
|
message: redactError(String(uploadThrottle.lastError), home)
|
|
85
88
|
}
|
|
86
89
|
: null
|
|
87
|
-
}
|
|
90
|
+
},
|
|
91
|
+
auto_retry: autoRetryAt
|
|
92
|
+
? {
|
|
93
|
+
next_retry_at: autoRetryAt,
|
|
94
|
+
reason: typeof autoRetry?.reason === 'string' ? autoRetry.reason : null,
|
|
95
|
+
pending_bytes: Number.isFinite(Number(autoRetry?.pendingBytes))
|
|
96
|
+
? Math.max(0, Number(autoRetry.pendingBytes))
|
|
97
|
+
: null,
|
|
98
|
+
scheduled_at: typeof autoRetry?.scheduledAt === 'string' ? autoRetry.scheduledAt : null,
|
|
99
|
+
source: typeof autoRetry?.source === 'string' ? autoRetry.source : null
|
|
100
|
+
}
|
|
101
|
+
: null
|
|
88
102
|
};
|
|
89
103
|
}
|
|
90
104
|
|
|
@@ -135,4 +149,3 @@ function parseEpochMsToIso(v) {
|
|
|
135
149
|
}
|
|
136
150
|
|
|
137
151
|
module.exports = { collectTrackerDiagnostics };
|
|
138
|
-
|