vibeusage 0.2.21 → 0.2.23
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 +306 -173
- package/README.old.md +324 -0
- package/README.zh-CN.md +304 -188
- package/package.json +32 -32
- package/src/cli.js +37 -37
- package/src/commands/activate-if-needed.js +41 -0
- package/src/commands/diagnostics.js +8 -9
- package/src/commands/doctor.js +31 -26
- package/src/commands/init.js +285 -218
- package/src/commands/status.js +86 -83
- package/src/commands/sync.js +178 -130
- package/src/commands/uninstall.js +66 -62
- package/src/lib/activation-check.js +341 -0
- package/src/lib/browser-auth.js +52 -54
- package/src/lib/claude-config.js +25 -25
- package/src/lib/cli-ui.js +35 -35
- package/src/lib/codex-config.js +40 -36
- package/src/lib/debug-flags.js +2 -2
- package/src/lib/diagnostics.js +70 -57
- package/src/lib/doctor.js +139 -132
- package/src/lib/fs.js +17 -17
- package/src/lib/gemini-config.js +44 -40
- package/src/lib/init-flow.js +16 -22
- package/src/lib/insforge-client.js +10 -10
- package/src/lib/insforge.js +9 -3
- package/src/lib/openclaw-hook.js +89 -66
- package/src/lib/openclaw-session-plugin.js +116 -92
- package/src/lib/opencode-config.js +31 -32
- package/src/lib/opencode-usage-audit.js +34 -31
- package/src/lib/progress.js +12 -13
- package/src/lib/project-usage-purge.js +23 -17
- package/src/lib/prompt.js +8 -4
- package/src/lib/rollout.js +342 -241
- package/src/lib/runtime-config.js +34 -22
- package/src/lib/subscriptions.js +94 -92
- package/src/lib/tracker-paths.js +6 -6
- package/src/lib/upload-throttle.js +35 -16
- package/src/lib/uploader.js +33 -29
- package/src/lib/vibeusage-api.js +72 -56
- package/src/lib/vibeusage-public-repo.js +41 -24
package/src/commands/sync.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
const os = require(
|
|
2
|
-
const path = require(
|
|
3
|
-
const fs = require(
|
|
4
|
-
const cp = require(
|
|
1
|
+
const os = require("node:os");
|
|
2
|
+
const path = require("node:path");
|
|
3
|
+
const fs = require("node:fs/promises");
|
|
4
|
+
const cp = require("node:child_process");
|
|
5
5
|
|
|
6
|
-
const { ensureDir, readJson, writeJson, openLock } = require(
|
|
6
|
+
const { ensureDir, readJson, writeJson, openLock } = require("../lib/fs");
|
|
7
7
|
const {
|
|
8
8
|
listRolloutFiles,
|
|
9
9
|
listClaudeProjectFiles,
|
|
@@ -13,22 +13,22 @@ const {
|
|
|
13
13
|
parseClaudeIncremental,
|
|
14
14
|
parseGeminiIncremental,
|
|
15
15
|
parseOpencodeIncremental,
|
|
16
|
-
parseOpenclawIncremental
|
|
17
|
-
} = require(
|
|
18
|
-
const { drainQueueToCloud } = require(
|
|
19
|
-
const { collectLocalSubscriptions } = require(
|
|
20
|
-
const { createProgress, renderBar, formatNumber, formatBytes } = require(
|
|
21
|
-
const { syncHeartbeat } = require(
|
|
16
|
+
parseOpenclawIncremental,
|
|
17
|
+
} = require("../lib/rollout");
|
|
18
|
+
const { drainQueueToCloud } = require("../lib/uploader");
|
|
19
|
+
const { collectLocalSubscriptions } = require("../lib/subscriptions");
|
|
20
|
+
const { createProgress, renderBar, formatNumber, formatBytes } = require("../lib/progress");
|
|
21
|
+
const { syncHeartbeat } = require("../lib/vibeusage-api");
|
|
22
22
|
const {
|
|
23
23
|
DEFAULTS: UPLOAD_DEFAULTS,
|
|
24
24
|
normalizeState: normalizeUploadState,
|
|
25
25
|
decideAutoUpload,
|
|
26
26
|
recordUploadSuccess,
|
|
27
|
-
recordUploadFailure
|
|
28
|
-
} = require(
|
|
29
|
-
const { purgeProjectUsage } = require(
|
|
30
|
-
const { resolveTrackerPaths } = require(
|
|
31
|
-
const { resolveRuntimeConfig } = require(
|
|
27
|
+
recordUploadFailure,
|
|
28
|
+
} = require("../lib/upload-throttle");
|
|
29
|
+
const { purgeProjectUsage } = require("../lib/project-usage-purge");
|
|
30
|
+
const { resolveTrackerPaths } = require("../lib/tracker-paths");
|
|
31
|
+
const { resolveRuntimeConfig } = require("../lib/runtime-config");
|
|
32
32
|
|
|
33
33
|
async function cmdSync(argv) {
|
|
34
34
|
const opts = parseArgs(argv);
|
|
@@ -40,42 +40,44 @@ async function cmdSync(argv) {
|
|
|
40
40
|
await writeOpenclawSignal(trackerDir);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
const lockPath = path.join(trackerDir,
|
|
43
|
+
const lockPath = path.join(trackerDir, "sync.lock");
|
|
44
44
|
const lock = await openLock(lockPath, { quietIfLocked: opts.auto });
|
|
45
45
|
if (!lock) return;
|
|
46
46
|
|
|
47
47
|
let progress = null;
|
|
48
48
|
try {
|
|
49
49
|
progress = !opts.auto ? createProgress({ stream: process.stdout }) : null;
|
|
50
|
-
const configPath = path.join(trackerDir,
|
|
51
|
-
const cursorsPath = path.join(trackerDir,
|
|
52
|
-
const queuePath = path.join(trackerDir,
|
|
53
|
-
const queueStatePath = path.join(trackerDir,
|
|
54
|
-
const projectQueuePath = path.join(trackerDir,
|
|
55
|
-
const projectQueueStatePath = path.join(trackerDir,
|
|
56
|
-
const uploadThrottlePath = path.join(trackerDir,
|
|
50
|
+
const configPath = path.join(trackerDir, "config.json");
|
|
51
|
+
const cursorsPath = path.join(trackerDir, "cursors.json");
|
|
52
|
+
const queuePath = path.join(trackerDir, "queue.jsonl");
|
|
53
|
+
const queueStatePath = path.join(trackerDir, "queue.state.json");
|
|
54
|
+
const projectQueuePath = path.join(trackerDir, "project.queue.jsonl");
|
|
55
|
+
const projectQueueStatePath = path.join(trackerDir, "project.queue.state.json");
|
|
56
|
+
const uploadThrottlePath = path.join(trackerDir, "upload.throttle.json");
|
|
57
57
|
|
|
58
58
|
const config = await readJson(configPath);
|
|
59
59
|
const cursors = (await readJson(cursorsPath)) || { version: 1, files: {}, updatedAt: null };
|
|
60
60
|
const uploadThrottle = normalizeUploadState(await readJson(uploadThrottlePath));
|
|
61
61
|
let uploadThrottleState = uploadThrottle;
|
|
62
62
|
|
|
63
|
-
const codexHome = process.env.CODEX_HOME || path.join(home,
|
|
64
|
-
const codeHome = process.env.CODE_HOME || path.join(home,
|
|
65
|
-
const claudeProjectsDir = path.join(home,
|
|
66
|
-
const geminiHome = process.env.GEMINI_HOME || path.join(home,
|
|
67
|
-
const geminiTmpDir = path.join(geminiHome,
|
|
68
|
-
const xdgDataHome = process.env.XDG_DATA_HOME || path.join(home,
|
|
69
|
-
const opencodeHome = process.env.OPENCODE_HOME || path.join(xdgDataHome,
|
|
70
|
-
const opencodeStorageDir = path.join(opencodeHome,
|
|
63
|
+
const codexHome = process.env.CODEX_HOME || path.join(home, ".codex");
|
|
64
|
+
const codeHome = process.env.CODE_HOME || path.join(home, ".code");
|
|
65
|
+
const claudeProjectsDir = path.join(home, ".claude", "projects");
|
|
66
|
+
const geminiHome = process.env.GEMINI_HOME || path.join(home, ".gemini");
|
|
67
|
+
const geminiTmpDir = path.join(geminiHome, "tmp");
|
|
68
|
+
const xdgDataHome = process.env.XDG_DATA_HOME || path.join(home, ".local", "share");
|
|
69
|
+
const opencodeHome = process.env.OPENCODE_HOME || path.join(xdgDataHome, "opencode");
|
|
70
|
+
const opencodeStorageDir = path.join(opencodeHome, "storage");
|
|
71
71
|
|
|
72
72
|
// OpenClaw hook integration: allow a hook to request incremental parsing for a single session jsonl.
|
|
73
73
|
// We still parse all regular sources so model/source attribution stays complete (e.g. Kimi sessions).
|
|
74
|
-
const openclawSignal = opts.fromOpenclaw
|
|
74
|
+
const openclawSignal = opts.fromOpenclaw
|
|
75
|
+
? resolveOpenclawSignal({ home, env: process.env })
|
|
76
|
+
: null;
|
|
75
77
|
|
|
76
78
|
const sources = [
|
|
77
|
-
{ source:
|
|
78
|
-
{ source:
|
|
79
|
+
{ source: "codex", sessionsDir: path.join(codexHome, "sessions") },
|
|
80
|
+
{ source: "every-code", sessionsDir: path.join(codeHome, "sessions") },
|
|
79
81
|
];
|
|
80
82
|
|
|
81
83
|
const rolloutFiles = [];
|
|
@@ -89,10 +91,14 @@ async function cmdSync(argv) {
|
|
|
89
91
|
}
|
|
90
92
|
}
|
|
91
93
|
|
|
92
|
-
const openclawFiles = openclawSignal?.sessionFile
|
|
94
|
+
const openclawFiles = openclawSignal?.sessionFile
|
|
95
|
+
? [{ path: openclawSignal.sessionFile, source: "openclaw" }]
|
|
96
|
+
: [];
|
|
93
97
|
|
|
94
98
|
if (progress?.enabled) {
|
|
95
|
-
progress.start(
|
|
99
|
+
progress.start(
|
|
100
|
+
`Parsing ${renderBar(0)} 0/${formatNumber(rolloutFiles.length)} files | buckets 0`,
|
|
101
|
+
);
|
|
96
102
|
}
|
|
97
103
|
|
|
98
104
|
const parseResult = await parseRolloutIncremental({
|
|
@@ -105,10 +111,10 @@ async function cmdSync(argv) {
|
|
|
105
111
|
const pct = p.total > 0 ? p.index / p.total : 1;
|
|
106
112
|
progress.update(
|
|
107
113
|
`Parsing ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(p.total)} files | buckets ${formatNumber(
|
|
108
|
-
p.bucketsQueued
|
|
109
|
-
)}
|
|
114
|
+
p.bucketsQueued,
|
|
115
|
+
)}`,
|
|
110
116
|
);
|
|
111
|
-
}
|
|
117
|
+
},
|
|
112
118
|
});
|
|
113
119
|
|
|
114
120
|
let openclawResult = { filesProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
@@ -119,7 +125,7 @@ async function cmdSync(argv) {
|
|
|
119
125
|
cursors,
|
|
120
126
|
queuePath,
|
|
121
127
|
projectQueuePath,
|
|
122
|
-
source:
|
|
128
|
+
source: "openclaw",
|
|
123
129
|
});
|
|
124
130
|
}
|
|
125
131
|
|
|
@@ -128,7 +134,7 @@ async function cmdSync(argv) {
|
|
|
128
134
|
signal: openclawSignal,
|
|
129
135
|
cursors,
|
|
130
136
|
queuePath,
|
|
131
|
-
projectQueuePath
|
|
137
|
+
projectQueuePath,
|
|
132
138
|
});
|
|
133
139
|
openclawResult.filesProcessed += openclawFallback.filesProcessed;
|
|
134
140
|
openclawResult.eventsAggregated += openclawFallback.eventsAggregated;
|
|
@@ -138,7 +144,9 @@ async function cmdSync(argv) {
|
|
|
138
144
|
let claudeResult = { filesProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
139
145
|
if (claudeFiles.length > 0) {
|
|
140
146
|
if (progress?.enabled) {
|
|
141
|
-
progress.start(
|
|
147
|
+
progress.start(
|
|
148
|
+
`Parsing Claude ${renderBar(0)} 0/${formatNumber(claudeFiles.length)} files | buckets 0`,
|
|
149
|
+
);
|
|
142
150
|
}
|
|
143
151
|
claudeResult = await parseClaudeIncremental({
|
|
144
152
|
projectFiles: claudeFiles,
|
|
@@ -150,11 +158,11 @@ async function cmdSync(argv) {
|
|
|
150
158
|
const pct = p.total > 0 ? p.index / p.total : 1;
|
|
151
159
|
progress.update(
|
|
152
160
|
`Parsing Claude ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(p.total)} files | buckets ${formatNumber(
|
|
153
|
-
p.bucketsQueued
|
|
154
|
-
)}
|
|
161
|
+
p.bucketsQueued,
|
|
162
|
+
)}`,
|
|
155
163
|
);
|
|
156
164
|
},
|
|
157
|
-
source:
|
|
165
|
+
source: "claude",
|
|
158
166
|
});
|
|
159
167
|
}
|
|
160
168
|
|
|
@@ -162,7 +170,9 @@ async function cmdSync(argv) {
|
|
|
162
170
|
let geminiResult = { filesProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
163
171
|
if (geminiFiles.length > 0) {
|
|
164
172
|
if (progress?.enabled) {
|
|
165
|
-
progress.start(
|
|
173
|
+
progress.start(
|
|
174
|
+
`Parsing Gemini ${renderBar(0)} 0/${formatNumber(geminiFiles.length)} files | buckets 0`,
|
|
175
|
+
);
|
|
166
176
|
}
|
|
167
177
|
geminiResult = await parseGeminiIncremental({
|
|
168
178
|
sessionFiles: geminiFiles,
|
|
@@ -174,11 +184,11 @@ async function cmdSync(argv) {
|
|
|
174
184
|
const pct = p.total > 0 ? p.index / p.total : 1;
|
|
175
185
|
progress.update(
|
|
176
186
|
`Parsing Gemini ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(p.total)} files | buckets ${formatNumber(
|
|
177
|
-
p.bucketsQueued
|
|
178
|
-
)}
|
|
187
|
+
p.bucketsQueued,
|
|
188
|
+
)}`,
|
|
179
189
|
);
|
|
180
190
|
},
|
|
181
|
-
source:
|
|
191
|
+
source: "gemini",
|
|
182
192
|
});
|
|
183
193
|
}
|
|
184
194
|
|
|
@@ -186,7 +196,9 @@ async function cmdSync(argv) {
|
|
|
186
196
|
let opencodeResult = { filesProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
187
197
|
if (opencodeFiles.length > 0) {
|
|
188
198
|
if (progress?.enabled) {
|
|
189
|
-
progress.start(
|
|
199
|
+
progress.start(
|
|
200
|
+
`Parsing Opencode ${renderBar(0)} 0/${formatNumber(opencodeFiles.length)} files | buckets 0`,
|
|
201
|
+
);
|
|
190
202
|
}
|
|
191
203
|
opencodeResult = await parseOpencodeIncremental({
|
|
192
204
|
messageFiles: opencodeFiles,
|
|
@@ -198,23 +210,23 @@ async function cmdSync(argv) {
|
|
|
198
210
|
const pct = p.total > 0 ? p.index / p.total : 1;
|
|
199
211
|
progress.update(
|
|
200
212
|
`Parsing Opencode ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(
|
|
201
|
-
p.total
|
|
202
|
-
)} files | buckets ${formatNumber(p.bucketsQueued)}
|
|
213
|
+
p.total,
|
|
214
|
+
)} files | buckets ${formatNumber(p.bucketsQueued)}`,
|
|
203
215
|
);
|
|
204
216
|
},
|
|
205
|
-
source:
|
|
217
|
+
source: "opencode",
|
|
206
218
|
});
|
|
207
219
|
}
|
|
208
220
|
|
|
209
221
|
if (cursors?.projectHourly?.projects && projectQueuePath && projectQueueStatePath) {
|
|
210
222
|
for (const [projectKey, meta] of Object.entries(cursors.projectHourly.projects)) {
|
|
211
|
-
if (!meta || typeof meta !==
|
|
212
|
-
if (meta.status !==
|
|
223
|
+
if (!meta || typeof meta !== "object") continue;
|
|
224
|
+
if (meta.status !== "blocked" || !meta.purge_pending) continue;
|
|
213
225
|
await purgeProjectUsage({
|
|
214
226
|
projectKey,
|
|
215
227
|
projectQueuePath,
|
|
216
228
|
projectQueueStatePath,
|
|
217
|
-
projectState: cursors.projectHourly
|
|
229
|
+
projectState: cursors.projectHourly,
|
|
218
230
|
});
|
|
219
231
|
meta.purge_pending = false;
|
|
220
232
|
meta.purged_at = new Date().toISOString();
|
|
@@ -249,7 +261,7 @@ async function cmdSync(argv) {
|
|
|
249
261
|
autoDecision = decideAutoUpload({
|
|
250
262
|
nowMs: Date.now(),
|
|
251
263
|
pendingBytes,
|
|
252
|
-
state: uploadThrottle
|
|
264
|
+
state: uploadThrottle,
|
|
253
265
|
});
|
|
254
266
|
allowUpload = allowUpload && autoDecision.allowed;
|
|
255
267
|
maxBatches = autoDecision.allowed ? autoDecision.maxBatches : 0;
|
|
@@ -261,18 +273,19 @@ async function cmdSync(argv) {
|
|
|
261
273
|
retryAtMs: autoDecision.blockedUntilMs,
|
|
262
274
|
reason,
|
|
263
275
|
pendingBytes,
|
|
264
|
-
source:
|
|
265
|
-
autoRetryNoSpawn: runtime.autoRetryNoSpawn
|
|
276
|
+
source: "auto-throttled",
|
|
277
|
+
autoRetryNoSpawn: runtime.autoRetryNoSpawn,
|
|
266
278
|
});
|
|
267
279
|
}
|
|
268
280
|
}
|
|
269
281
|
|
|
270
282
|
if (progress?.enabled && pendingBytes > 0 && allowUpload) {
|
|
271
283
|
const totalSize = queueSize + projectQueueSize;
|
|
272
|
-
const totalOffset =
|
|
284
|
+
const totalOffset =
|
|
285
|
+
Number(beforeState.offset || 0) + Number(projectBeforeState.offset || 0);
|
|
273
286
|
const pct = totalSize > 0 ? totalOffset / totalSize : 0;
|
|
274
287
|
progress.start(
|
|
275
|
-
`Uploading ${renderBar(pct)} ${formatBytes(totalOffset)}/${formatBytes(totalSize)} | inserted 0 skipped 0
|
|
288
|
+
`Uploading ${renderBar(pct)} ${formatBytes(totalOffset)}/${formatBytes(totalSize)} | inserted 0 skipped 0`,
|
|
276
289
|
);
|
|
277
290
|
}
|
|
278
291
|
|
|
@@ -282,7 +295,7 @@ async function cmdSync(argv) {
|
|
|
282
295
|
home,
|
|
283
296
|
env: process.env,
|
|
284
297
|
probeKeychain: true,
|
|
285
|
-
probeKeychainDetails: true
|
|
298
|
+
probeKeychainDetails: true,
|
|
286
299
|
});
|
|
287
300
|
try {
|
|
288
301
|
uploadResult = await drainQueueToCloud({
|
|
@@ -300,10 +313,10 @@ async function cmdSync(argv) {
|
|
|
300
313
|
const pct = u.queueSize > 0 ? u.offset / u.queueSize : 1;
|
|
301
314
|
progress.update(
|
|
302
315
|
`Uploading ${renderBar(pct)} ${formatBytes(u.offset)}/${formatBytes(u.queueSize)} | inserted ${formatNumber(
|
|
303
|
-
u.inserted
|
|
304
|
-
)} skipped ${formatNumber(u.skipped)}
|
|
316
|
+
u.inserted,
|
|
317
|
+
)} skipped ${formatNumber(u.skipped)}`,
|
|
305
318
|
);
|
|
306
|
-
}
|
|
319
|
+
},
|
|
307
320
|
});
|
|
308
321
|
if (uploadResult.attempted > 0) {
|
|
309
322
|
const next = recordUploadSuccess({ nowMs: Date.now(), state: uploadThrottleState });
|
|
@@ -311,7 +324,11 @@ async function cmdSync(argv) {
|
|
|
311
324
|
await writeJson(uploadThrottlePath, next);
|
|
312
325
|
}
|
|
313
326
|
} catch (e) {
|
|
314
|
-
const next = recordUploadFailure({
|
|
327
|
+
const next = recordUploadFailure({
|
|
328
|
+
nowMs: Date.now(),
|
|
329
|
+
state: uploadThrottleState,
|
|
330
|
+
error: e,
|
|
331
|
+
});
|
|
315
332
|
uploadThrottleState = next;
|
|
316
333
|
await writeJson(uploadThrottlePath, next);
|
|
317
334
|
if (opts.auto && pendingBytes > 0) {
|
|
@@ -320,10 +337,10 @@ async function cmdSync(argv) {
|
|
|
320
337
|
await scheduleAutoRetry({
|
|
321
338
|
trackerDir,
|
|
322
339
|
retryAtMs,
|
|
323
|
-
reason:
|
|
340
|
+
reason: "backoff",
|
|
324
341
|
pendingBytes,
|
|
325
|
-
source:
|
|
326
|
-
autoRetryNoSpawn: runtime.autoRetryNoSpawn
|
|
342
|
+
source: "auto-error",
|
|
343
|
+
autoRetryNoSpawn: runtime.autoRetryNoSpawn,
|
|
327
344
|
});
|
|
328
345
|
}
|
|
329
346
|
}
|
|
@@ -352,10 +369,10 @@ async function cmdSync(argv) {
|
|
|
352
369
|
await scheduleAutoRetry({
|
|
353
370
|
trackerDir,
|
|
354
371
|
retryAtMs,
|
|
355
|
-
reason:
|
|
372
|
+
reason: "backlog",
|
|
356
373
|
pendingBytes,
|
|
357
|
-
source:
|
|
358
|
-
autoRetryNoSpawn: runtime.autoRetryNoSpawn
|
|
374
|
+
source: "auto-backlog",
|
|
375
|
+
autoRetryNoSpawn: runtime.autoRetryNoSpawn,
|
|
359
376
|
});
|
|
360
377
|
}
|
|
361
378
|
}
|
|
@@ -365,7 +382,7 @@ async function cmdSync(argv) {
|
|
|
365
382
|
deviceToken,
|
|
366
383
|
trackerDir,
|
|
367
384
|
uploadResult,
|
|
368
|
-
pendingBytes
|
|
385
|
+
pendingBytes,
|
|
369
386
|
});
|
|
370
387
|
|
|
371
388
|
if (!opts.auto) {
|
|
@@ -383,19 +400,19 @@ async function cmdSync(argv) {
|
|
|
383
400
|
opencodeResult.bucketsQueued;
|
|
384
401
|
process.stdout.write(
|
|
385
402
|
[
|
|
386
|
-
|
|
403
|
+
"Sync finished:",
|
|
387
404
|
`- Parsed files: ${totalParsed}`,
|
|
388
405
|
`- New 30-min buckets queued: ${totalBuckets}`,
|
|
389
406
|
deviceToken
|
|
390
407
|
? `- Uploaded: ${uploadResult.inserted} inserted, ${uploadResult.skipped} skipped`
|
|
391
|
-
:
|
|
408
|
+
: "- Uploaded: skipped (no device token)",
|
|
392
409
|
deviceToken && pendingBytes > 0 && !opts.drain
|
|
393
410
|
? `- Remaining: ${formatBytes(pendingBytes)} pending (run sync again, or use --drain)`
|
|
394
411
|
: null,
|
|
395
|
-
|
|
412
|
+
"",
|
|
396
413
|
]
|
|
397
414
|
.filter(Boolean)
|
|
398
|
-
.join(
|
|
415
|
+
.join("\n"),
|
|
399
416
|
);
|
|
400
417
|
}
|
|
401
418
|
} finally {
|
|
@@ -411,15 +428,15 @@ function parseArgs(argv) {
|
|
|
411
428
|
fromNotify: false,
|
|
412
429
|
fromRetry: false,
|
|
413
430
|
fromOpenclaw: false,
|
|
414
|
-
drain: false
|
|
431
|
+
drain: false,
|
|
415
432
|
};
|
|
416
433
|
for (let i = 0; i < argv.length; i++) {
|
|
417
434
|
const a = argv[i];
|
|
418
|
-
if (a ===
|
|
419
|
-
else if (a ===
|
|
420
|
-
else if (a ===
|
|
421
|
-
else if (a ===
|
|
422
|
-
else if (a ===
|
|
435
|
+
if (a === "--auto") out.auto = true;
|
|
436
|
+
else if (a === "--from-notify") out.fromNotify = true;
|
|
437
|
+
else if (a === "--from-retry") out.fromRetry = true;
|
|
438
|
+
else if (a === "--from-openclaw") out.fromOpenclaw = true;
|
|
439
|
+
else if (a === "--drain") out.drain = true;
|
|
423
440
|
else throw new Error(`Unknown option: ${a}`);
|
|
424
441
|
}
|
|
425
442
|
return out;
|
|
@@ -428,7 +445,7 @@ function parseArgs(argv) {
|
|
|
428
445
|
module.exports = { cmdSync };
|
|
429
446
|
|
|
430
447
|
function normalizeString(value) {
|
|
431
|
-
if (typeof value !==
|
|
448
|
+
if (typeof value !== "string") return null;
|
|
432
449
|
const trimmed = value.trim();
|
|
433
450
|
return trimmed.length > 0 ? trimmed : null;
|
|
434
451
|
}
|
|
@@ -440,15 +457,16 @@ function resolveOpenclawSignal({ home, env } = {}) {
|
|
|
440
457
|
const sessionId = normalizeString(env.VIBEUSAGE_OPENCLAW_PREV_SESSION_ID);
|
|
441
458
|
if (!agentId || !sessionId) return null;
|
|
442
459
|
|
|
443
|
-
const openclawHome =
|
|
444
|
-
|
|
460
|
+
const openclawHome =
|
|
461
|
+
normalizeString(env.VIBEUSAGE_OPENCLAW_HOME) || path.join(home || os.homedir(), ".openclaw");
|
|
462
|
+
const sessionFile = path.join(openclawHome, "agents", agentId, "sessions", `${sessionId}.jsonl`);
|
|
445
463
|
|
|
446
464
|
const prevTotals = {
|
|
447
465
|
totalTokens: normalizeNonNegativeInt(env.VIBEUSAGE_OPENCLAW_PREV_TOTAL_TOKENS),
|
|
448
466
|
inputTokens: normalizeNonNegativeInt(env.VIBEUSAGE_OPENCLAW_PREV_INPUT_TOKENS),
|
|
449
467
|
outputTokens: normalizeNonNegativeInt(env.VIBEUSAGE_OPENCLAW_PREV_OUTPUT_TOKENS),
|
|
450
468
|
model: normalizeString(env.VIBEUSAGE_OPENCLAW_PREV_MODEL),
|
|
451
|
-
updatedAt: normalizeIsoOrEpoch(env.VIBEUSAGE_OPENCLAW_PREV_UPDATED_AT)
|
|
469
|
+
updatedAt: normalizeIsoOrEpoch(env.VIBEUSAGE_OPENCLAW_PREV_UPDATED_AT),
|
|
452
470
|
};
|
|
453
471
|
|
|
454
472
|
return {
|
|
@@ -457,40 +475,55 @@ function resolveOpenclawSignal({ home, env } = {}) {
|
|
|
457
475
|
sessionKey: normalizeString(env.VIBEUSAGE_OPENCLAW_SESSION_KEY),
|
|
458
476
|
openclawHome,
|
|
459
477
|
sessionFile,
|
|
460
|
-
prevTotals
|
|
478
|
+
prevTotals,
|
|
461
479
|
};
|
|
462
480
|
}
|
|
463
481
|
|
|
464
|
-
|
|
465
|
-
|
|
482
|
+
async function applyOpenclawTotalsFallback({
|
|
483
|
+
trackerDir,
|
|
484
|
+
signal,
|
|
485
|
+
cursors,
|
|
486
|
+
queuePath,
|
|
487
|
+
projectQueuePath,
|
|
488
|
+
}) {
|
|
466
489
|
const totalTokens = Number(signal?.prevTotals?.totalTokens || 0);
|
|
467
490
|
if (!trackerDir || !signal || totalTokens <= 0) {
|
|
468
491
|
return { filesProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
469
492
|
}
|
|
470
493
|
|
|
471
494
|
const sessionKey = `${signal.agentId}:${signal.sessionId}`;
|
|
472
|
-
const statePath = path.join(trackerDir,
|
|
473
|
-
const fallbackFilePath = path.join(trackerDir,
|
|
495
|
+
const statePath = path.join(trackerDir, "openclaw.fallback.state.json");
|
|
496
|
+
const fallbackFilePath = path.join(trackerDir, "openclaw.fallback.jsonl");
|
|
474
497
|
const state = (await readJson(statePath)) || { version: 1, sessions: {} };
|
|
475
|
-
const sessions = state.sessions && typeof state.sessions ===
|
|
476
|
-
const prev =
|
|
498
|
+
const sessions = state.sessions && typeof state.sessions === "object" ? state.sessions : {};
|
|
499
|
+
const prev =
|
|
500
|
+
sessions[sessionKey] && typeof sessions[sessionKey] === "object" ? sessions[sessionKey] : null;
|
|
477
501
|
|
|
478
502
|
const current = {
|
|
479
503
|
totalTokens: normalizeNonNegativeInt(signal?.prevTotals?.totalTokens) || 0,
|
|
480
504
|
inputTokens: normalizeNonNegativeInt(signal?.prevTotals?.inputTokens) || 0,
|
|
481
505
|
outputTokens: normalizeNonNegativeInt(signal?.prevTotals?.outputTokens) || 0,
|
|
482
|
-
model: normalizeString(signal?.prevTotals?.model) ||
|
|
506
|
+
model: normalizeString(signal?.prevTotals?.model) || "unknown",
|
|
483
507
|
updatedAt: normalizeIsoOrEpoch(signal?.prevTotals?.updatedAt) || new Date().toISOString(),
|
|
484
|
-
seenAt: new Date().toISOString()
|
|
508
|
+
seenAt: new Date().toISOString(),
|
|
485
509
|
};
|
|
486
510
|
|
|
487
511
|
let deltaTotal = current.totalTokens;
|
|
488
512
|
let deltaInput = current.inputTokens;
|
|
489
513
|
let deltaOutput = current.outputTokens;
|
|
490
514
|
if (prev) {
|
|
491
|
-
deltaTotal = Math.max(
|
|
492
|
-
|
|
493
|
-
|
|
515
|
+
deltaTotal = Math.max(
|
|
516
|
+
0,
|
|
517
|
+
current.totalTokens - (normalizeNonNegativeInt(prev.totalTokens) || 0),
|
|
518
|
+
);
|
|
519
|
+
deltaInput = Math.max(
|
|
520
|
+
0,
|
|
521
|
+
current.inputTokens - (normalizeNonNegativeInt(prev.inputTokens) || 0),
|
|
522
|
+
);
|
|
523
|
+
deltaOutput = Math.max(
|
|
524
|
+
0,
|
|
525
|
+
current.outputTokens - (normalizeNonNegativeInt(prev.outputTokens) || 0),
|
|
526
|
+
);
|
|
494
527
|
}
|
|
495
528
|
|
|
496
529
|
if (deltaTotal > 0 && deltaInput + deltaOutput === 0) {
|
|
@@ -508,29 +541,29 @@ async function applyOpenclawTotalsFallback({ trackerDir, signal, cursors, queueP
|
|
|
508
541
|
|
|
509
542
|
await ensureDir(path.dirname(fallbackFilePath));
|
|
510
543
|
const syntheticMessage = {
|
|
511
|
-
type:
|
|
544
|
+
type: "message",
|
|
512
545
|
timestamp: current.updatedAt,
|
|
513
546
|
message: {
|
|
514
|
-
role:
|
|
547
|
+
role: "assistant",
|
|
515
548
|
model: current.model,
|
|
516
549
|
usage: {
|
|
517
550
|
input: deltaInput,
|
|
518
551
|
output: deltaOutput,
|
|
519
552
|
cacheRead: 0,
|
|
520
553
|
cacheWrite: 0,
|
|
521
|
-
totalTokens: deltaTotal
|
|
522
|
-
}
|
|
523
|
-
}
|
|
554
|
+
totalTokens: deltaTotal,
|
|
555
|
+
},
|
|
556
|
+
},
|
|
524
557
|
};
|
|
525
|
-
await fs.appendFile(fallbackFilePath, `${JSON.stringify(syntheticMessage)}\n`,
|
|
558
|
+
await fs.appendFile(fallbackFilePath, `${JSON.stringify(syntheticMessage)}\n`, "utf8");
|
|
526
559
|
await writeJson(statePath, state);
|
|
527
560
|
|
|
528
561
|
return parseOpenclawIncremental({
|
|
529
|
-
sessionFiles: [{ path: fallbackFilePath, source:
|
|
562
|
+
sessionFiles: [{ path: fallbackFilePath, source: "openclaw" }],
|
|
530
563
|
cursors,
|
|
531
564
|
queuePath,
|
|
532
565
|
projectQueuePath,
|
|
533
|
-
source:
|
|
566
|
+
source: "openclaw",
|
|
534
567
|
});
|
|
535
568
|
}
|
|
536
569
|
|
|
@@ -541,7 +574,7 @@ function normalizeNonNegativeInt(value) {
|
|
|
541
574
|
}
|
|
542
575
|
|
|
543
576
|
function normalizeIsoOrEpoch(value) {
|
|
544
|
-
if (typeof value ===
|
|
577
|
+
if (typeof value === "string") {
|
|
545
578
|
const trimmed = value.trim();
|
|
546
579
|
if (trimmed.length > 0 && !Number.isNaN(Date.parse(trimmed))) return trimmed;
|
|
547
580
|
const numeric = Number(trimmed);
|
|
@@ -569,14 +602,20 @@ async function safeStatSize(p) {
|
|
|
569
602
|
}
|
|
570
603
|
}
|
|
571
604
|
|
|
572
|
-
async function maybeSendHeartbeat({
|
|
605
|
+
async function maybeSendHeartbeat({
|
|
606
|
+
baseUrl,
|
|
607
|
+
deviceToken,
|
|
608
|
+
trackerDir,
|
|
609
|
+
uploadResult,
|
|
610
|
+
pendingBytes,
|
|
611
|
+
}) {
|
|
573
612
|
if (!deviceToken || !uploadResult) return;
|
|
574
613
|
if (pendingBytes > 0) return;
|
|
575
614
|
if (Number(uploadResult.inserted || 0) !== 0) return;
|
|
576
615
|
|
|
577
|
-
const heartbeatPath = path.join(trackerDir,
|
|
616
|
+
const heartbeatPath = path.join(trackerDir, "sync.heartbeat.json");
|
|
578
617
|
const heartbeatState = await readJson(heartbeatPath);
|
|
579
|
-
const lastPingAt = Date.parse(heartbeatState?.lastPingAt ||
|
|
618
|
+
const lastPingAt = Date.parse(heartbeatState?.lastPingAt || "");
|
|
580
619
|
const nowMs = Date.now();
|
|
581
620
|
if (Number.isFinite(lastPingAt) && nowMs - lastPingAt < HEARTBEAT_MIN_INTERVAL_MS) return;
|
|
582
621
|
|
|
@@ -584,7 +623,7 @@ async function maybeSendHeartbeat({ baseUrl, deviceToken, trackerDir, uploadResu
|
|
|
584
623
|
await syncHeartbeat({ baseUrl, deviceToken });
|
|
585
624
|
await writeJson(heartbeatPath, {
|
|
586
625
|
lastPingAt: new Date(nowMs).toISOString(),
|
|
587
|
-
minIntervalMinutes: HEARTBEAT_MIN_INTERVAL_MINUTES
|
|
626
|
+
minIntervalMinutes: HEARTBEAT_MIN_INTERVAL_MINUTES,
|
|
588
627
|
});
|
|
589
628
|
} catch (_e) {
|
|
590
629
|
// best-effort heartbeat; ignore failures
|
|
@@ -592,14 +631,21 @@ async function maybeSendHeartbeat({ baseUrl, deviceToken, trackerDir, uploadResu
|
|
|
592
631
|
}
|
|
593
632
|
|
|
594
633
|
function deriveAutoSkipReason({ decision, state }) {
|
|
595
|
-
if (!decision || decision.reason !==
|
|
634
|
+
if (!decision || decision.reason !== "throttled") return decision?.reason || "unknown";
|
|
596
635
|
const backoffUntilMs = Number(state?.backoffUntilMs || 0);
|
|
597
636
|
const nextAllowedAtMs = Number(state?.nextAllowedAtMs || 0);
|
|
598
|
-
if (backoffUntilMs > 0 && backoffUntilMs >= nextAllowedAtMs) return
|
|
599
|
-
return
|
|
637
|
+
if (backoffUntilMs > 0 && backoffUntilMs >= nextAllowedAtMs) return "backoff";
|
|
638
|
+
return "throttled";
|
|
600
639
|
}
|
|
601
640
|
|
|
602
|
-
async function scheduleAutoRetry({
|
|
641
|
+
async function scheduleAutoRetry({
|
|
642
|
+
trackerDir,
|
|
643
|
+
retryAtMs,
|
|
644
|
+
reason,
|
|
645
|
+
pendingBytes,
|
|
646
|
+
source,
|
|
647
|
+
autoRetryNoSpawn,
|
|
648
|
+
}) {
|
|
603
649
|
const retryMs = coerceRetryMs(retryAtMs);
|
|
604
650
|
if (!retryMs) return { scheduled: false, retryAtMs: 0 };
|
|
605
651
|
|
|
@@ -615,10 +661,10 @@ async function scheduleAutoRetry({ trackerDir, retryAtMs, reason, pendingBytes,
|
|
|
615
661
|
version: 1,
|
|
616
662
|
retryAtMs: retryMs,
|
|
617
663
|
retryAt: new Date(retryMs).toISOString(),
|
|
618
|
-
reason: typeof reason ===
|
|
664
|
+
reason: typeof reason === "string" && reason.length > 0 ? reason : "throttled",
|
|
619
665
|
pendingBytes: Math.max(0, Number(pendingBytes || 0)),
|
|
620
666
|
scheduledAt: new Date(nowMs).toISOString(),
|
|
621
|
-
source: typeof source ===
|
|
667
|
+
source: typeof source === "string" ? source : "auto",
|
|
622
668
|
};
|
|
623
669
|
|
|
624
670
|
await writeJson(retryPath, payload);
|
|
@@ -631,9 +677,9 @@ async function scheduleAutoRetry({ trackerDir, retryAtMs, reason, pendingBytes,
|
|
|
631
677
|
|
|
632
678
|
spawnAutoRetryProcess({
|
|
633
679
|
retryPath,
|
|
634
|
-
trackerBinPath: path.join(trackerDir,
|
|
635
|
-
fallbackPkg:
|
|
636
|
-
delayMs
|
|
680
|
+
trackerBinPath: path.join(trackerDir, "app", "bin", "tracker.js"),
|
|
681
|
+
fallbackPkg: "vibeusage",
|
|
682
|
+
delayMs,
|
|
637
683
|
});
|
|
638
684
|
return { scheduled: true, retryAtMs: retryMs };
|
|
639
685
|
}
|
|
@@ -646,17 +692,18 @@ async function clearAutoRetry(trackerDir) {
|
|
|
646
692
|
function spawnAutoRetryProcess({ retryPath, trackerBinPath, fallbackPkg, delayMs }) {
|
|
647
693
|
const script = buildAutoRetryScript({ retryPath, trackerBinPath, fallbackPkg, delayMs });
|
|
648
694
|
try {
|
|
649
|
-
const child = cp.spawn(process.execPath, [
|
|
695
|
+
const child = cp.spawn(process.execPath, ["-e", script], {
|
|
650
696
|
detached: true,
|
|
651
|
-
stdio:
|
|
652
|
-
env: process.env
|
|
697
|
+
stdio: "ignore",
|
|
698
|
+
env: process.env,
|
|
653
699
|
});
|
|
654
700
|
child.unref();
|
|
655
701
|
} catch (_e) {}
|
|
656
702
|
}
|
|
657
703
|
|
|
658
704
|
function buildAutoRetryScript({ retryPath, trackerBinPath, fallbackPkg, delayMs }) {
|
|
659
|
-
return
|
|
705
|
+
return (
|
|
706
|
+
`'use strict';\n` +
|
|
660
707
|
`const fs = require('node:fs');\n` +
|
|
661
708
|
`const cp = require('node:child_process');\n` +
|
|
662
709
|
`const retryPath = ${JSON.stringify(retryPath)};\n` +
|
|
@@ -678,7 +725,8 @@ function buildAutoRetryScript({ retryPath, trackerBinPath, fallbackPkg, delayMs
|
|
|
678
725
|
` const child = cp.spawn(cmd[0], cmd.slice(1), { detached: true, stdio: 'ignore', env: process.env });\n` +
|
|
679
726
|
` child.unref();\n` +
|
|
680
727
|
` } catch (_) {}\n` +
|
|
681
|
-
`}, delayMs);\n
|
|
728
|
+
`}, delayMs);\n`
|
|
729
|
+
);
|
|
682
730
|
}
|
|
683
731
|
|
|
684
732
|
function coerceRetryMs(v) {
|
|
@@ -688,9 +736,9 @@ function coerceRetryMs(v) {
|
|
|
688
736
|
}
|
|
689
737
|
|
|
690
738
|
async function writeOpenclawSignal(trackerDir) {
|
|
691
|
-
const openclawSignalPath = path.join(trackerDir,
|
|
739
|
+
const openclawSignalPath = path.join(trackerDir, "openclaw.signal");
|
|
692
740
|
try {
|
|
693
|
-
await fs.writeFile(openclawSignalPath, new Date().toISOString(),
|
|
741
|
+
await fs.writeFile(openclawSignalPath, new Date().toISOString(), "utf8");
|
|
694
742
|
} catch (_e) {
|
|
695
743
|
// best-effort marker
|
|
696
744
|
}
|
|
@@ -698,5 +746,5 @@ async function writeOpenclawSignal(trackerDir) {
|
|
|
698
746
|
|
|
699
747
|
const HEARTBEAT_MIN_INTERVAL_MINUTES = 30;
|
|
700
748
|
const HEARTBEAT_MIN_INTERVAL_MS = HEARTBEAT_MIN_INTERVAL_MINUTES * 60 * 1000;
|
|
701
|
-
const AUTO_RETRY_FILENAME =
|
|
749
|
+
const AUTO_RETRY_FILENAME = "auto.retry.json";
|
|
702
750
|
const AUTO_RETRY_MAX_DELAY_MS = 2 * 60 * 60 * 1000;
|