vibeusage 0.3.3 → 0.3.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.
@@ -337,31 +337,79 @@ function buildSessionPluginMeta() {
337
337
  )}\n`;
338
338
  }
339
339
 
340
- function buildSessionPluginIndex({ trackerDir }) {
340
+ function buildSessionPluginIndex({ trackerDir, packageName = "vibeusage" }) {
341
341
  const ledgerModuleUrl = pathToFileURL(
342
342
  path.join(trackerDir, "app", "src", "lib", "openclaw-usage-ledger.js"),
343
343
  ).href;
344
+ const trackerBinPath = path.join(trackerDir, "app", "bin", "tracker.js");
345
+ const fallbackPkg = packageName || "vibeusage";
344
346
 
345
347
  return (
348
+ `import fs from 'node:fs';\n` +
349
+ `import path from 'node:path';\n` +
350
+ `import cp from 'node:child_process';\n` +
346
351
  `import { appendOpenclawUsageEvent } from ${JSON.stringify(ledgerModuleUrl)};\n` +
347
352
  `\n` +
348
353
  `const trackerDir = ${JSON.stringify(trackerDir)};\n` +
354
+ `const trackerBinPath = ${JSON.stringify(trackerBinPath)};\n` +
355
+ `const fallbackPkg = ${JSON.stringify(fallbackPkg)};\n` +
356
+ `const depsMarkerPath = path.join(trackerDir, 'app', 'node_modules', '@insforge', 'sdk', 'package.json');\n` +
357
+ `const triggerStatePath = path.join(trackerDir, 'openclaw.session-sync.trigger-state.json');\n` +
358
+ `const SESSION_TRIGGER_THROTTLE_MS = 15_000;\n` +
349
359
  `\n` +
350
360
  `export default function register(api) {\n` +
351
361
  ` api.on('llm_output', async (event, ctx) => {\n` +
352
362
  ` try {\n` +
353
363
  ` const payload = buildPayload(event, ctx);\n` +
354
364
  ` if (!payload) return;\n` +
355
- ` await appendOpenclawUsageEvent({ trackerDir, payload });\n` +
365
+ ` const result = await appendOpenclawUsageEvent({ trackerDir, payload });\n` +
366
+ ` if (!result || result.appended === false) return;\n` +
367
+ ` const scope = normalize(ctx && ctx.agentId) || 'openclaw';\n` +
368
+ ` const target = resolveSessionRef(event, ctx) || normalize(payload.eventId) || 'unknown';\n` +
369
+ ` if (!allowTrigger('llm_output', scope, target)) return;\n` +
370
+ ` spawnSync({ args: ['sync', '--auto', '--from-openclaw'] });\n` +
356
371
  ` } catch (_) {}\n` +
357
372
  ` });\n` +
358
373
  `}\n` +
359
374
  `\n` +
375
+ `function spawnSync({ args }) {\n` +
376
+ ` const hasLocalRuntime = fs.existsSync(trackerBinPath);\n` +
377
+ ` const hasLocalDeps = fs.existsSync(depsMarkerPath);\n` +
378
+ ` const argv = Array.isArray(args) && args.length > 0 ? args : ['sync', '--auto'];\n` +
379
+ ` const cmd = hasLocalRuntime && hasLocalDeps\n` +
380
+ ` ? [process.execPath, trackerBinPath, ...argv]\n` +
381
+ ` : ['npx', '--yes', fallbackPkg, ...argv];\n` +
382
+ ` const child = cp.spawn(cmd[0], cmd.slice(1), {\n` +
383
+ ` detached: true,\n` +
384
+ ` stdio: 'ignore',\n` +
385
+ ` env: { ...process.env }\n` +
386
+ ` });\n` +
387
+ ` child.unref();\n` +
388
+ `}\n` +
389
+ `\n` +
390
+ `function allowTrigger(kind, scope, target) {\n` +
391
+ ` const key = [kind, scope || 'na', target || 'na'].join(':');\n` +
392
+ ` const now = Date.now();\n` +
393
+ ` let state = {};\n` +
394
+ ` try {\n` +
395
+ ` state = JSON.parse(fs.readFileSync(triggerStatePath, 'utf8'));\n` +
396
+ ` if (!state || typeof state !== 'object') state = {};\n` +
397
+ ` } catch (_) {}\n` +
398
+ ` const last = Number(state[key] || 0);\n` +
399
+ ` if (Number.isFinite(last) && now - last < SESSION_TRIGGER_THROTTLE_MS) return false;\n` +
400
+ ` state[key] = now;\n` +
401
+ ` try {\n` +
402
+ ` fs.mkdirSync(path.dirname(triggerStatePath), { recursive: true });\n` +
403
+ ` fs.writeFileSync(triggerStatePath, JSON.stringify(state), 'utf8');\n` +
404
+ ` } catch (_) {}\n` +
405
+ ` return true;\n` +
406
+ `}\n` +
407
+ `\n` +
360
408
  `function buildPayload(event, ctx) {\n` +
361
409
  ` const usage = normalizeUsage(event);\n` +
362
410
  ` if (!usage) return null;\n` +
363
411
  `\n` +
364
- ` const sessionKey = normalize(ctx && ctx.sessionKey);\n` +
412
+ ` const sessionKey = resolveSessionRef(event, ctx);\n` +
365
413
  ` if (!sessionKey) return null;\n` +
366
414
  `\n` +
367
415
  ` return {\n` +
@@ -378,6 +426,22 @@ function buildSessionPluginIndex({ trackerDir }) {
378
426
  ` };\n` +
379
427
  `}\n` +
380
428
  `\n` +
429
+ `function resolveSessionRef(event, ctx) {\n` +
430
+ ` const explicit = normalize(ctx && ctx.sessionKey);\n` +
431
+ ` if (explicit) return explicit;\n` +
432
+ ` const eventSessionId = normalize(event && event.sessionId);\n` +
433
+ ` if (eventSessionId) {\n` +
434
+ ` const agentId = normalize(ctx && ctx.agentId) || 'openclaw';\n` +
435
+ ` return 'agent:' + agentId + ':session:' + eventSessionId;\n` +
436
+ ` }\n` +
437
+ ` const ctxSessionId = normalize(ctx && ctx.sessionId);\n` +
438
+ ` if (ctxSessionId) {\n` +
439
+ ` const agentId = normalize(ctx && ctx.agentId) || 'openclaw';\n` +
440
+ ` return 'agent:' + agentId + ':session:' + ctxSessionId;\n` +
441
+ ` }\n` +
442
+ ` return null;\n` +
443
+ `}\n` +
444
+ `\n` +
381
445
  `function normalizeUsage(event) {\n` +
382
446
  ` const usage = event && typeof event.usage === 'object' ? event.usage : event || {};\n` +
383
447
  ` const normalized = {\n` +
@@ -0,0 +1,75 @@
1
+ # VIBEUSAGE_HERMES_PLUGIN
2
+ # Generated by VibeUsage. Do not edit manually.
3
+ import json
4
+ import os
5
+ from datetime import datetime, timezone
6
+
7
+ LEDGER_PATH = "__LEDGER_PATH__"
8
+ SOURCE = "hermes"
9
+ VERSION = 1
10
+
11
+
12
+ def _iso_now():
13
+ return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
14
+
15
+
16
+ def _safe_int(value):
17
+ try:
18
+ return max(0, int(value or 0))
19
+ except Exception:
20
+ return 0
21
+
22
+
23
+ def _append_record(record):
24
+ try:
25
+ os.makedirs(os.path.dirname(LEDGER_PATH), exist_ok=True)
26
+ with open(LEDGER_PATH, "a", encoding="utf-8") as handle:
27
+ handle.write(json.dumps(record, ensure_ascii=False) + "\n")
28
+ except Exception:
29
+ return None
30
+ return None
31
+
32
+
33
+ def _base_record(record_type, session_id="", platform="", model="", provider=""):
34
+ return {
35
+ "version": VERSION,
36
+ "type": str(record_type or ""),
37
+ "source": SOURCE,
38
+ "session_id": str(session_id or ""),
39
+ "platform": str(platform or ""),
40
+ "model": str(model or ""),
41
+ "provider": str(provider or ""),
42
+ "emitted_at": _iso_now(),
43
+ }
44
+
45
+
46
+ def on_session_start(session_id="", model="", platform="", **_kwargs):
47
+ return _append_record(_base_record("session_start", session_id=session_id, platform=platform, model=model))
48
+
49
+
50
+ def post_api_request(session_id="", platform="", model="", provider="", api_mode="", api_call_count=0, finish_reason="", usage=None, **_kwargs):
51
+ if not isinstance(usage, dict):
52
+ return None
53
+ record = _base_record("usage", session_id=session_id, platform=platform, model=model, provider=provider)
54
+ record.update({
55
+ "api_mode": str(api_mode or ""),
56
+ "api_call_count": _safe_int(api_call_count),
57
+ "input_tokens": _safe_int(usage.get("input_tokens")),
58
+ "output_tokens": _safe_int(usage.get("output_tokens")),
59
+ "cache_read_tokens": _safe_int(usage.get("cache_read_tokens")),
60
+ "cache_write_tokens": _safe_int(usage.get("cache_write_tokens")),
61
+ "reasoning_tokens": _safe_int(usage.get("reasoning_tokens")),
62
+ "total_tokens": _safe_int(usage.get("total_tokens")),
63
+ "finish_reason": str(finish_reason or ""),
64
+ })
65
+ return _append_record(record)
66
+
67
+
68
+ def on_session_end(session_id="", model="", platform="", **_kwargs):
69
+ return _append_record(_base_record("session_end", session_id=session_id, platform=platform, model=model))
70
+
71
+
72
+ def register(ctx):
73
+ ctx.register_hook("on_session_start", on_session_start)
74
+ ctx.register_hook("post_api_request", post_api_request)
75
+ ctx.register_hook("on_session_end", on_session_end)
@@ -0,0 +1,9 @@
1
+ # VIBEUSAGE_HERMES_PLUGIN
2
+ name: vibeusage
3
+ version: "1"
4
+ description: "VibeUsage Hermes usage ledger plugin"
5
+ author: "VibeUsage"
6
+ provides_hooks:
7
+ - "on_session_start"
8
+ - "post_api_request"
9
+ - "on_session_end"