agentix-cli 0.15.0 → 0.17.0

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 (148) hide show
  1. package/README.md +12 -6
  2. package/dist/agent-4AOMVXRJ.js +2 -0
  3. package/dist/board-dashboard-CXIVJKDP.js +7282 -0
  4. package/dist/board-dashboard-CXIVJKDP.js.map +1 -0
  5. package/dist/chunk-34NG7UDX.js +11 -0
  6. package/dist/chunk-34NG7UDX.js.map +1 -0
  7. package/dist/chunk-4HP6UC2X.js +13 -0
  8. package/dist/chunk-4HP6UC2X.js.map +1 -0
  9. package/dist/chunk-5F3J55LG.js +2 -0
  10. package/dist/chunk-5F3J55LG.js.map +1 -0
  11. package/dist/chunk-5WNGHDFC.js +164 -0
  12. package/dist/chunk-5WNGHDFC.js.map +1 -0
  13. package/dist/{chunk-7MBTMCKY.js → chunk-63UYRJIC.js} +2 -2
  14. package/dist/chunk-64P4QHSZ.js +2 -0
  15. package/dist/chunk-64P4QHSZ.js.map +1 -0
  16. package/dist/chunk-6PPIFDOQ.js +1742 -0
  17. package/dist/chunk-6PPIFDOQ.js.map +1 -0
  18. package/dist/chunk-ABCHHNG6.js +625 -0
  19. package/dist/chunk-ABCHHNG6.js.map +1 -0
  20. package/dist/chunk-AFRHKA7B.js +2 -0
  21. package/dist/chunk-AFRHKA7B.js.map +1 -0
  22. package/dist/chunk-BFZUG66X.js +45 -0
  23. package/dist/chunk-BFZUG66X.js.map +1 -0
  24. package/dist/chunk-CDNWCYF7.js +2 -0
  25. package/dist/chunk-CDNWCYF7.js.map +1 -0
  26. package/dist/{chunk-6MKAXEE5.js → chunk-EKTPCSN4.js} +2 -2
  27. package/dist/{chunk-6MKAXEE5.js.map → chunk-EKTPCSN4.js.map} +1 -1
  28. package/dist/chunk-EW27SCCC.js +2 -0
  29. package/dist/chunk-EW27SCCC.js.map +1 -0
  30. package/dist/chunk-HCFSHGK4.js +46 -0
  31. package/dist/chunk-HCFSHGK4.js.map +1 -0
  32. package/dist/chunk-HHJGKD5B.js +71 -0
  33. package/dist/chunk-HHJGKD5B.js.map +1 -0
  34. package/dist/chunk-LEIYC3BF.js +104 -0
  35. package/dist/chunk-LEIYC3BF.js.map +1 -0
  36. package/dist/{chunk-HLRVDCNH.js → chunk-MBEQASP3.js} +2 -2
  37. package/dist/chunk-N67JZAXU.js +2 -0
  38. package/dist/chunk-N67JZAXU.js.map +1 -0
  39. package/dist/{chunk-RGUYBH77.js → chunk-SRHLXZRF.js} +2 -2
  40. package/dist/chunk-TT2Q7YMZ.js +118 -0
  41. package/dist/chunk-TT2Q7YMZ.js.map +1 -0
  42. package/dist/{chunk-HI24KAEY.js → chunk-WJXU5F4Z.js} +3 -4
  43. package/dist/{chunk-HI24KAEY.js.map → chunk-WJXU5F4Z.js.map} +1 -1
  44. package/dist/chunk-WZEYII2S.js +6 -0
  45. package/dist/chunk-WZEYII2S.js.map +1 -0
  46. package/dist/chunk-XDJSYQST.js +6 -0
  47. package/dist/chunk-XDJSYQST.js.map +1 -0
  48. package/dist/chunk-YPOELN6Z.js +798 -0
  49. package/dist/chunk-YPOELN6Z.js.map +1 -0
  50. package/dist/{chunk-QZP65I2H.js → chunk-ZE2YFX4K.js} +2 -2
  51. package/dist/cli.js +1 -302
  52. package/dist/cli.js.map +1 -1
  53. package/dist/{compaction-JBZZ7I7Y.js → compaction-X46EQEPS.js} +6 -6
  54. package/dist/compaction-X46EQEPS.js.map +1 -0
  55. package/dist/{config-DCJYECL4.js → config-KCYHMKCT.js} +2 -2
  56. package/dist/config-mutator-QP4GLBOE.js +2 -0
  57. package/dist/context-planner-G2J7FNNU.js +17 -0
  58. package/dist/context-planner-G2J7FNNU.js.map +1 -0
  59. package/dist/editor-chat-R54E65W5.js +137 -0
  60. package/dist/editor-chat-R54E65W5.js.map +1 -0
  61. package/dist/heal-JDXUPRET.js +2 -0
  62. package/dist/inbox-L5ZZ623Y.js +315 -0
  63. package/dist/inbox-L5ZZ623Y.js.map +1 -0
  64. package/dist/index.d.ts +1996 -18
  65. package/dist/index.js +22 -49
  66. package/dist/index.js.map +1 -1
  67. package/dist/ingest-whatsapp-NS4OWDOU.js +6 -0
  68. package/dist/ingest-whatsapp-NS4OWDOU.js.map +1 -0
  69. package/dist/memory-extract-WH4FZ456.js +2 -0
  70. package/dist/processes-XF5CV4LK.js +256 -0
  71. package/dist/processes-XF5CV4LK.js.map +1 -0
  72. package/dist/program-FFWEILAS.js +2 -0
  73. package/dist/providers-AKQ4HTB4.js +2 -0
  74. package/dist/query-PKGYE5HR.js +2 -0
  75. package/dist/registry-IFMDJWQ7.js +2 -0
  76. package/dist/scheduler-63D5KFO7.js +2 -0
  77. package/dist/scheduler-63D5KFO7.js.map +1 -0
  78. package/dist/slack-PSQLVTGB.js +3 -0
  79. package/dist/slack-PSQLVTGB.js.map +1 -0
  80. package/dist/store-5MJOO74U.js +2 -0
  81. package/dist/store-5MJOO74U.js.map +1 -0
  82. package/dist/task-store-NM7VKNLE.js +2 -0
  83. package/dist/task-store-NM7VKNLE.js.map +1 -0
  84. package/dist/telegram-W4SLFMNY.js +3 -0
  85. package/dist/telegram-W4SLFMNY.js.map +1 -0
  86. package/dist/timers-PCB4ZIBD.js +2 -0
  87. package/dist/timers-PCB4ZIBD.js.map +1 -0
  88. package/dist/types-C7IFH42Z.js +2 -0
  89. package/dist/types-C7IFH42Z.js.map +1 -0
  90. package/dist/types-OIFU3KSQ.js +2 -0
  91. package/dist/types-OIFU3KSQ.js.map +1 -0
  92. package/dist/usage-dashboard-AXPXB2KP.js +803 -0
  93. package/dist/usage-dashboard-AXPXB2KP.js.map +1 -0
  94. package/dist/web/workflow-editor.global.js +1427 -0
  95. package/dist/web/workflow-editor.global.js.map +1 -0
  96. package/dist/whatsapp-DCHRBRVV.js +3 -0
  97. package/dist/whatsapp-DCHRBRVV.js.map +1 -0
  98. package/dist/wiki-UDPYFJBL.js +2 -0
  99. package/dist/wiki-UDPYFJBL.js.map +1 -0
  100. package/dist/workspace-setup-C4VB3QZV.js +2 -0
  101. package/dist/workspace-setup-C4VB3QZV.js.map +1 -0
  102. package/package.json +10 -3
  103. package/dist/agent-LRSSVFDR.js +0 -2
  104. package/dist/board-dashboard-DAPHL6W2.js +0 -4508
  105. package/dist/board-dashboard-DAPHL6W2.js.map +0 -1
  106. package/dist/chunk-3XULOWYB.js +0 -199
  107. package/dist/chunk-3XULOWYB.js.map +0 -1
  108. package/dist/chunk-6U2XOQHU.js +0 -7
  109. package/dist/chunk-6U2XOQHU.js.map +0 -1
  110. package/dist/chunk-AU37OREL.js +0 -108
  111. package/dist/chunk-AU37OREL.js.map +0 -1
  112. package/dist/chunk-B4Q4X7D5.js +0 -6
  113. package/dist/chunk-B4Q4X7D5.js.map +0 -1
  114. package/dist/chunk-CXY2RYNI.js +0 -11
  115. package/dist/chunk-CXY2RYNI.js.map +0 -1
  116. package/dist/chunk-IEVEMKAI.js +0 -168
  117. package/dist/chunk-IEVEMKAI.js.map +0 -1
  118. package/dist/chunk-KXZMYLHQ.js +0 -46
  119. package/dist/chunk-KXZMYLHQ.js.map +0 -1
  120. package/dist/chunk-M7HKBG3V.js +0 -2
  121. package/dist/chunk-M7HKBG3V.js.map +0 -1
  122. package/dist/chunk-RB7265LO.js +0 -12
  123. package/dist/chunk-RB7265LO.js.map +0 -1
  124. package/dist/chunk-WVNSWBCU.js +0 -138
  125. package/dist/chunk-WVNSWBCU.js.map +0 -1
  126. package/dist/compaction-JBZZ7I7Y.js.map +0 -1
  127. package/dist/config-mutator-6EJHSP3Y.js +0 -2
  128. package/dist/heal-WKY5I4KB.js +0 -2
  129. package/dist/memory-extract-AD7KMRPC.js +0 -2
  130. package/dist/providers-VUIWO3ZK.js +0 -2
  131. package/dist/registry-5ND7GLFJ.js +0 -2
  132. package/dist/scheduler-FCXOXCN5.js +0 -2
  133. package/dist/usage-dashboard-QX3CF6KK.js +0 -404
  134. package/dist/usage-dashboard-QX3CF6KK.js.map +0 -1
  135. package/dist/workspace-setup-ZWTT42XT.js +0 -2
  136. /package/dist/{agent-LRSSVFDR.js.map → agent-4AOMVXRJ.js.map} +0 -0
  137. /package/dist/{chunk-7MBTMCKY.js.map → chunk-63UYRJIC.js.map} +0 -0
  138. /package/dist/{chunk-HLRVDCNH.js.map → chunk-MBEQASP3.js.map} +0 -0
  139. /package/dist/{chunk-RGUYBH77.js.map → chunk-SRHLXZRF.js.map} +0 -0
  140. /package/dist/{chunk-QZP65I2H.js.map → chunk-ZE2YFX4K.js.map} +0 -0
  141. /package/dist/{config-DCJYECL4.js.map → config-KCYHMKCT.js.map} +0 -0
  142. /package/dist/{config-mutator-6EJHSP3Y.js.map → config-mutator-QP4GLBOE.js.map} +0 -0
  143. /package/dist/{heal-WKY5I4KB.js.map → heal-JDXUPRET.js.map} +0 -0
  144. /package/dist/{memory-extract-AD7KMRPC.js.map → memory-extract-WH4FZ456.js.map} +0 -0
  145. /package/dist/{providers-VUIWO3ZK.js.map → program-FFWEILAS.js.map} +0 -0
  146. /package/dist/{registry-5ND7GLFJ.js.map → providers-AKQ4HTB4.js.map} +0 -0
  147. /package/dist/{scheduler-FCXOXCN5.js.map → query-PKGYE5HR.js.map} +0 -0
  148. /package/dist/{workspace-setup-ZWTT42XT.js.map → registry-IFMDJWQ7.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/agents/drift-detection.ts","../src/agents/compaction.ts"],"sourcesContent":["import { tokenize } from \"../memory/bm25\"\n\n// --- Behavioral Drift Detection ---\n//\n// Detects silent behavioral shifts after compaction or session boundaries.\n// Three signals, all zero-AI (pure signal processing):\n//\n// 1. Ghost lexicon decay — domain vocabulary disappearing after compaction\n// 2. Tool-call sequence shift — Jaccard distance on tool usage patterns\n// 3. Semantic drift — keyword overlap decline across session boundaries\n//\n// Inspired by CrewAI #5155 and agent-morrow/compression-monitor.\n\nexport interface BehavioralFingerprint {\n /** Domain vocabulary: word → frequency */\n lexicon: Map<string, number>\n /** Tool call sequence: tool names in order */\n toolCalls: string[]\n /** Top keywords by frequency (for quick comparison) */\n topKeywords: string[]\n /** Timestamp of fingerprint capture */\n capturedAt: string\n /** Number of messages in the sample */\n messageCount: number\n}\n\nexport interface DriftReport {\n /** 0-1 score: 0 = no drift, 1 = complete drift */\n overallScore: number\n /** Ghost lexicon decay: percentage of domain words that disappeared */\n lexiconDecay: number\n /** Tool-call Jaccard distance: 0 = identical patterns, 1 = completely different */\n toolShift: number\n /** Keyword overlap decline: percentage of shared keywords lost */\n semanticDrift: number\n /** Domain words that disappeared */\n lostWords: string[]\n /** New tool calls that weren't in the baseline */\n newTools: string[]\n /** Missing tool calls that were in the baseline */\n missingTools: string[]\n}\n\n/**\n * Build a behavioral fingerprint from agent messages.\n * Call this BEFORE compaction to establish a baseline.\n */\nexport function buildFingerprint(\n messages: Array<{ role: string; content: string; name?: string }>,\n toolCalls?: string[],\n): BehavioralFingerprint {\n const lexicon = new Map<string, number>()\n\n // Build vocabulary from agent responses\n for (const msg of messages) {\n if (msg.role !== \"agent\") continue\n const words = tokenize(msg.content)\n for (const word of words) {\n lexicon.set(word, (lexicon.get(word) || 0) + 1)\n }\n }\n\n // Top keywords by frequency (domain vocabulary)\n const topKeywords = [...lexicon.entries()]\n .sort((a, b) => b[1] - a[1])\n .slice(0, 50)\n .map(([word]) => word)\n\n return {\n lexicon,\n toolCalls: toolCalls || [],\n topKeywords,\n capturedAt: new Date().toISOString(),\n messageCount: messages.length,\n }\n}\n\n/**\n * Compare two fingerprints and produce a drift report.\n * Call this AFTER compaction with the new session's fingerprint.\n */\nexport function detectDrift(\n baseline: BehavioralFingerprint,\n current: BehavioralFingerprint,\n): DriftReport {\n // 1. Ghost lexicon decay: how many baseline domain words are missing?\n const lexiconDecay = computeLexiconDecay(baseline, current)\n\n // 2. Tool-call sequence shift: Jaccard distance on tool usage\n const { distance: toolShift, newTools, missingTools } = computeToolShift(baseline, current)\n\n // 3. Semantic drift: keyword overlap decline\n const semanticDrift = computeSemanticDrift(baseline, current)\n\n // Overall score: weighted average (lexicon decay matters most)\n const overallScore = Math.min(1, (lexiconDecay * 0.4) + (toolShift * 0.3) + (semanticDrift * 0.3))\n\n // Lost domain words\n const lostWords = baseline.topKeywords.filter(\n word => !current.lexicon.has(word) || (current.lexicon.get(word) || 0) === 0,\n ).slice(0, 10)\n\n return {\n overallScore: Math.round(overallScore * 100) / 100,\n lexiconDecay: Math.round(lexiconDecay * 100) / 100,\n toolShift: Math.round(toolShift * 100) / 100,\n semanticDrift: Math.round(semanticDrift * 100) / 100,\n lostWords,\n newTools,\n missingTools,\n }\n}\n\n/**\n * Ghost lexicon decay: percentage of baseline's top domain words\n * that disappeared from the current fingerprint.\n */\nfunction computeLexiconDecay(\n baseline: BehavioralFingerprint,\n current: BehavioralFingerprint,\n): number {\n if (baseline.topKeywords.length === 0) return 0\n\n let missing = 0\n for (const word of baseline.topKeywords) {\n if (!current.lexicon.has(word)) missing++\n }\n\n return missing / baseline.topKeywords.length\n}\n\n/**\n * Tool-call sequence shift: Jaccard distance between tool usage sets.\n * J(A,B) = |A ∩ B| / |A ∪ B|, distance = 1 - J\n */\nfunction computeToolShift(\n baseline: BehavioralFingerprint,\n current: BehavioralFingerprint,\n): { distance: number; newTools: string[]; missingTools: string[] } {\n const baseSet = new Set(baseline.toolCalls)\n const currSet = new Set(current.toolCalls)\n\n if (baseSet.size === 0 && currSet.size === 0) {\n return { distance: 0, newTools: [], missingTools: [] }\n }\n\n const intersection = [...baseSet].filter(t => currSet.has(t))\n const union = new Set([...baseSet, ...currSet])\n const jaccard = intersection.length / union.size\n const distance = 1 - jaccard\n\n const newTools = [...currSet].filter(t => !baseSet.has(t))\n const missingTools = [...baseSet].filter(t => !currSet.has(t))\n\n return { distance, newTools, missingTools }\n}\n\n/**\n * Semantic drift: keyword overlap decline.\n * Compares top keywords between baseline and current.\n */\nfunction computeSemanticDrift(\n baseline: BehavioralFingerprint,\n current: BehavioralFingerprint,\n): number {\n if (baseline.topKeywords.length === 0) return 0\n\n const currentSet = new Set(current.topKeywords)\n let overlap = 0\n\n for (const word of baseline.topKeywords) {\n if (currentSet.has(word)) overlap++\n }\n\n // Drift = 1 - overlap ratio\n return 1 - (overlap / baseline.topKeywords.length)\n}\n\n// --- Per-agent fingerprint persistence ---\n\nimport { readFileSync, writeFileSync, existsSync, mkdirSync } from \"fs\"\nimport { resolve } from \"path\"\n\nconst DRIFT_DIR = \".agentx/drift\"\n\n/**\n * Save a behavioral fingerprint for an agent (pre-compaction baseline).\n */\nexport function saveFingerprint(\n agentId: string,\n fingerprint: BehavioralFingerprint,\n baseDir: string = process.cwd(),\n): void {\n const dir = resolve(baseDir, DRIFT_DIR)\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true })\n\n const serialized = {\n ...fingerprint,\n lexicon: Object.fromEntries(fingerprint.lexicon),\n }\n\n writeFileSync(\n resolve(dir, `${agentId}.json`),\n JSON.stringify(serialized, null, 2),\n )\n}\n\n/**\n * Load the saved fingerprint for an agent.\n */\nexport function loadFingerprint(\n agentId: string,\n baseDir: string = process.cwd(),\n): BehavioralFingerprint | null {\n const file = resolve(baseDir, DRIFT_DIR, `${agentId}.json`)\n if (!existsSync(file)) return null\n\n try {\n const data = JSON.parse(readFileSync(file, \"utf-8\"))\n return {\n ...data,\n lexicon: new Map(Object.entries(data.lexicon)),\n }\n } catch {\n return null\n }\n}\n","import type { SessionMessage } from \"./sessions\"\nimport type { MemoryStore } from \"./memory-store\"\nimport { buildFingerprint, detectDrift, saveFingerprint, loadFingerprint, type DriftReport } from \"./drift-detection\"\n\n// --- Context Compaction ---\n//\n// Summarizes conversation history when it grows too large,\n// preventing context loss in long-running sessions.\n//\n// Strategy:\n// 1. Memory flush: extract memorable facts BEFORE compacting\n// 2. Split-aware: keep tool_use/tool_result pairs intact\n// 3. Uses cheaper model (Haiku) for summarization\n// 4. Preserves recent messages verbatim, compacts older ones\n\nconst COMPACTION_MODEL = \"claude-haiku-4-20250514\"\nconst MAX_HISTORY_CHARS = 12000\nconst COMPACT_THRESHOLD = 10000 // Start compacting when history exceeds this\nconst KEEP_RECENT = 6 // Always keep last N messages verbatim\nconst MIN_MESSAGES_TO_COMPACT = 8 // Don't compact if fewer than this\n\nconst COMPACTION_PROMPT = `You are a conversation summarizer. Given a conversation history between a user and an AI agent, produce a concise summary that preserves:\n\n1. Key decisions made and their reasoning\n2. Important facts learned (names, IDs, paths, URLs, credentials shared)\n3. Commitments and action items (what was promised, deadlines)\n4. Current task state (what's in progress, what's done, what's blocked)\n5. User preferences expressed during the conversation\n\nFormat as a structured summary:\n[Session Summary]\n- Decisions: ...\n- Facts: ...\n- In progress: ...\n- Done: ...\n- Preferences: ...\n\nKeep it under 500 words. Prioritize actionable information over conversational flow.\nDo NOT include greetings, acknowledgments, or meta-commentary about the conversation.`\n\nexport interface CompactionResult {\n /** Summarized text replacing older messages */\n summary: string\n /** Messages that were compacted (removed from history) */\n compactedCount: number\n /** Messages kept verbatim (recent) */\n keptCount: number\n /** Whether memory flush was performed before compaction */\n memoryFlushed: boolean\n /** Quality score: percentage of key entities preserved in summary (0-100) */\n qualityScore?: number\n /** Entities that were lost in compaction */\n lostEntities?: string[]\n /** Behavioral drift report (compared to pre-compaction baseline) */\n drift?: DriftReport\n}\n\n/**\n * Check whether a session's history needs compaction.\n */\nexport function needsCompaction(messages: SessionMessage[]): boolean {\n if (messages.length < MIN_MESSAGES_TO_COMPACT) return false\n const totalChars = messages.reduce((sum, m) => sum + m.content.length, 0)\n return totalChars > COMPACT_THRESHOLD\n}\n\n/**\n * Compact a session's conversation history.\n *\n * Flow:\n * 1. Flush memories from the about-to-be-compacted messages\n * 2. Summarize older messages using Haiku\n * 3. Return summary + preserved recent messages\n */\nexport async function compactSession(\n messages: SessionMessage[],\n agentId: string,\n memoryStore?: MemoryStore,\n): Promise<CompactionResult> {\n if (messages.length < MIN_MESSAGES_TO_COMPACT) {\n return {\n summary: \"\",\n compactedCount: 0,\n keptCount: messages.length,\n memoryFlushed: false,\n }\n }\n\n // Split: older messages to compact, recent to keep\n const splitIdx = Math.max(0, messages.length - KEEP_RECENT)\n const toCompact = messages.slice(0, splitIdx)\n const toKeep = messages.slice(splitIdx)\n\n if (toCompact.length === 0) {\n return {\n summary: \"\",\n compactedCount: 0,\n keptCount: toKeep.length,\n memoryFlushed: false,\n }\n }\n\n // Step 0: Capture behavioral fingerprint BEFORE compaction (drift baseline)\n const preFingerprint = buildFingerprint(toCompact)\n saveFingerprint(agentId, preFingerprint)\n\n // Step 1: Memory flush — extract facts from messages about to be compacted\n let memoryFlushed = false\n if (memoryStore) {\n try {\n await flushMemories(agentId, toCompact, memoryStore)\n memoryFlushed = true\n } catch {\n // Best-effort memory flush\n }\n }\n\n // Step 2: Summarize older messages\n const historyText = formatMessagesForSummary(toCompact)\n const summary = await summarize(historyText)\n\n // Step 3: Verify compaction quality — check entity preservation\n const { qualityScore, lostEntities } = verifyCompactionQuality(historyText, summary)\n\n // Step 4: Check for behavioral drift — compare summary against pre-compaction baseline\n let drift: DriftReport | undefined\n const summaryMessages: SessionMessage[] = [{\n role: \"agent\" as const,\n content: summary,\n timestamp: new Date().toISOString(),\n }]\n const postFingerprint = buildFingerprint(summaryMessages)\n const baseline = loadFingerprint(agentId)\n if (baseline) {\n drift = detectDrift(baseline, postFingerprint)\n }\n\n return {\n summary,\n compactedCount: toCompact.length,\n keptCount: toKeep.length,\n memoryFlushed,\n qualityScore,\n lostEntities: lostEntities.length > 0 ? lostEntities : undefined,\n drift: drift && drift.overallScore > 0.1 ? drift : undefined,\n }\n}\n\n/**\n * Verify compaction quality by checking entity preservation.\n * Extracts key entities (names, IDs, URLs, decisions) from the original\n * and checks how many survived in the summary.\n */\nfunction verifyCompactionQuality(\n original: string,\n summary: string,\n): { qualityScore: number; lostEntities: string[] } {\n const entities = extractEntities(original)\n if (entities.length === 0) return { qualityScore: 100, lostEntities: [] }\n\n const summaryLower = summary.toLowerCase()\n const preserved: string[] = []\n const lost: string[] = []\n\n for (const entity of entities) {\n if (summaryLower.includes(entity.toLowerCase())) {\n preserved.push(entity)\n } else {\n lost.push(entity)\n }\n }\n\n const score = Math.round((preserved.length / entities.length) * 100)\n return { qualityScore: score, lostEntities: lost }\n}\n\n/**\n * Extract key entities from text: names, IDs, URLs, file paths, decisions.\n */\nfunction extractEntities(text: string): string[] {\n const entities: string[] = []\n const seen = new Set<string>()\n\n const add = (entity: string) => {\n const key = entity.toLowerCase().trim()\n if (key.length < 3 || seen.has(key)) return\n seen.add(key)\n entities.push(entity.trim())\n }\n\n // URLs\n const urls = text.match(/https?:\\/\\/[^\\s<>)\"]+/g) || []\n for (const url of urls) add(url.slice(0, 80))\n\n // File paths\n const paths = text.match(/(?:\\/[\\w.-]+){2,}/g) || []\n for (const p of paths) add(p)\n\n // Ticket/issue references (#123, !456)\n const refs = text.match(/[#!]\\d{2,}/g) || []\n for (const r of refs) add(r)\n\n // Email addresses\n const emails = text.match(/[\\w.-]+@[\\w.-]+\\.\\w+/g) || []\n for (const e of emails) add(e)\n\n // Quoted strings (potential names, IDs, keys)\n const quoted = text.match(/\"([^\"]{3,40})\"/g) || []\n for (const q of quoted) add(q.slice(1, -1))\n\n // Capitalized proper nouns (2+ words starting with uppercase)\n const properNouns = text.match(/[A-Z][a-z]+(?:\\s+[A-Z][a-z]+)+/g) || []\n for (const n of properNouns) add(n)\n\n // UUIDs and hashes\n const ids = text.match(/[a-f0-9]{8,}/gi) || []\n for (const id of ids.slice(0, 5)) add(id) // cap to avoid noise\n\n return entities.slice(0, 30) // max 30 entities to check\n}\n\n/**\n * Apply compaction result to a session's messages.\n * Replaces older messages with a single summary message and keeps recent ones.\n */\nexport function applyCompaction(\n messages: SessionMessage[],\n result: CompactionResult,\n): SessionMessage[] {\n if (result.compactedCount === 0) return messages\n\n const kept = messages.slice(-result.keptCount)\n\n // Prepend summary as a synthetic \"system\" message\n const summaryMessage: SessionMessage = {\n role: \"agent\",\n name: \"system\",\n content: `[Compacted conversation summary — ${result.compactedCount} earlier messages]\\n\\n${result.summary}`,\n timestamp: new Date().toISOString(),\n }\n\n return [summaryMessage, ...kept]\n}\n\n/**\n * Flush extractable memories from messages before they get compacted.\n * This ensures important facts survive compaction.\n */\nasync function flushMemories(\n agentId: string,\n messages: SessionMessage[],\n store: MemoryStore,\n): Promise<void> {\n // Build a conversation block from the messages\n const userMessages: string[] = []\n const agentMessages: string[] = []\n\n for (const msg of messages) {\n if (msg.role === \"user\") {\n userMessages.push(`${msg.name || \"User\"}: ${msg.content}`)\n } else {\n agentMessages.push(msg.content)\n }\n }\n\n if (userMessages.length === 0 || agentMessages.length === 0) return\n\n // Use the existing memory extraction on a combined block\n const { extractMemories } = await import(\"./memory-extract\")\n await extractMemories(\n agentId,\n userMessages.join(\"\\n\"),\n agentMessages.join(\"\\n\"),\n { channel: \"compaction\", chatId: \"flush\", sender: \"compaction\" },\n store,\n )\n}\n\n/**\n * Format messages into a readable transcript for the summarizer.\n */\nfunction formatMessagesForSummary(messages: SessionMessage[]): string {\n return messages\n .map((m) => {\n const time = m.timestamp.slice(11, 16)\n const speaker = m.role === \"user\" ? (m.name || \"User\") : (m.name || \"Agent\")\n return `[${time}] ${speaker}: ${m.content}`\n })\n .join(\"\\n\\n\")\n}\n\n/**\n * Summarize a conversation transcript using a cheap/fast model.\n */\nasync function summarize(transcript: string): Promise<string> {\n // Truncate extremely long transcripts to avoid exceeding Haiku's context\n const maxInputChars = 20000\n const truncated = transcript.length > maxInputChars\n ? transcript.slice(-maxInputChars)\n : transcript\n\n try {\n const { createProvider } = await import(\"@/agent/providers\")\n const provider = createProvider(\"claude\")\n\n const result = await provider.generate(\n [\n { role: \"system\", content: COMPACTION_PROMPT },\n { role: \"user\", content: truncated },\n ],\n { model: COMPACTION_MODEL, maxTokens: 1024 },\n )\n\n return result.content.trim()\n } catch (error: any) {\n // Fallback: simple truncation if summarization fails\n const lines = transcript.split(\"\\n\")\n const kept = lines.slice(-20)\n return `[Summary unavailable — keeping last ${kept.length} lines]\\n${kept.join(\"\\n\")}`\n }\n}\n"],"mappings":"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","names":["readFileSync","writeFileSync","existsSync","mkdirSync","resolve","buildFingerprint","messages","toolCalls","lexicon","msg","words","tokenize","word","topKeywords","a","b","detectDrift","baseline","current","lexiconDecay","computeLexiconDecay","toolShift","newTools","missingTools","computeToolShift","semanticDrift","computeSemanticDrift","overallScore","lostWords","missing","baseSet","currSet","intersection","t","union","distance","currentSet","overlap","DRIFT_DIR","saveFingerprint","agentId","fingerprint","baseDir","dir","serialized","loadFingerprint","file","data","COMPACTION_MODEL","COMPACT_THRESHOLD","KEEP_RECENT","MIN_MESSAGES_TO_COMPACT","COMPACTION_PROMPT","needsCompaction","messages","sum","m","compactSession","agentId","memoryStore","splitIdx","toCompact","toKeep","preFingerprint","buildFingerprint","saveFingerprint","memoryFlushed","flushMemories","historyText","formatMessagesForSummary","summary","summarize","qualityScore","lostEntities","verifyCompactionQuality","drift","summaryMessages","postFingerprint","baseline","loadFingerprint","detectDrift","original","entities","extractEntities","summaryLower","preserved","lost","entity","text","seen","add","key","urls","url","paths","p","refs","r","emails","e","quoted","q","properNouns","n","ids","id","applyCompaction","result","kept","store","userMessages","agentMessages","msg","extractMemories","time","speaker","transcript","truncated","createProvider","COMPACTION_MODEL"]}
@@ -1,2 +0,0 @@
1
- import{a,b,c,d}from"./chunk-HLRVDCNH.js";import"./chunk-B4Q4X7D5.js";import"./chunk-FIOB3MEX.js";export{a as applyConfigMutation,c as getAtPath,b as setAtPath,d as unsetAtPath};
2
- //# sourceMappingURL=config-mutator-6EJHSP3Y.js.map
@@ -1,2 +0,0 @@
1
- import{a}from"./chunk-6MKAXEE5.js";import"./chunk-SFQUP3BP.js";import"./chunk-KXZMYLHQ.js";import"./chunk-M7HKBG3V.js";import"./chunk-FIOB3MEX.js";export{a as HealEngine};
2
- //# sourceMappingURL=heal-WKY5I4KB.js.map
@@ -1,2 +0,0 @@
1
- import{a}from"./chunk-7MBTMCKY.js";import"./chunk-FIOB3MEX.js";export{a as extractMemories};
2
- //# sourceMappingURL=memory-extract-AD7KMRPC.js.map
@@ -1,2 +0,0 @@
1
- import{e as a,f as b,g as c}from"./chunk-KXZMYLHQ.js";import"./chunk-M7HKBG3V.js";import"./chunk-FIOB3MEX.js";export{b as ClaudeCodeProvider,a as ClaudeProvider,c as createProvider};
2
- //# sourceMappingURL=providers-VUIWO3ZK.js.map
@@ -1,2 +0,0 @@
1
- import{g as a,h as b,i as c}from"./chunk-IEVEMKAI.js";import"./chunk-CXY2RYNI.js";import"./chunk-HI24KAEY.js";import"./chunk-7MBTMCKY.js";import"./chunk-QOYAX2FT.js";import"./chunk-FIOB3MEX.js";export{c as AgentRegistry,b as getGlobalRegistry,a as setGlobalRegistry};
2
- //# sourceMappingURL=registry-5ND7GLFJ.js.map
@@ -1,2 +0,0 @@
1
- import{a,b}from"./chunk-RGUYBH77.js";import"./chunk-HLRVDCNH.js";import"./chunk-B4Q4X7D5.js";import"./chunk-FIOB3MEX.js";export{b as CronScheduler,a as getNextCronDate};
2
- //# sourceMappingURL=scheduler-FCXOXCN5.js.map
@@ -1,404 +0,0 @@
1
- import{a as p}from"./chunk-CXY2RYNI.js";import"./chunk-FIOB3MEX.js";import{createServer as w}from"http";import{readdirSync as k}from"fs";function T(s=4201){let n=new p;w(async(i,e)=>{let t=new URL(i.url||"/",`http://localhost:${s}`),o=t.pathname;if(e.setHeader("Access-Control-Allow-Origin","*"),o==="/api/usage"){let c=t.searchParams.get("from")||"",r=t.searchParams.get("to")||"",l=R(n,c,r);e.writeHead(200,{"Content-Type":"application/json"}),e.end(JSON.stringify(l));return}e.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),e.end(S())}).listen(s,()=>{console.log(`
2
- Usage dashboard: http://localhost:${s}
3
- `)})}function R(s,n,d){let i=s.dir,e=[];try{for(let t of k(i)){if(!t.endsWith(".json"))continue;let o=t.replace(".json","");n&&o<n||d&&o>d||e.push(o)}}catch{return[]}return e.sort(),e.map(t=>{let o=s.getDate(t);if(!o)return null;let c=0,r=0,l=0,h=0,u=0,f=0,x={};for(let[C,a]of Object.entries(o.agents)){let m=p.calculateCost(a),g=(a.inputTokens||0)+(a.tier2InputTokens||0),b=(a.outputTokens||0)+(a.tier2OutputTokens||0),v=(a.cacheReadTokens||0)+(a.tier2CacheReadTokens||0),y=(a.cacheCreateTokens||0)+(a.tier2CacheCreateTokens||0);c+=a.tasks,r+=g,l+=b,h+=v,u+=y,f+=m,x[C]={tasks:a.tasks,cost:m,input:g,output:b,cacheRead:v,cacheCreate:y,model:a.model}}return{date:t,tasks:c,input:r,output:l,cacheRead:h,cacheCreate:u,cost:f,agents:x}}).filter(t=>t!==null&&t.tasks>0)}function S(){return`<!DOCTYPE html>
4
- <html lang="en">
5
- <head>
6
- <meta charset="utf-8">
7
- <meta name="viewport" content="width=device-width, initial-scale=1">
8
- <title>AgentX Usage Dashboard</title>
9
- <style>${D}</style>
10
- </head>
11
- <body>
12
- <div class="header">
13
- <h1>AgentX Usage Dashboard</h1>
14
- <div class="filters">
15
- <label>From <input type="date" id="from"></label>
16
- <label>To <input type="date" id="to"></label>
17
- <button onclick="load()">Apply</button>
18
- <button onclick="setRange(7)">7d</button>
19
- <button onclick="setRange(14)">14d</button>
20
- <button onclick="setRange(30)">30d</button>
21
- <button onclick="setRange(0)">All</button>
22
- </div>
23
- </div>
24
-
25
- <div class="stats" id="stats"></div>
26
-
27
- <div class="grid">
28
- <div class="card">
29
- <h2>Cost Over Time</h2>
30
- <canvas id="costChart" height="260"></canvas>
31
- </div>
32
- <div class="card">
33
- <h2>Tokens Over Time</h2>
34
- <canvas id="tokenChart" height="260"></canvas>
35
- </div>
36
- </div>
37
-
38
- <div class="grid">
39
- <div class="card">
40
- <h2>Cost Per Agent</h2>
41
- <canvas id="agentChart" height="260"></canvas>
42
- </div>
43
- <div class="card">
44
- <h2>Token Breakdown</h2>
45
- <canvas id="breakdownChart" height="260"></canvas>
46
- </div>
47
- </div>
48
-
49
- <div class="card">
50
- <h2>Daily Detail</h2>
51
- <table id="table">
52
- <thead>
53
- <tr><th>Date</th><th>Tasks</th><th>Input</th><th>Output</th><th>Cache R</th><th>Cache W</th><th>Cost</th><th>Top Agent</th></tr>
54
- </thead>
55
- <tbody></tbody>
56
- </table>
57
- </div>
58
-
59
- <script>${O}</script>
60
- </body>
61
- </html>`}var D=`
62
- :root {
63
- --bg: #0f1117;
64
- --card: #1a1d27;
65
- --border: #2a2d3a;
66
- --text: #e1e4ed;
67
- --muted: #8b8fa3;
68
- --accent: #6366f1;
69
- --green: #22c55e;
70
- --orange: #f59e0b;
71
- --red: #ef4444;
72
- --blue: #3b82f6;
73
- --purple: #a855f7;
74
- --cyan: #06b6d4;
75
- }
76
- * { margin: 0; padding: 0; box-sizing: border-box; }
77
- body { background: var(--bg); color: var(--text); font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; padding: 24px; }
78
- .header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px; flex-wrap: wrap; gap: 12px; }
79
- h1 { font-size: 20px; font-weight: 600; }
80
- h2 { font-size: 14px; font-weight: 500; color: var(--muted); margin-bottom: 16px; text-transform: uppercase; letter-spacing: 0.5px; }
81
- .filters { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
82
- .filters label { font-size: 13px; color: var(--muted); }
83
- .filters input { background: var(--card); border: 1px solid var(--border); color: var(--text); padding: 6px 10px; border-radius: 6px; font-size: 13px; }
84
- .filters button { background: var(--card); border: 1px solid var(--border); color: var(--text); padding: 6px 14px; border-radius: 6px; cursor: pointer; font-size: 13px; transition: background 0.15s; }
85
- .filters button:hover { background: var(--accent); border-color: var(--accent); }
86
- .stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 12px; margin-bottom: 24px; }
87
- .stat { background: var(--card); border: 1px solid var(--border); border-radius: 10px; padding: 16px; }
88
- .stat .value { font-size: 24px; font-weight: 700; margin-bottom: 4px; }
89
- .stat .label { font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; }
90
- .stat .delta { font-size: 12px; margin-top: 4px; }
91
- .stat .delta.up { color: var(--red); }
92
- .stat .delta.down { color: var(--green); }
93
- .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 16px; }
94
- @media (max-width: 900px) { .grid { grid-template-columns: 1fr; } }
95
- .card { background: var(--card); border: 1px solid var(--border); border-radius: 10px; padding: 20px; }
96
- canvas { width: 100% !important; }
97
- table { width: 100%; border-collapse: collapse; font-size: 13px; }
98
- th { text-align: left; padding: 8px 12px; border-bottom: 1px solid var(--border); color: var(--muted); font-weight: 500; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; }
99
- td { padding: 8px 12px; border-bottom: 1px solid var(--border); }
100
- tr:hover td { background: rgba(99, 102, 241, 0.05); }
101
- .cost { color: var(--accent); font-weight: 600; font-variant-numeric: tabular-nums; }
102
- .tok { font-variant-numeric: tabular-nums; }
103
- `,O=`
104
- let data = [];
105
-
106
- function fmtTok(n) {
107
- if (n >= 1e6) return (n/1e6).toFixed(1) + 'M';
108
- if (n >= 1e3) return (n/1e3).toFixed(0) + 'K';
109
- return String(n);
110
- }
111
- function fmtCost(n) { return n < 0.01 ? '$' + n.toFixed(4) : '$' + n.toFixed(2); }
112
-
113
- function setRange(days) {
114
- if (days === 0) {
115
- document.getElementById('from').value = '';
116
- document.getElementById('to').value = '';
117
- } else {
118
- const to = new Date();
119
- const from = new Date(Date.now() - days * 86400000);
120
- document.getElementById('from').value = from.toISOString().slice(0,10);
121
- document.getElementById('to').value = to.toISOString().slice(0,10);
122
- }
123
- load();
124
- }
125
-
126
- async function load() {
127
- const from = document.getElementById('from').value;
128
- const to = document.getElementById('to').value;
129
- const params = new URLSearchParams();
130
- if (from) params.set('from', from);
131
- if (to) params.set('to', to);
132
- const res = await fetch('/api/usage?' + params);
133
- data = await res.json();
134
- render();
135
- }
136
-
137
- function render() {
138
- renderStats();
139
- renderCostChart();
140
- renderTokenChart();
141
- renderAgentChart();
142
- renderBreakdownChart();
143
- renderTable();
144
- }
145
-
146
- function renderStats() {
147
- const el = document.getElementById('stats');
148
- const totalCost = data.reduce((s,d) => s + d.cost, 0);
149
- const totalTasks = data.reduce((s,d) => s + d.tasks, 0);
150
- const totalInput = data.reduce((s,d) => s + d.input, 0);
151
- const totalOutput = data.reduce((s,d) => s + d.output, 0);
152
- const totalCacheR = data.reduce((s,d) => s + d.cacheRead, 0);
153
- const totalCacheW = data.reduce((s,d) => s + d.cacheCreate, 0);
154
- const cacheRatio = (totalCacheR + totalCacheW) > 0 ? (totalCacheR / (totalCacheR + totalCacheW) * 100).toFixed(0) : '0';
155
- const avgDaily = data.length > 0 ? totalCost / data.length : 0;
156
-
157
- // Delta: compare last day vs previous
158
- let delta = '';
159
- if (data.length >= 2) {
160
- const last = data[data.length-1].cost;
161
- const prev = data[data.length-2].cost;
162
- if (prev > 0) {
163
- const pct = ((last - prev) / prev * 100).toFixed(0);
164
- const cls = last > prev ? 'up' : 'down';
165
- const arrow = last > prev ? '\\u2191' : '\\u2193';
166
- delta = '<div class="delta ' + cls + '">' + arrow + ' ' + Math.abs(pct) + '% vs prev day</div>';
167
- }
168
- }
169
-
170
- el.innerHTML =
171
- stat(fmtCost(totalCost), 'Total Cost', delta) +
172
- stat(fmtCost(avgDaily), 'Avg Daily Cost', '') +
173
- stat(totalTasks.toLocaleString(), 'Total Tasks', '') +
174
- stat(fmtTok(totalInput + totalOutput), 'Tokens (I/O)', '') +
175
- stat(fmtTok(totalCacheR), 'Cache Reads', '') +
176
- stat(cacheRatio + '%', 'Cache Hit Rate', '') +
177
- stat(data.length.toString(), 'Days Tracked', '');
178
- }
179
-
180
- function stat(value, label, extra) {
181
- return '<div class="stat"><div class="value">' + value + '</div><div class="label">' + label + '</div>' + extra + '</div>';
182
- }
183
-
184
- // --- Canvas chart helpers ---
185
- function drawLine(ctx, points, w, h, color, fill) {
186
- if (points.length < 2) return;
187
- const max = Math.max(...points, 0.001);
188
- const pad = { t: 10, r: 10, b: 30, l: 50 };
189
- const cw = w - pad.l - pad.r;
190
- const ch = h - pad.t - pad.b;
191
-
192
- ctx.strokeStyle = color;
193
- ctx.lineWidth = 2;
194
- ctx.beginPath();
195
- points.forEach((v, i) => {
196
- const x = pad.l + (i / (points.length - 1)) * cw;
197
- const y = pad.t + ch - (v / max) * ch;
198
- i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
199
- });
200
- ctx.stroke();
201
-
202
- if (fill) {
203
- ctx.lineTo(pad.l + cw, pad.t + ch);
204
- ctx.lineTo(pad.l, pad.t + ch);
205
- ctx.closePath();
206
- ctx.fillStyle = fill;
207
- ctx.fill();
208
- }
209
-
210
- // Y axis labels
211
- ctx.fillStyle = '#8b8fa3';
212
- ctx.font = '11px -apple-system, sans-serif';
213
- ctx.textAlign = 'right';
214
- for (let i = 0; i <= 4; i++) {
215
- const v = (max / 4) * i;
216
- const y = pad.t + ch - (i / 4) * ch;
217
- ctx.fillText(v >= 1000 ? fmtTok(v) : v < 0.01 ? v.toFixed(4) : v.toFixed(2), pad.l - 6, y + 4);
218
- ctx.strokeStyle = '#2a2d3a';
219
- ctx.lineWidth = 0.5;
220
- ctx.beginPath(); ctx.moveTo(pad.l, y); ctx.lineTo(pad.l + cw, y); ctx.stroke();
221
- }
222
-
223
- // X axis labels (first, middle, last)
224
- ctx.fillStyle = '#8b8fa3';
225
- ctx.textAlign = 'center';
226
- if (data.length > 0) {
227
- const labels = [0, Math.floor(data.length/2), data.length-1];
228
- labels.forEach(i => {
229
- if (i < data.length) {
230
- const x = pad.l + (i / (points.length - 1)) * cw;
231
- ctx.fillText(data[i].date.slice(5), x, pad.t + ch + 18);
232
- }
233
- });
234
- }
235
- }
236
-
237
- function drawBars(ctx, items, w, h, colors) {
238
- if (items.length === 0) return;
239
- const max = Math.max(...items.map(i => i.value), 0.001);
240
- const pad = { t: 10, r: 10, b: 60, l: 60 };
241
- const cw = w - pad.l - pad.r;
242
- const ch = h - pad.t - pad.b;
243
- const barW = Math.min(40, (cw / items.length) * 0.7);
244
- const gap = cw / items.length;
245
-
246
- items.forEach((item, i) => {
247
- const barH = (item.value / max) * ch;
248
- const x = pad.l + i * gap + (gap - barW) / 2;
249
- const y = pad.t + ch - barH;
250
- ctx.fillStyle = colors[i % colors.length];
251
- ctx.beginPath();
252
- ctx.roundRect(x, y, barW, barH, 3);
253
- ctx.fill();
254
-
255
- // Label
256
- ctx.fillStyle = '#8b8fa3';
257
- ctx.font = '11px -apple-system, sans-serif';
258
- ctx.textAlign = 'center';
259
- ctx.save();
260
- ctx.translate(x + barW/2, pad.t + ch + 12);
261
- ctx.rotate(-0.4);
262
- ctx.fillText(item.label.length > 14 ? item.label.slice(0,12) + '..' : item.label, 0, 0);
263
- ctx.restore();
264
-
265
- // Value on top
266
- ctx.fillStyle = '#e1e4ed';
267
- ctx.textAlign = 'center';
268
- ctx.fillText(item.fmt, x + barW/2, y - 6);
269
- });
270
- }
271
-
272
- function drawStacked(ctx, series, labels, w, h, colors) {
273
- if (labels.length === 0) return;
274
- const pad = { t: 10, r: 10, b: 30, l: 50 };
275
- const cw = w - pad.l - pad.r;
276
- const ch = h - pad.t - pad.b;
277
-
278
- // Calculate totals for max
279
- const totals = labels.map((_, i) => series.reduce((s, sr) => s + sr.values[i], 0));
280
- const max = Math.max(...totals, 1);
281
-
282
- const barW = Math.min(30, (cw / labels.length) * 0.7);
283
- const gap = cw / labels.length;
284
-
285
- labels.forEach((_, li) => {
286
- let yOffset = 0;
287
- series.forEach((sr, si) => {
288
- const val = sr.values[li];
289
- const barH = (val / max) * ch;
290
- const x = pad.l + li * gap + (gap - barW) / 2;
291
- const y = pad.t + ch - yOffset - barH;
292
- ctx.fillStyle = colors[si % colors.length];
293
- ctx.fillRect(x, y, barW, barH);
294
- yOffset += barH;
295
- });
296
- });
297
-
298
- // Legend
299
- ctx.font = '11px -apple-system, sans-serif';
300
- series.forEach((sr, i) => {
301
- const x = pad.l + i * 100;
302
- ctx.fillStyle = colors[i % colors.length];
303
- ctx.fillRect(x, h - 16, 10, 10);
304
- ctx.fillStyle = '#8b8fa3';
305
- ctx.textAlign = 'left';
306
- ctx.fillText(sr.name, x + 14, h - 7);
307
- });
308
-
309
- // X axis
310
- ctx.fillStyle = '#8b8fa3';
311
- ctx.textAlign = 'center';
312
- if (data.length > 0) {
313
- [0, Math.floor(labels.length/2), labels.length-1].forEach(i => {
314
- if (i < labels.length) {
315
- const x = pad.l + (i / Math.max(labels.length-1,1)) * cw;
316
- ctx.fillText(labels[i].slice(5), x, pad.t + ch + 18);
317
- }
318
- });
319
- }
320
- }
321
-
322
- function setupCanvas(id) {
323
- const canvas = document.getElementById(id);
324
- const dpr = window.devicePixelRatio || 1;
325
- const rect = canvas.parentElement.getBoundingClientRect();
326
- const w = rect.width - 40; // account for card padding
327
- const h = canvas.height;
328
- canvas.width = w * dpr;
329
- canvas.height = h * dpr;
330
- canvas.style.width = w + 'px';
331
- canvas.style.height = h + 'px';
332
- const ctx = canvas.getContext('2d');
333
- ctx.scale(dpr, dpr);
334
- ctx.clearRect(0, 0, w, h);
335
- return { ctx, w, h };
336
- }
337
-
338
- function renderCostChart() {
339
- const { ctx, w, h } = setupCanvas('costChart');
340
- const points = data.map(d => d.cost);
341
- drawLine(ctx, points, w, h, '#6366f1', 'rgba(99,102,241,0.1)');
342
- }
343
-
344
- function renderTokenChart() {
345
- const { ctx, w, h } = setupCanvas('tokenChart');
346
- const labels = data.map(d => d.date);
347
- drawStacked(ctx, [
348
- { name: 'Input', values: data.map(d => d.input) },
349
- { name: 'Output', values: data.map(d => d.output) },
350
- { name: 'Cache R', values: data.map(d => d.cacheRead) },
351
- { name: 'Cache W', values: data.map(d => d.cacheCreate) },
352
- ], labels, w, h, ['#3b82f6', '#22c55e', '#06b6d4', '#f59e0b']);
353
- }
354
-
355
- function renderAgentChart() {
356
- const { ctx, w, h } = setupCanvas('agentChart');
357
- const agentTotals = {};
358
- data.forEach(d => {
359
- for (const [id, a] of Object.entries(d.agents)) {
360
- agentTotals[id] = (agentTotals[id] || 0) + a.cost;
361
- }
362
- });
363
- const items = Object.entries(agentTotals)
364
- .sort(([,a],[,b]) => b - a)
365
- .slice(0, 10)
366
- .map(([id, cost]) => ({ label: id, value: cost, fmt: fmtCost(cost) }));
367
- drawBars(ctx, items, w, h, ['#6366f1','#3b82f6','#06b6d4','#22c55e','#f59e0b','#ef4444','#a855f7','#ec4899']);
368
- }
369
-
370
- function renderBreakdownChart() {
371
- const { ctx, w, h } = setupCanvas('breakdownChart');
372
- const totals = { input: 0, output: 0, cacheRead: 0, cacheCreate: 0 };
373
- data.forEach(d => { totals.input += d.input; totals.output += d.output; totals.cacheRead += d.cacheRead; totals.cacheCreate += d.cacheCreate; });
374
- const items = [
375
- { label: 'Input', value: totals.input, fmt: fmtTok(totals.input) },
376
- { label: 'Output', value: totals.output, fmt: fmtTok(totals.output) },
377
- { label: 'Cache Read', value: totals.cacheRead, fmt: fmtTok(totals.cacheRead) },
378
- { label: 'Cache Write', value: totals.cacheCreate, fmt: fmtTok(totals.cacheCreate) },
379
- ];
380
- drawBars(ctx, items, w, h, ['#3b82f6','#22c55e','#06b6d4','#f59e0b']);
381
- }
382
-
383
- function renderTable() {
384
- const tbody = document.querySelector('#table tbody');
385
- tbody.innerHTML = data.slice().reverse().map(d => {
386
- const top = Object.entries(d.agents).sort(([,a],[,b]) => b.cost - a.cost)[0];
387
- const topStr = top ? top[0] + ' (' + fmtCost(top[1].cost) + ')' : '-';
388
- return '<tr>' +
389
- '<td>' + d.date + '</td>' +
390
- '<td>' + d.tasks + '</td>' +
391
- '<td class="tok">' + fmtTok(d.input) + '</td>' +
392
- '<td class="tok">' + fmtTok(d.output) + '</td>' +
393
- '<td class="tok">' + fmtTok(d.cacheRead) + '</td>' +
394
- '<td class="tok">' + fmtTok(d.cacheCreate) + '</td>' +
395
- '<td class="cost">' + fmtCost(d.cost) + '</td>' +
396
- '<td>' + topStr + '</td>' +
397
- '</tr>';
398
- }).join('');
399
- }
400
-
401
- // Auto-load last 30 days
402
- setRange(30);
403
- `,W=T;export{W as default,T as startUsageDashboard};
404
- //# sourceMappingURL=usage-dashboard-QX3CF6KK.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/daemon/usage-dashboard.ts"],"sourcesContent":["import { createServer } from \"http\"\nimport { readdirSync } from \"fs\"\nimport { TokenTracker, type DailyUsage, type AgentUsage, type DailyReport } from \"./token-tracker\"\n\n// --- Usage Dashboard: zero-dep web UI for token cost tracking ---\n\nexport function startUsageDashboard(port: number = 4201): void {\n const tracker = new TokenTracker()\n\n const server = createServer(async (req, res) => {\n const url = new URL(req.url || \"/\", `http://localhost:${port}`)\n const path = url.pathname\n\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\")\n\n if (path === \"/api/usage\") {\n const from = url.searchParams.get(\"from\") || \"\"\n const to = url.searchParams.get(\"to\") || \"\"\n const data = loadUsageRange(tracker, from, to)\n res.writeHead(200, { \"Content-Type\": \"application/json\" })\n res.end(JSON.stringify(data))\n return\n }\n\n res.writeHead(200, { \"Content-Type\": \"text/html; charset=utf-8\" })\n res.end(renderDashboard())\n })\n\n server.listen(port, () => {\n console.log(`\\n Usage dashboard: http://localhost:${port}\\n`)\n })\n}\n\n// --- Data loading ---\n\ninterface UsageDay {\n date: string\n tasks: number\n input: number\n output: number\n cacheRead: number\n cacheCreate: number\n cost: number\n agents: Record<string, { tasks: number; cost: number; input: number; output: number; cacheRead: number; cacheCreate: number; model?: string }>\n}\n\nfunction loadUsageRange(tracker: TokenTracker, from: string, to: string): UsageDay[] {\n const dir = tracker[\"dir\"] // access private dir for file listing\n const files: string[] = []\n\n try {\n for (const f of readdirSync(dir)) {\n if (!f.endsWith(\".json\")) continue\n const date = f.replace(\".json\", \"\")\n if (from && date < from) continue\n if (to && date > to) continue\n files.push(date)\n }\n } catch {\n return []\n }\n\n files.sort()\n\n return files.map((date) => {\n const usage = tracker.getDate(date)\n if (!usage) return null\n\n let tasks = 0, input = 0, output = 0, cacheRead = 0, cacheCreate = 0, cost = 0\n const agents: UsageDay[\"agents\"] = {}\n\n for (const [id, agent] of Object.entries(usage.agents)) {\n const agentCost = TokenTracker.calculateCost(agent)\n // Token displays sum tier1 + tier2 so operators see the REAL volume\n // they paid for. Tier2 tokens (from requests >200K input) are billed\n // at 1.5×, but they're still tokens processed — hiding them made\n // low-display/high-cost days look inconsistent on the dashboard.\n const aInput = (agent.inputTokens || 0) + (agent.tier2InputTokens || 0)\n const aOutput = (agent.outputTokens || 0) + (agent.tier2OutputTokens || 0)\n const aCacheRead = (agent.cacheReadTokens || 0) + (agent.tier2CacheReadTokens || 0)\n const aCacheCreate = (agent.cacheCreateTokens || 0) + (agent.tier2CacheCreateTokens || 0)\n tasks += agent.tasks\n input += aInput\n output += aOutput\n cacheRead += aCacheRead\n cacheCreate += aCacheCreate\n cost += agentCost\n agents[id] = {\n tasks: agent.tasks,\n cost: agentCost,\n input: aInput,\n output: aOutput,\n cacheRead: aCacheRead,\n cacheCreate: aCacheCreate,\n model: agent.model,\n }\n }\n\n return { date, tasks, input, output, cacheRead, cacheCreate, cost, agents }\n }).filter((d): d is UsageDay => d !== null && d.tasks > 0)\n}\n\n// --- HTML rendering ---\n\nfunction renderDashboard(): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>AgentX Usage Dashboard</title>\n<style>${CSS}</style>\n</head>\n<body>\n<div class=\"header\">\n <h1>AgentX Usage Dashboard</h1>\n <div class=\"filters\">\n <label>From <input type=\"date\" id=\"from\"></label>\n <label>To <input type=\"date\" id=\"to\"></label>\n <button onclick=\"load()\">Apply</button>\n <button onclick=\"setRange(7)\">7d</button>\n <button onclick=\"setRange(14)\">14d</button>\n <button onclick=\"setRange(30)\">30d</button>\n <button onclick=\"setRange(0)\">All</button>\n </div>\n</div>\n\n<div class=\"stats\" id=\"stats\"></div>\n\n<div class=\"grid\">\n <div class=\"card\">\n <h2>Cost Over Time</h2>\n <canvas id=\"costChart\" height=\"260\"></canvas>\n </div>\n <div class=\"card\">\n <h2>Tokens Over Time</h2>\n <canvas id=\"tokenChart\" height=\"260\"></canvas>\n </div>\n</div>\n\n<div class=\"grid\">\n <div class=\"card\">\n <h2>Cost Per Agent</h2>\n <canvas id=\"agentChart\" height=\"260\"></canvas>\n </div>\n <div class=\"card\">\n <h2>Token Breakdown</h2>\n <canvas id=\"breakdownChart\" height=\"260\"></canvas>\n </div>\n</div>\n\n<div class=\"card\">\n <h2>Daily Detail</h2>\n <table id=\"table\">\n <thead>\n <tr><th>Date</th><th>Tasks</th><th>Input</th><th>Output</th><th>Cache R</th><th>Cache W</th><th>Cost</th><th>Top Agent</th></tr>\n </thead>\n <tbody></tbody>\n </table>\n</div>\n\n<script>${JS}</script>\n</body>\n</html>`\n}\n\n// --- Embedded CSS ---\n\nconst CSS = `\n:root {\n --bg: #0f1117;\n --card: #1a1d27;\n --border: #2a2d3a;\n --text: #e1e4ed;\n --muted: #8b8fa3;\n --accent: #6366f1;\n --green: #22c55e;\n --orange: #f59e0b;\n --red: #ef4444;\n --blue: #3b82f6;\n --purple: #a855f7;\n --cyan: #06b6d4;\n}\n* { margin: 0; padding: 0; box-sizing: border-box; }\nbody { background: var(--bg); color: var(--text); font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; padding: 24px; }\n.header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px; flex-wrap: wrap; gap: 12px; }\nh1 { font-size: 20px; font-weight: 600; }\nh2 { font-size: 14px; font-weight: 500; color: var(--muted); margin-bottom: 16px; text-transform: uppercase; letter-spacing: 0.5px; }\n.filters { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }\n.filters label { font-size: 13px; color: var(--muted); }\n.filters input { background: var(--card); border: 1px solid var(--border); color: var(--text); padding: 6px 10px; border-radius: 6px; font-size: 13px; }\n.filters button { background: var(--card); border: 1px solid var(--border); color: var(--text); padding: 6px 14px; border-radius: 6px; cursor: pointer; font-size: 13px; transition: background 0.15s; }\n.filters button:hover { background: var(--accent); border-color: var(--accent); }\n.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 12px; margin-bottom: 24px; }\n.stat { background: var(--card); border: 1px solid var(--border); border-radius: 10px; padding: 16px; }\n.stat .value { font-size: 24px; font-weight: 700; margin-bottom: 4px; }\n.stat .label { font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; }\n.stat .delta { font-size: 12px; margin-top: 4px; }\n.stat .delta.up { color: var(--red); }\n.stat .delta.down { color: var(--green); }\n.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 16px; }\n@media (max-width: 900px) { .grid { grid-template-columns: 1fr; } }\n.card { background: var(--card); border: 1px solid var(--border); border-radius: 10px; padding: 20px; }\ncanvas { width: 100% !important; }\ntable { width: 100%; border-collapse: collapse; font-size: 13px; }\nth { text-align: left; padding: 8px 12px; border-bottom: 1px solid var(--border); color: var(--muted); font-weight: 500; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; }\ntd { padding: 8px 12px; border-bottom: 1px solid var(--border); }\ntr:hover td { background: rgba(99, 102, 241, 0.05); }\n.cost { color: var(--accent); font-weight: 600; font-variant-numeric: tabular-nums; }\n.tok { font-variant-numeric: tabular-nums; }\n`\n\n// --- Embedded JavaScript ---\n\nconst JS = `\nlet data = [];\n\nfunction fmtTok(n) {\n if (n >= 1e6) return (n/1e6).toFixed(1) + 'M';\n if (n >= 1e3) return (n/1e3).toFixed(0) + 'K';\n return String(n);\n}\nfunction fmtCost(n) { return n < 0.01 ? '$' + n.toFixed(4) : '$' + n.toFixed(2); }\n\nfunction setRange(days) {\n if (days === 0) {\n document.getElementById('from').value = '';\n document.getElementById('to').value = '';\n } else {\n const to = new Date();\n const from = new Date(Date.now() - days * 86400000);\n document.getElementById('from').value = from.toISOString().slice(0,10);\n document.getElementById('to').value = to.toISOString().slice(0,10);\n }\n load();\n}\n\nasync function load() {\n const from = document.getElementById('from').value;\n const to = document.getElementById('to').value;\n const params = new URLSearchParams();\n if (from) params.set('from', from);\n if (to) params.set('to', to);\n const res = await fetch('/api/usage?' + params);\n data = await res.json();\n render();\n}\n\nfunction render() {\n renderStats();\n renderCostChart();\n renderTokenChart();\n renderAgentChart();\n renderBreakdownChart();\n renderTable();\n}\n\nfunction renderStats() {\n const el = document.getElementById('stats');\n const totalCost = data.reduce((s,d) => s + d.cost, 0);\n const totalTasks = data.reduce((s,d) => s + d.tasks, 0);\n const totalInput = data.reduce((s,d) => s + d.input, 0);\n const totalOutput = data.reduce((s,d) => s + d.output, 0);\n const totalCacheR = data.reduce((s,d) => s + d.cacheRead, 0);\n const totalCacheW = data.reduce((s,d) => s + d.cacheCreate, 0);\n const cacheRatio = (totalCacheR + totalCacheW) > 0 ? (totalCacheR / (totalCacheR + totalCacheW) * 100).toFixed(0) : '0';\n const avgDaily = data.length > 0 ? totalCost / data.length : 0;\n\n // Delta: compare last day vs previous\n let delta = '';\n if (data.length >= 2) {\n const last = data[data.length-1].cost;\n const prev = data[data.length-2].cost;\n if (prev > 0) {\n const pct = ((last - prev) / prev * 100).toFixed(0);\n const cls = last > prev ? 'up' : 'down';\n const arrow = last > prev ? '\\\\u2191' : '\\\\u2193';\n delta = '<div class=\"delta ' + cls + '\">' + arrow + ' ' + Math.abs(pct) + '% vs prev day</div>';\n }\n }\n\n el.innerHTML =\n stat(fmtCost(totalCost), 'Total Cost', delta) +\n stat(fmtCost(avgDaily), 'Avg Daily Cost', '') +\n stat(totalTasks.toLocaleString(), 'Total Tasks', '') +\n stat(fmtTok(totalInput + totalOutput), 'Tokens (I/O)', '') +\n stat(fmtTok(totalCacheR), 'Cache Reads', '') +\n stat(cacheRatio + '%', 'Cache Hit Rate', '') +\n stat(data.length.toString(), 'Days Tracked', '');\n}\n\nfunction stat(value, label, extra) {\n return '<div class=\"stat\"><div class=\"value\">' + value + '</div><div class=\"label\">' + label + '</div>' + extra + '</div>';\n}\n\n// --- Canvas chart helpers ---\nfunction drawLine(ctx, points, w, h, color, fill) {\n if (points.length < 2) return;\n const max = Math.max(...points, 0.001);\n const pad = { t: 10, r: 10, b: 30, l: 50 };\n const cw = w - pad.l - pad.r;\n const ch = h - pad.t - pad.b;\n\n ctx.strokeStyle = color;\n ctx.lineWidth = 2;\n ctx.beginPath();\n points.forEach((v, i) => {\n const x = pad.l + (i / (points.length - 1)) * cw;\n const y = pad.t + ch - (v / max) * ch;\n i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);\n });\n ctx.stroke();\n\n if (fill) {\n ctx.lineTo(pad.l + cw, pad.t + ch);\n ctx.lineTo(pad.l, pad.t + ch);\n ctx.closePath();\n ctx.fillStyle = fill;\n ctx.fill();\n }\n\n // Y axis labels\n ctx.fillStyle = '#8b8fa3';\n ctx.font = '11px -apple-system, sans-serif';\n ctx.textAlign = 'right';\n for (let i = 0; i <= 4; i++) {\n const v = (max / 4) * i;\n const y = pad.t + ch - (i / 4) * ch;\n ctx.fillText(v >= 1000 ? fmtTok(v) : v < 0.01 ? v.toFixed(4) : v.toFixed(2), pad.l - 6, y + 4);\n ctx.strokeStyle = '#2a2d3a';\n ctx.lineWidth = 0.5;\n ctx.beginPath(); ctx.moveTo(pad.l, y); ctx.lineTo(pad.l + cw, y); ctx.stroke();\n }\n\n // X axis labels (first, middle, last)\n ctx.fillStyle = '#8b8fa3';\n ctx.textAlign = 'center';\n if (data.length > 0) {\n const labels = [0, Math.floor(data.length/2), data.length-1];\n labels.forEach(i => {\n if (i < data.length) {\n const x = pad.l + (i / (points.length - 1)) * cw;\n ctx.fillText(data[i].date.slice(5), x, pad.t + ch + 18);\n }\n });\n }\n}\n\nfunction drawBars(ctx, items, w, h, colors) {\n if (items.length === 0) return;\n const max = Math.max(...items.map(i => i.value), 0.001);\n const pad = { t: 10, r: 10, b: 60, l: 60 };\n const cw = w - pad.l - pad.r;\n const ch = h - pad.t - pad.b;\n const barW = Math.min(40, (cw / items.length) * 0.7);\n const gap = cw / items.length;\n\n items.forEach((item, i) => {\n const barH = (item.value / max) * ch;\n const x = pad.l + i * gap + (gap - barW) / 2;\n const y = pad.t + ch - barH;\n ctx.fillStyle = colors[i % colors.length];\n ctx.beginPath();\n ctx.roundRect(x, y, barW, barH, 3);\n ctx.fill();\n\n // Label\n ctx.fillStyle = '#8b8fa3';\n ctx.font = '11px -apple-system, sans-serif';\n ctx.textAlign = 'center';\n ctx.save();\n ctx.translate(x + barW/2, pad.t + ch + 12);\n ctx.rotate(-0.4);\n ctx.fillText(item.label.length > 14 ? item.label.slice(0,12) + '..' : item.label, 0, 0);\n ctx.restore();\n\n // Value on top\n ctx.fillStyle = '#e1e4ed';\n ctx.textAlign = 'center';\n ctx.fillText(item.fmt, x + barW/2, y - 6);\n });\n}\n\nfunction drawStacked(ctx, series, labels, w, h, colors) {\n if (labels.length === 0) return;\n const pad = { t: 10, r: 10, b: 30, l: 50 };\n const cw = w - pad.l - pad.r;\n const ch = h - pad.t - pad.b;\n\n // Calculate totals for max\n const totals = labels.map((_, i) => series.reduce((s, sr) => s + sr.values[i], 0));\n const max = Math.max(...totals, 1);\n\n const barW = Math.min(30, (cw / labels.length) * 0.7);\n const gap = cw / labels.length;\n\n labels.forEach((_, li) => {\n let yOffset = 0;\n series.forEach((sr, si) => {\n const val = sr.values[li];\n const barH = (val / max) * ch;\n const x = pad.l + li * gap + (gap - barW) / 2;\n const y = pad.t + ch - yOffset - barH;\n ctx.fillStyle = colors[si % colors.length];\n ctx.fillRect(x, y, barW, barH);\n yOffset += barH;\n });\n });\n\n // Legend\n ctx.font = '11px -apple-system, sans-serif';\n series.forEach((sr, i) => {\n const x = pad.l + i * 100;\n ctx.fillStyle = colors[i % colors.length];\n ctx.fillRect(x, h - 16, 10, 10);\n ctx.fillStyle = '#8b8fa3';\n ctx.textAlign = 'left';\n ctx.fillText(sr.name, x + 14, h - 7);\n });\n\n // X axis\n ctx.fillStyle = '#8b8fa3';\n ctx.textAlign = 'center';\n if (data.length > 0) {\n [0, Math.floor(labels.length/2), labels.length-1].forEach(i => {\n if (i < labels.length) {\n const x = pad.l + (i / Math.max(labels.length-1,1)) * cw;\n ctx.fillText(labels[i].slice(5), x, pad.t + ch + 18);\n }\n });\n }\n}\n\nfunction setupCanvas(id) {\n const canvas = document.getElementById(id);\n const dpr = window.devicePixelRatio || 1;\n const rect = canvas.parentElement.getBoundingClientRect();\n const w = rect.width - 40; // account for card padding\n const h = canvas.height;\n canvas.width = w * dpr;\n canvas.height = h * dpr;\n canvas.style.width = w + 'px';\n canvas.style.height = h + 'px';\n const ctx = canvas.getContext('2d');\n ctx.scale(dpr, dpr);\n ctx.clearRect(0, 0, w, h);\n return { ctx, w, h };\n}\n\nfunction renderCostChart() {\n const { ctx, w, h } = setupCanvas('costChart');\n const points = data.map(d => d.cost);\n drawLine(ctx, points, w, h, '#6366f1', 'rgba(99,102,241,0.1)');\n}\n\nfunction renderTokenChart() {\n const { ctx, w, h } = setupCanvas('tokenChart');\n const labels = data.map(d => d.date);\n drawStacked(ctx, [\n { name: 'Input', values: data.map(d => d.input) },\n { name: 'Output', values: data.map(d => d.output) },\n { name: 'Cache R', values: data.map(d => d.cacheRead) },\n { name: 'Cache W', values: data.map(d => d.cacheCreate) },\n ], labels, w, h, ['#3b82f6', '#22c55e', '#06b6d4', '#f59e0b']);\n}\n\nfunction renderAgentChart() {\n const { ctx, w, h } = setupCanvas('agentChart');\n const agentTotals = {};\n data.forEach(d => {\n for (const [id, a] of Object.entries(d.agents)) {\n agentTotals[id] = (agentTotals[id] || 0) + a.cost;\n }\n });\n const items = Object.entries(agentTotals)\n .sort(([,a],[,b]) => b - a)\n .slice(0, 10)\n .map(([id, cost]) => ({ label: id, value: cost, fmt: fmtCost(cost) }));\n drawBars(ctx, items, w, h, ['#6366f1','#3b82f6','#06b6d4','#22c55e','#f59e0b','#ef4444','#a855f7','#ec4899']);\n}\n\nfunction renderBreakdownChart() {\n const { ctx, w, h } = setupCanvas('breakdownChart');\n const totals = { input: 0, output: 0, cacheRead: 0, cacheCreate: 0 };\n data.forEach(d => { totals.input += d.input; totals.output += d.output; totals.cacheRead += d.cacheRead; totals.cacheCreate += d.cacheCreate; });\n const items = [\n { label: 'Input', value: totals.input, fmt: fmtTok(totals.input) },\n { label: 'Output', value: totals.output, fmt: fmtTok(totals.output) },\n { label: 'Cache Read', value: totals.cacheRead, fmt: fmtTok(totals.cacheRead) },\n { label: 'Cache Write', value: totals.cacheCreate, fmt: fmtTok(totals.cacheCreate) },\n ];\n drawBars(ctx, items, w, h, ['#3b82f6','#22c55e','#06b6d4','#f59e0b']);\n}\n\nfunction renderTable() {\n const tbody = document.querySelector('#table tbody');\n tbody.innerHTML = data.slice().reverse().map(d => {\n const top = Object.entries(d.agents).sort(([,a],[,b]) => b.cost - a.cost)[0];\n const topStr = top ? top[0] + ' (' + fmtCost(top[1].cost) + ')' : '-';\n return '<tr>' +\n '<td>' + d.date + '</td>' +\n '<td>' + d.tasks + '</td>' +\n '<td class=\"tok\">' + fmtTok(d.input) + '</td>' +\n '<td class=\"tok\">' + fmtTok(d.output) + '</td>' +\n '<td class=\"tok\">' + fmtTok(d.cacheRead) + '</td>' +\n '<td class=\"tok\">' + fmtTok(d.cacheCreate) + '</td>' +\n '<td class=\"cost\">' + fmtCost(d.cost) + '</td>' +\n '<td>' + topStr + '</td>' +\n '</tr>';\n }).join('');\n}\n\n// Auto-load last 30 days\nsetRange(30);\n`\n\nexport default startUsageDashboard\n"],"mappings":"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A;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA8SJC,EAAQrC","names":["createServer","readdirSync","startUsageDashboard","port","tracker","TokenTracker","createServer","req","res","url","path","from","to","data","loadUsageRange","renderDashboard","dir","files","f","readdirSync","date","usage","tasks","input","output","cacheRead","cacheCreate","cost","agents","id","agent","agentCost","aInput","aOutput","aCacheRead","aCacheCreate","d","CSS","JS","usage_dashboard_default"]}
@@ -1,2 +0,0 @@
1
- import{a,b}from"./chunk-QZP65I2H.js";import"./chunk-HI24KAEY.js";import"./chunk-FIOB3MEX.js";export{b as setupAllWorkspaces,a as setupWorkspace};
2
- //# sourceMappingURL=workspace-setup-ZWTT42XT.js.map