From 8125cbf7b1aecc01a8be6a7b60ef593b61dd0d7c Mon Sep 17 00:00:00 2001 From: unohee Date: Thu, 10 Sep 2026 16:58:04 +0900 Subject: [PATCH] =?UTF-8?q?feat(web):=20usage=20dashboard=20=E2=80=94=20pu?= =?UTF-8?q?t=20the=20token/cost=20ledger=20on=20a=20screen=20(AGT-4289)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `/api/usage` has served six axes (model · stage · task · project · adapter · day) since AGT-4178 and had no frontend reader; the only consumer was the `openswarm cost` CLI. The dashboard's sole cost display was an SSE counter that accumulated within a session and reset on refresh. Two defects found while this was being written were each a single number on an axis the endpoint already served, and both were found by hand-querying the ledger over ssh rather than by looking at anything: - AGT-4286: the draft stage at a 30.5% cache hit rate while every other stage sat at 83-88%. - AGT-4288: 13,287 draft calls across 239 tasks — 55.6 per task. The endpoint groups by ONE axis per request, so the page fetches several in parallel rather than changing the aggregate. No backend change. The derived columns are the point. Cache rate, cost per call and calls per task do not exist in the rows the API returns, and they are what carry the signal: on the ledger this was written against, `qwen3-235b` shows a 0.0% cache rate beside `deepseek-v4-flash` at 82.7%. Numbers the page refuses to state rather than state wrongly: - a rate with no prompt tokens behind it, and a share with no cost to divide, render as `—`. Printing 0.0% would put an unmetered row beside the rows that genuinely cost nothing relative to their peers — which is the exact comparison this page exists to support. - the unattributed bucket is not a task, so it is excluded from the task ranking and from calls-per-task, and the note above that table says how many calls and how much cost were left out. - rows past the row cap are counted and priced in a note, because the project axis returns 67 rows over 30 days and the panel draws 25. Also collapses six duplicated page-shell route blocks in webAppRoutes.ts into a PAGE_SHELLS table plus tryServePageShell, which is the first test coverage any of those six routes has had, and widens `lint` to cover web/static/js — that directory had never been linted by any gate in this repo, which is how a 412-line new file reached round three with dead code still in it. Widening it surfaces 15 pre-existing warnings in other files; oxlint still exits 0. Layout is measured, not reasoned: `auto-fit` answers a higher floor with more tracks rather than wider ones, so a first attempt at the column-clipping fix looked right and left 비용 off-screen at every desktop width. The floor is now wider than the table it holds, and tests/web/usageLayout.test.ts is a tripwire on the two declarations that were wrong before, since jsdom computes no layout and no colour. Co-Authored-By: Claude Opus 5 (1M context) --- package.json | 2 +- src/support/dashboardHtml.ts | 1 + src/support/staticAssets.test.ts | 11 +- src/support/staticAssets.ts | 5 + src/support/webAppRoutes.test.ts | 67 ++++ src/support/webAppRoutes.ts | 99 ++--- tests/web/tokens.test.ts | 2 +- tests/web/usage.test.ts | 624 +++++++++++++++++++++++++++++++ tests/web/usageLayout.test.ts | 61 +++ web/static/app.html | 1 + web/static/chat.html | 1 + web/static/css/usage.css | 123 ++++++ web/static/js/usage.mjs | 444 ++++++++++++++++++++++ web/static/orchestration.html | 1 + web/static/threads.html | 1 + web/static/usage.html | 101 +++++ web/static/warehouse.html | 1 + 17 files changed, 1483 insertions(+), 62 deletions(-) create mode 100644 src/support/webAppRoutes.test.ts create mode 100644 tests/web/usage.test.ts create mode 100644 tests/web/usageLayout.test.ts create mode 100644 web/static/css/usage.css create mode 100644 web/static/js/usage.mjs create mode 100644 web/static/usage.html diff --git a/package.json b/package.json index 208ad3a3..012cd2ed 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "postbuild": "chmod +x dist/cli.js && node -e \"const fs=require('node:fs');fs.rmSync('dist/web-static',{recursive:true,force:true});fs.cpSync('web/static','dist/web-static',{recursive:true})\"", "start": "node --env-file=.env dist/index.js", "stop": "pkill -f 'node --env-file=.env.*openswarm' 2>/dev/null || echo 'No process running'", - "lint": "oxlint src/", + "lint": "oxlint src/ web/static/js/", "typecheck": "tsc --noEmit -p tsconfig.check.json", "test": "node --experimental-vm-modules node_modules/vitest/vitest.mjs run", "test:coverage": "node --experimental-vm-modules node_modules/vitest/vitest.mjs run --coverage", diff --git a/src/support/dashboardHtml.ts b/src/support/dashboardHtml.ts index 85a81110..2fba8f79 100644 --- a/src/support/dashboardHtml.ts +++ b/src/support/dashboardHtml.ts @@ -22,6 +22,7 @@ const DASHBOARD_HTML = ` Orchestration Threads Warehouse + Usage Issues
diff --git a/src/support/staticAssets.test.ts b/src/support/staticAssets.test.ts index b26a0a33..6c4761e8 100644 --- a/src/support/staticAssets.test.ts +++ b/src/support/staticAssets.test.ts @@ -3,7 +3,8 @@ import { mkdtempSync, mkdirSync, writeFileSync, symlinkSync, rmSync } from 'node import { join } from 'node:path'; import { tmpdir } from 'node:os'; import { - contentTypeFor, readAppShell, readStaticAsset, readThreadBoardShell, resolveStaticRoot, StaticAssetError, + contentTypeFor, readAppShell, readStaticAsset, readThreadBoardShell, readUsageShell, + resolveStaticRoot, StaticAssetError, } from './staticAssets.js'; describe('contentTypeFor', () => { @@ -26,6 +27,14 @@ describe('resolveStaticRoot', () => { it('ships the durable repository thread shell', async () => { expect((await readThreadBoardShell())?.toString()).toContain('Repository threads'); }); + + it('ships the usage shell, wired to its module (AGT-4289)', async () => { + // A shell that loses its + + + + + + +
+ 🐝 OpenSwarm + Usage + +
+
+ + + + +
+
+ +
+

불러오는 중…

+ +
+
비용
+
호출
+
토큰
+
캐시 적중률cached / prompt
+
+ +
+

일별

+
+
+ +
+
+

모델

+

+
+
+
+

스테이지

+

+
+
+
+

어댑터

+

+
+
+
+

프로젝트

+

+
+
+
+ +
+

작업별 — 호출이 많은 순

+

+
+
+ +
+

원격 접근

+
+

로컬이 아닌 브라우저에서는 배포 시 설정한 웹 토큰이 필요합니다.

+ +
+ + +
+ +
+
+
+ + + + diff --git a/web/static/warehouse.html b/web/static/warehouse.html index 8e5a940b..ca8642b4 100644 --- a/web/static/warehouse.html +++ b/web/static/warehouse.html @@ -20,6 +20,7 @@ Orchestration Threads Warehouse + Usage Supervisor