gitdone-agent 0.6.6 → 0.6.8

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.
Files changed (2) hide show
  1. package/index.js +69 -8
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -27,7 +27,7 @@ import { randomUUID } from 'node:crypto'
27
27
  // Reported to the server on every sync so the web UI can flag outdated agents.
28
28
  // Keep in lockstep with packages/agent/package.json "version" AND
29
29
  // src/lib/agentVersion.ts LATEST_AGENT_VERSION.
30
- const AGENT_VERSION = '0.6.6'
30
+ const AGENT_VERSION = '0.6.8'
31
31
 
32
32
  const AGENT_DIR = join(homedir(), '.gitdone-agent')
33
33
  const CONFIG_PATH = join(AGENT_DIR, 'config.json')
@@ -547,12 +547,15 @@ async function reportCommandResult(cfg, id, status, result) {
547
547
  }
548
548
 
549
549
  // Post a batch of console events (and optional lifecycle status) for an AiRun.
550
- async function postRunEvents(cfg, runId, events, status, result) {
550
+ // `usage` is sent once on the final (done/error) post so the server can record
551
+ // how many tokens the run cost.
552
+ async function postRunEvents(cfg, runId, events, status, result, usage) {
551
553
  await api(cfg, '/api/v1/agent/ai-run/events', {
552
554
  runId,
553
555
  events,
554
556
  ...(status ? { status } : {}),
555
557
  ...(result !== undefined ? { result } : {}),
558
+ ...(usage ? { usage } : {}),
556
559
  }).catch((e) => log(`✗ ai-run events post failed: ${e.message}`))
557
560
  }
558
561
 
@@ -575,7 +578,15 @@ async function postSessionEvents(cfg, sessionId, events, status, claudeSessionId
575
578
  // With `--include-partial-messages`, Claude also emits `stream_event` lines
576
579
  // carrying incremental text deltas — onDelta gets those so chat sessions can
577
580
  // show the reply being written live (gd-302).
578
- function parseStreamLine(line, push, onInit, onDelta) {
581
+ // "12.3k" / "1.2M" compaction for the console token summary line.
582
+ function fmtTokens(n) {
583
+ n = Number(n) || 0
584
+ if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`
585
+ if (n >= 1_000) return `${(n / 1_000).toFixed(1)}k`
586
+ return String(n)
587
+ }
588
+
589
+ function parseStreamLine(line, push, onInit, onDelta, onMeta) {
579
590
  let ev
580
591
  try { ev = JSON.parse(line) } catch { return }
581
592
  if (ev.type === 'system') {
@@ -583,6 +594,8 @@ function parseStreamLine(line, push, onInit, onDelta) {
583
594
  push('SYSTEM', `Сесия стартирана${ev.model ? ` (${ev.model})` : ''}.`)
584
595
  // Surface Claude's own session id so chat sessions can --resume it.
585
596
  if (ev.session_id && typeof onInit === 'function') onInit(ev.session_id)
597
+ // Remember which model ran, for the token/cost record (gd-334).
598
+ if (ev.model && typeof onMeta === 'function') onMeta({ model: ev.model })
586
599
  }
587
600
  return
588
601
  }
@@ -607,8 +620,40 @@ function parseStreamLine(line, push, onInit, onDelta) {
607
620
  }
608
621
  return
609
622
  }
610
- if (ev.type === 'result' && ev.subtype && ev.subtype !== 'success') {
611
- push('SYSTEM', `Резултат: ${ev.subtype}`)
623
+ if (ev.type === 'result') {
624
+ if (ev.subtype && ev.subtype !== 'success') push('SYSTEM', `Резултат: ${ev.subtype}`)
625
+ // claude's final result carries cumulative token usage + its own cost.
626
+ // Prefer `modelUsage` (summed over every model/subagent turn) which is the
627
+ // true cumulative; top-level `usage` is often just the last turn. Fall back
628
+ // to `usage` when modelUsage is absent (older CLI).
629
+ if (typeof onMeta === 'function' && (ev.modelUsage || ev.usage || typeof ev.total_cost_usd === 'number')) {
630
+ let usage
631
+ let model
632
+ if (ev.modelUsage && typeof ev.modelUsage === 'object') {
633
+ const acc = { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheCreateTokens: 0 }
634
+ for (const [name, mu] of Object.entries(ev.modelUsage)) {
635
+ if (!model) model = name
636
+ acc.inputTokens += mu.inputTokens ?? mu.input_tokens ?? 0
637
+ acc.outputTokens += mu.outputTokens ?? mu.output_tokens ?? 0
638
+ acc.cacheReadTokens += mu.cacheReadInputTokens ?? mu.cache_read_input_tokens ?? 0
639
+ acc.cacheCreateTokens += mu.cacheCreationInputTokens ?? mu.cache_creation_input_tokens ?? 0
640
+ }
641
+ usage = acc
642
+ } else if (ev.usage) {
643
+ const u = ev.usage
644
+ usage = {
645
+ inputTokens: u.input_tokens ?? 0,
646
+ outputTokens: u.output_tokens ?? 0,
647
+ cacheReadTokens: u.cache_read_input_tokens ?? 0,
648
+ cacheCreateTokens: u.cache_creation_input_tokens ?? 0,
649
+ }
650
+ }
651
+ onMeta({
652
+ ...(usage ? { usage } : {}),
653
+ ...(model ? { model } : {}),
654
+ costUsd: typeof ev.total_cost_usd === 'number' ? ev.total_cost_usd : undefined,
655
+ })
656
+ }
612
657
  }
613
658
  }
614
659
 
@@ -799,6 +844,15 @@ function runAiCommand(cfg, cmd, repoPath) {
799
844
  const push = (kind, text) => { if (text != null && String(text) !== '') pending.push({ kind, text: String(text) }) }
800
845
  const timer = setInterval(flush, 800)
801
846
 
847
+ // Accumulate model + token usage across the stream (model from init, usage
848
+ // from the final result event) to report once on exit (gd-334).
849
+ const meta = { model: undefined, usage: undefined, costUsd: undefined }
850
+ const onMeta = (m) => {
851
+ if (m.model) meta.model = m.model
852
+ if (m.usage) meta.usage = m.usage
853
+ if (typeof m.costUsd === 'number') meta.costUsd = m.costUsd
854
+ }
855
+
802
856
  log(`▶ ai_run ${runId} @ ${repoPath} via ${claudePath}`)
803
857
  postRunEvents(cfg, runId, [{ kind: 'SYSTEM', text: `Стартиране на Claude Code в ${repoPath}…` }], 'running')
804
858
 
@@ -827,21 +881,28 @@ function runAiCommand(cfg, cmd, repoPath) {
827
881
  while ((nl = buf.indexOf('\n')) >= 0) {
828
882
  const line = buf.slice(0, nl).trim()
829
883
  buf = buf.slice(nl + 1)
830
- if (line) parseStreamLine(line, push)
884
+ if (line) parseStreamLine(line, push, undefined, undefined, onMeta)
831
885
  }
832
886
  })
833
887
  child.stderr.on('data', (d) => { const s = d.toString().trim(); if (s) push('SYSTEM', s) })
834
888
  child.on('error', (err) => push('SYSTEM', `Процесна грешка: ${err.message}`))
835
889
  child.on('close', async (code) => {
836
890
  clearInterval(timer)
837
- if (buf.trim()) parseStreamLine(buf.trim(), push)
891
+ if (buf.trim()) parseStreamLine(buf.trim(), push, undefined, undefined, onMeta)
838
892
  await flush()
839
893
  const ok = code === 0
894
+ // Build the usage payload + a console summary line from what we saw.
895
+ const usage = meta.usage
896
+ ? { model: meta.model, ...meta.usage, ...(typeof meta.costUsd === 'number' ? { costUsd: meta.costUsd } : {}) }
897
+ : undefined
898
+ const events = [{ kind: 'SYSTEM', text: ok ? '✓ Готово.' : `✗ Процесът приключи с код ${code}.` }]
899
+ if (usage) events.push({ kind: 'SYSTEM', text: `📊 Токени: ${fmtTokens(usage.inputTokens)} вход · ${fmtTokens(usage.outputTokens)} изход${usage.cacheReadTokens ? ` · ${fmtTokens(usage.cacheReadTokens)} кеш` : ''}${typeof usage.costUsd === 'number' ? ` · $${usage.costUsd.toFixed(4)}` : ''}` })
840
900
  await postRunEvents(
841
901
  cfg, runId,
842
- [{ kind: 'SYSTEM', text: ok ? '✓ Готово.' : `✗ Процесът приключи с код ${code}.` }],
902
+ events,
843
903
  ok ? 'done' : 'error',
844
904
  ok ? 'ok' : `exit ${code}`,
905
+ usage,
845
906
  )
846
907
  reportCommandResult(cfg, cmd.id, ok ? 'done' : 'error', `exit ${code}`)
847
908
  log(`■ ai_run ${runId} приключи (code ${code})`)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitdone-agent",
3
- "version": "0.6.6",
3
+ "version": "0.6.8",
4
4
  "description": "Local git agent for gitdone — watches a local repo and sends snapshots to gitdone.eu",
5
5
  "type": "module",
6
6
  "bin": {