@desplega.ai/agent-swarm 1.20.0 → 1.51.2
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 +271 -169
- package/openapi.json +5015 -0
- package/package.json +40 -7
- package/plugin/commands/close-issue.md +7 -3
- package/plugin/commands/create-pr.md +18 -12
- package/plugin/commands/implement-issue.md +7 -3
- package/plugin/commands/respond-github.md +8 -4
- package/plugin/commands/review-pr.md +44 -10
- package/plugin/commands/start-leader.md +1 -3
- package/plugin/commands/start-worker.md +1 -3
- package/plugin/commands/work-on-task.md +22 -3
- package/plugin/pi-skills/close-issue/SKILL.md +90 -0
- package/plugin/pi-skills/create-pr/SKILL.md +99 -0
- package/plugin/pi-skills/implement-issue/SKILL.md +135 -0
- package/plugin/pi-skills/investigate-sentry-issue/SKILL.md +138 -0
- package/plugin/pi-skills/respond-github/SKILL.md +98 -0
- package/plugin/pi-skills/review-offered-task/SKILL.md +45 -0
- package/plugin/pi-skills/review-pr/SKILL.md +261 -0
- package/plugin/pi-skills/start-leader/SKILL.md +121 -0
- package/plugin/pi-skills/start-worker/SKILL.md +60 -0
- package/plugin/pi-skills/swarm-chat/SKILL.md +82 -0
- package/plugin/pi-skills/todos/SKILL.md +66 -0
- package/plugin/pi-skills/work-on-task/SKILL.md +65 -0
- package/plugin/skills/artifacts/examples/approval-flow.ts +34 -0
- package/plugin/skills/artifacts/examples/hono-dashboard.ts +31 -0
- package/plugin/skills/artifacts/examples/multi-artifact.ts +20 -0
- package/plugin/skills/artifacts/examples/static-report.sh +17 -0
- package/plugin/skills/artifacts/skill.md +71 -0
- package/src/agentmail/app.ts +65 -0
- package/src/agentmail/handlers.ts +262 -0
- package/src/agentmail/index.ts +9 -0
- package/src/agentmail/templates.ts +111 -0
- package/src/agentmail/types.ts +51 -0
- package/src/artifact-sdk/browser-sdk.ts +30 -0
- package/src/artifact-sdk/index.ts +2 -0
- package/src/artifact-sdk/localtunnel.d.ts +20 -0
- package/src/artifact-sdk/port.ts +12 -0
- package/src/artifact-sdk/server.ts +156 -0
- package/src/artifact-sdk/tunnel.ts +19 -0
- package/src/be/chunking.ts +193 -0
- package/src/be/db-queries/oauth.ts +90 -0
- package/src/be/db-queries/tracker.ts +182 -0
- package/src/be/db.ts +3327 -784
- package/src/be/embedding.ts +80 -0
- package/src/be/migrations/001_initial.sql +409 -0
- package/src/be/migrations/002_one_time_schedules.sql +59 -0
- package/src/be/migrations/003_workflows.sql +51 -0
- package/src/be/migrations/004_workflow_source.sql +81 -0
- package/src/be/migrations/005_epic_next_steps.sql +2 -0
- package/src/be/migrations/006_vcs_provider.sql +94 -0
- package/src/be/migrations/007_task_dir.sql +2 -0
- package/src/be/migrations/008_workflow_redesign.sql +85 -0
- package/src/be/migrations/009_tracker_integration.sql +144 -0
- package/src/be/migrations/010_step_diagnostics.sql +1 -0
- package/src/be/migrations/011_step_next_port.sql +1 -0
- package/src/be/migrations/012_trigger_schema.sql +1 -0
- package/src/be/migrations/013_task_output_schema.sql +2 -0
- package/src/be/migrations/014_prompt_templates.sql +33 -0
- package/src/be/migrations/015_workflow_workspace.sql +3 -0
- package/src/be/migrations/016_active_session_runner_session.sql +4 -0
- package/src/be/migrations/017_channel_activity_cursors.sql +6 -0
- package/src/be/migrations/018_fix_seed_double_version.sql +30 -0
- package/src/be/migrations/runner.ts +188 -0
- package/src/be/seed.ts +62 -0
- package/src/cli.tsx +231 -299
- package/src/commands/artifact.ts +241 -0
- package/src/commands/onboard/compose-generator.ts +169 -0
- package/src/commands/onboard/env-generator.ts +79 -0
- package/src/commands/onboard/manifest.ts +37 -0
- package/src/commands/onboard/presets.ts +85 -0
- package/src/commands/onboard/service-names.ts +47 -0
- package/src/commands/onboard/steps/core-credentials.tsx +111 -0
- package/src/commands/onboard/steps/custom-templates.tsx +168 -0
- package/src/commands/onboard/steps/generate.tsx +154 -0
- package/src/commands/onboard/steps/harness-credentials.tsx +195 -0
- package/src/commands/onboard/steps/harness.tsx +21 -0
- package/src/commands/onboard/steps/health-check.tsx +171 -0
- package/src/commands/onboard/steps/integration-github.tsx +105 -0
- package/src/commands/onboard/steps/integration-gitlab.tsx +79 -0
- package/src/commands/onboard/steps/integration-menu.tsx +58 -0
- package/src/commands/onboard/steps/integration-sentry.tsx +79 -0
- package/src/commands/onboard/steps/integration-slack.tsx +165 -0
- package/src/commands/onboard/steps/post-connect.tsx +145 -0
- package/src/commands/onboard/steps/post-dashboard.tsx +34 -0
- package/src/commands/onboard/steps/post-task.tsx +103 -0
- package/src/commands/onboard/steps/prereq-check.tsx +178 -0
- package/src/commands/onboard/steps/review.tsx +82 -0
- package/src/commands/onboard/steps/start.tsx +97 -0
- package/src/commands/onboard/templates.ts +34 -0
- package/src/commands/onboard/types.ts +259 -0
- package/src/commands/onboard.tsx +425 -0
- package/src/commands/runner.ts +1540 -630
- package/src/commands/setup.tsx +23 -38
- package/src/commands/shared/client-config.ts +41 -0
- package/src/commands/templates.ts +172 -0
- package/src/github/app.ts +8 -0
- package/src/github/handlers.ts +384 -151
- package/src/github/index.ts +1 -0
- package/src/github/mentions-aliases.test.ts +73 -0
- package/src/github/mentions.test.ts +3 -3
- package/src/github/mentions.ts +32 -6
- package/src/github/templates.ts +398 -0
- package/src/github/types.ts +1 -0
- package/src/gitlab/auth.ts +63 -0
- package/src/gitlab/handlers.ts +368 -0
- package/src/gitlab/index.ts +19 -0
- package/src/gitlab/reactions.ts +104 -0
- package/src/gitlab/templates.ts +140 -0
- package/src/gitlab/types.ts +130 -0
- package/src/heartbeat/heartbeat.ts +434 -0
- package/src/heartbeat/index.ts +1 -0
- package/src/heartbeat/templates.ts +30 -0
- package/src/hooks/hook.ts +555 -4
- package/src/hooks/tool-loop-detection.test.ts +158 -0
- package/src/hooks/tool-loop-detection.ts +167 -0
- package/src/http/active-sessions.ts +199 -0
- package/src/http/agents.ts +328 -0
- package/src/http/config.ts +191 -0
- package/src/http/core.ts +309 -0
- package/src/http/db-query.ts +91 -0
- package/src/http/ecosystem.ts +63 -0
- package/src/http/epics.ts +460 -0
- package/src/http/index.ts +216 -0
- package/src/http/mcp.ts +77 -0
- package/src/http/memory.ts +168 -0
- package/src/http/openapi.ts +109 -0
- package/src/http/poll.ts +299 -0
- package/src/http/prompt-templates.ts +412 -0
- package/src/http/repos.ts +195 -0
- package/src/http/route-def.ts +123 -0
- package/src/http/schedules.ts +426 -0
- package/src/http/session-data.ts +241 -0
- package/src/http/stats.ts +174 -0
- package/src/http/tasks.ts +468 -0
- package/src/http/trackers/index.ts +10 -0
- package/src/http/trackers/linear.ts +187 -0
- package/src/http/types.ts +12 -0
- package/src/http/utils.ts +87 -0
- package/src/http/webhooks.ts +432 -0
- package/src/http/workflows.ts +530 -0
- package/src/http.ts +1 -1890
- package/src/linear/README.md +65 -0
- package/src/linear/app.ts +48 -0
- package/src/linear/client.ts +18 -0
- package/src/linear/index.ts +1 -0
- package/src/linear/oauth.ts +35 -0
- package/src/linear/outbound.ts +212 -0
- package/src/linear/sync.ts +567 -0
- package/src/linear/templates.ts +47 -0
- package/src/linear/types.ts +7 -0
- package/src/linear/webhook.ts +104 -0
- package/src/oauth/README.md +66 -0
- package/src/oauth/index.ts +6 -0
- package/src/oauth/wrapper.ts +204 -0
- package/src/prompts/base-prompt.ts +150 -265
- package/src/prompts/defaults.ts +196 -0
- package/src/prompts/registry.ts +57 -0
- package/src/prompts/resolver.ts +296 -0
- package/src/prompts/session-templates.ts +604 -0
- package/src/providers/claude-adapter.ts +442 -0
- package/src/providers/index.ts +24 -0
- package/src/providers/pi-mono-adapter.ts +442 -0
- package/src/providers/pi-mono-extension.ts +624 -0
- package/src/providers/pi-mono-mcp-client.ts +124 -0
- package/src/providers/types.ts +75 -0
- package/src/scheduler/scheduler.test.ts +2 -0
- package/src/scheduler/scheduler.ts +231 -40
- package/src/server.ts +97 -6
- package/src/slack/HEURISTICS.md +105 -0
- package/src/slack/actions.ts +133 -0
- package/src/slack/app.ts +7 -0
- package/src/slack/assistant.ts +118 -0
- package/src/slack/blocks.ts +233 -0
- package/src/slack/channel-activity.ts +177 -0
- package/src/slack/commands.ts +31 -17
- package/src/slack/files.ts +1 -1
- package/src/slack/handlers.test.ts +114 -1
- package/src/slack/handlers.ts +230 -55
- package/src/slack/responses.ts +120 -67
- package/src/slack/router.ts +17 -99
- package/src/slack/templates.ts +55 -0
- package/src/slack/thread-buffer.ts +213 -0
- package/src/slack/watcher.ts +119 -4
- package/src/tests/agent-activity.test.ts +247 -0
- package/src/tests/agentmail-filters.test.ts +97 -0
- package/src/tests/artifact-sdk.test.ts +800 -0
- package/src/tests/base-prompt.test.ts +264 -0
- package/src/tests/build-pi-skills.test.ts +127 -0
- package/src/tests/channel-activity.test.ts +363 -0
- package/src/tests/claude-adapter.test.ts +126 -0
- package/src/tests/context-versioning.test.ts +425 -0
- package/src/tests/db-queries-oauth.test.ts +197 -0
- package/src/tests/db-queries-tracker.test.ts +230 -0
- package/src/tests/epics.test.ts +3 -3
- package/src/tests/error-tracker.test.ts +368 -0
- package/src/tests/fetch-resolved-env.test.ts +167 -0
- package/src/tests/generate-default-claude-md.test.ts +9 -1
- package/src/tests/generate-identity-templates.test.ts +124 -0
- package/src/tests/gitlab-auth.test.ts +109 -0
- package/src/tests/gitlab-handlers.test.ts +691 -0
- package/src/tests/gitlab-vcs-db.test.ts +177 -0
- package/src/tests/heartbeat.test.ts +364 -0
- package/src/tests/http-api-integration.test.ts +1698 -0
- package/src/tests/linear-outbound-sync.test.ts +200 -0
- package/src/tests/linear-webhook.test.ts +406 -0
- package/src/tests/match-route.test.ts +187 -0
- package/src/tests/memory.test.ts +737 -0
- package/src/tests/migration-runner-regressions.test.ts +86 -0
- package/src/tests/model-control.test.ts +338 -0
- package/src/tests/oauth-wrapper.test.ts +147 -0
- package/src/tests/onboard-compose.test.ts +138 -0
- package/src/tests/onboard-env.test.ts +174 -0
- package/src/tests/onboard-manifest.test.ts +137 -0
- package/src/tests/pi-mono-adapter.test.ts +234 -0
- package/src/tests/pool-session-logs.test.ts +199 -0
- package/src/tests/progress-dedup.test.ts +98 -0
- package/src/tests/prompt-template-github.test.ts +682 -0
- package/src/tests/prompt-template-remaining.test.ts +504 -0
- package/src/tests/prompt-template-resolver.test.ts +621 -0
- package/src/tests/prompt-template-session.test.ts +363 -0
- package/src/tests/prompt-templates-db.test.ts +616 -0
- package/src/tests/provider-adapter.test.ts +122 -0
- package/src/tests/provider-command-format.test.ts +98 -0
- package/src/tests/reload-config.test.ts +170 -0
- package/src/tests/runner-polling-api.test.ts +25 -20
- package/src/tests/scheduled-tasks.test.ts +104 -0
- package/src/tests/scheduler-backoff.test.ts +166 -0
- package/src/tests/self-improvement.test.ts +541 -0
- package/src/tests/session-attach.test.ts +536 -0
- package/src/tests/session-costs.test.ts +267 -1
- package/src/tests/slack-actions.test.ts +133 -0
- package/src/tests/slack-assistant.test.ts +136 -0
- package/src/tests/slack-blocks.test.ts +246 -0
- package/src/tests/slack-metadata-inheritance.test.ts +243 -0
- package/src/tests/slack-queue-offline.test.ts +174 -0
- package/src/tests/slack-router.test.ts +181 -0
- package/src/tests/slack-thread-buffer.test.ts +305 -0
- package/src/tests/slack-thread-followups.test.ts +298 -0
- package/src/tests/slack-watcher.test.ts +101 -0
- package/src/tests/structured-output.test.ts +307 -0
- package/src/tests/swarm-repos.test.ts +198 -0
- package/src/tests/task-cancellation.test.ts +6 -4
- package/src/tests/task-working-dir.test.ts +176 -0
- package/src/tests/template-fetch.test.ts +490 -0
- package/src/tests/tool-annotations.test.ts +371 -0
- package/src/tests/tracker-tools.test.ts +184 -0
- package/src/tests/update-profile-agentid.test.ts +248 -0
- package/src/tests/update-profile-api.test.ts +143 -3
- package/src/tests/update-profile-auth.test.ts +195 -0
- package/src/tests/validation-adapters.test.ts +86 -0
- package/src/tests/vcs-provider.test.ts +27 -0
- package/src/tests/workflow-agent-task.test.ts +196 -0
- package/src/tests/workflow-async-v2.test.ts +508 -0
- package/src/tests/workflow-convergence.test.ts +541 -0
- package/src/tests/workflow-definition-validation.test.ts +366 -0
- package/src/tests/workflow-engine-v2.test.ts +691 -0
- package/src/tests/workflow-executors.test.ts +736 -0
- package/src/tests/workflow-http-v2.test.ts +599 -0
- package/src/tests/workflow-integration-io.test.ts +902 -0
- package/src/tests/workflow-io-schemas.test.ts +624 -0
- package/src/tests/workflow-registry.test.ts +592 -0
- package/src/tests/workflow-retry-v2.test.ts +401 -0
- package/src/tests/workflow-retry-validation.test.ts +282 -0
- package/src/tests/workflow-schedule-trigger.test.ts +104 -0
- package/src/tests/workflow-template.test.ts +288 -0
- package/src/tests/workflow-trigger-schema.test.ts +359 -0
- package/src/tests/workflow-triggers-v2.test.ts +264 -0
- package/src/tests/workflow-versions.test.ts +208 -0
- package/src/tests/workflow-workspace.test.ts +272 -0
- package/src/tests/x402-client.test.ts +117 -0
- package/src/tests/x402-config.test.ts +182 -0
- package/src/tests/x402-spending-tracker.test.ts +185 -0
- package/src/tools/cancel-task.ts +2 -0
- package/src/tools/context-diff.ts +171 -0
- package/src/tools/context-history.ts +138 -0
- package/src/tools/create-channel.ts +1 -0
- package/src/tools/db-query.ts +78 -0
- package/src/tools/delete-channel.ts +132 -0
- package/src/tools/epics/assign-task-to-epic.ts +1 -0
- package/src/tools/epics/create-epic.ts +3 -2
- package/src/tools/epics/delete-epic.ts +2 -0
- package/src/tools/epics/get-epic-details.ts +2 -0
- package/src/tools/epics/list-epics.ts +2 -0
- package/src/tools/epics/unassign-task-from-epic.ts +1 -0
- package/src/tools/epics/update-epic.ts +7 -4
- package/src/tools/get-swarm.ts +2 -0
- package/src/tools/get-task-details.ts +2 -0
- package/src/tools/get-tasks.ts +27 -1
- package/src/tools/inject-learning.ts +106 -0
- package/src/tools/join-swarm.ts +17 -7
- package/src/tools/list-channels.ts +2 -0
- package/src/tools/list-services.ts +2 -0
- package/src/tools/memory-get.ts +56 -0
- package/src/tools/memory-search.ts +131 -0
- package/src/tools/my-agent-info.ts +2 -0
- package/src/tools/poll-task.ts +2 -20
- package/src/tools/post-message.ts +1 -0
- package/src/tools/prompt-templates/delete.ts +86 -0
- package/src/tools/prompt-templates/get.ts +89 -0
- package/src/tools/prompt-templates/index.ts +5 -0
- package/src/tools/prompt-templates/list.ts +95 -0
- package/src/tools/prompt-templates/preview.ts +84 -0
- package/src/tools/prompt-templates/set.ts +117 -0
- package/src/tools/read-messages.ts +2 -0
- package/src/tools/register-agentmail-inbox.ts +166 -0
- package/src/tools/register-service.ts +2 -0
- package/src/tools/schedules/create-schedule.ts +134 -24
- package/src/tools/schedules/delete-schedule.ts +2 -0
- package/src/tools/schedules/list-schedules.ts +20 -4
- package/src/tools/schedules/run-schedule-now.ts +1 -0
- package/src/tools/schedules/update-schedule.ts +49 -17
- package/src/tools/send-task.ts +132 -10
- package/src/tools/slack-download-file.ts +4 -2
- package/src/tools/slack-list-channels.ts +2 -0
- package/src/tools/slack-post.ts +2 -0
- package/src/tools/slack-read.ts +2 -0
- package/src/tools/slack-reply.ts +2 -0
- package/src/tools/slack-upload-file.ts +2 -0
- package/src/tools/store-progress.ts +205 -4
- package/src/tools/swarm-config/delete-config.ts +87 -0
- package/src/tools/swarm-config/get-config.ts +108 -0
- package/src/tools/swarm-config/index.ts +4 -0
- package/src/tools/swarm-config/list-config.ts +99 -0
- package/src/tools/swarm-config/set-config.ts +118 -0
- package/src/tools/task-action.ts +50 -5
- package/src/tools/task-dedup.ts +97 -0
- package/src/tools/templates.ts +53 -0
- package/src/tools/tool-config.ts +124 -0
- package/src/tools/tracker/index.ts +6 -0
- package/src/tools/tracker/tracker-link-epic.ts +64 -0
- package/src/tools/tracker/tracker-link-task.ts +64 -0
- package/src/tools/tracker/tracker-map-agent.ts +57 -0
- package/src/tools/tracker/tracker-status.ts +56 -0
- package/src/tools/tracker/tracker-sync-status.ts +42 -0
- package/src/tools/tracker/tracker-unlink.ts +41 -0
- package/src/tools/unregister-service.ts +2 -0
- package/src/tools/update-profile.ts +172 -17
- package/src/tools/update-service-status.ts +2 -0
- package/src/tools/utils.ts +10 -1
- package/src/tools/workflows/create-workflow.ts +129 -0
- package/src/tools/workflows/delete-workflow.ts +42 -0
- package/src/tools/workflows/get-workflow-run.ts +59 -0
- package/src/tools/workflows/get-workflow.ts +53 -0
- package/src/tools/workflows/index.ts +9 -0
- package/src/tools/workflows/list-workflow-runs.ts +48 -0
- package/src/tools/workflows/list-workflows.ts +42 -0
- package/src/tools/workflows/retry-workflow-run.ts +40 -0
- package/src/tools/workflows/trigger-workflow.ts +96 -0
- package/src/tools/workflows/update-workflow.ts +133 -0
- package/src/tracker/types.ts +51 -0
- package/src/types.ts +530 -14
- package/src/utils/credentials.test.ts +156 -0
- package/src/utils/credentials.ts +50 -0
- package/src/utils/error-tracker.ts +190 -0
- package/src/vcs/index.ts +15 -0
- package/src/vcs/types.ts +5 -0
- package/src/workflows/checkpoint.ts +121 -0
- package/src/workflows/cooldown.ts +28 -0
- package/src/workflows/definition.ts +235 -0
- package/src/workflows/engine.ts +580 -0
- package/src/workflows/event-bus.ts +29 -0
- package/src/workflows/executors/agent-task.ts +103 -0
- package/src/workflows/executors/base.ts +86 -0
- package/src/workflows/executors/code-match.ts +88 -0
- package/src/workflows/executors/index.ts +16 -0
- package/src/workflows/executors/notify.ts +93 -0
- package/src/workflows/executors/property-match.ts +104 -0
- package/src/workflows/executors/raw-llm.ts +83 -0
- package/src/workflows/executors/registry.ts +76 -0
- package/src/workflows/executors/script.ts +103 -0
- package/src/workflows/executors/validate.ts +215 -0
- package/src/workflows/executors/vcs.ts +58 -0
- package/src/workflows/index.ts +61 -0
- package/src/workflows/input.ts +46 -0
- package/src/workflows/json-schema-validator.ts +118 -0
- package/src/workflows/recovery.ts +139 -0
- package/src/workflows/resume.ts +229 -0
- package/src/workflows/retry-poller.ts +216 -0
- package/src/workflows/template.ts +74 -0
- package/src/workflows/templates.ts +86 -0
- package/src/workflows/triggers.ts +124 -0
- package/src/workflows/validation.ts +104 -0
- package/src/workflows/version.ts +44 -0
- package/src/x402/cli.ts +140 -0
- package/src/x402/client.ts +192 -0
- package/src/x402/config.ts +131 -0
- package/src/x402/index.ts +37 -0
- package/src/x402/openfort-signer.ts +83 -0
- package/src/x402/spending-tracker.ts +109 -0
- package/templates/official/coder/CLAUDE.md +49 -0
- package/templates/official/coder/IDENTITY.md +28 -0
- package/templates/official/coder/SOUL.md +43 -0
- package/templates/official/coder/TOOLS.md +40 -0
- package/templates/official/coder/config.json +23 -0
- package/templates/official/coder/start-up.sh +23 -0
- package/templates/official/content-reviewer/CLAUDE.md +68 -0
- package/templates/official/content-reviewer/IDENTITY.md +28 -0
- package/templates/official/content-reviewer/SOUL.md +44 -0
- package/templates/official/content-reviewer/TOOLS.md +37 -0
- package/templates/official/content-reviewer/config.json +23 -0
- package/templates/official/content-reviewer/start-up.sh +23 -0
- package/templates/official/content-strategist/CLAUDE.md +63 -0
- package/templates/official/content-strategist/IDENTITY.md +33 -0
- package/templates/official/content-strategist/SOUL.md +48 -0
- package/templates/official/content-strategist/TOOLS.md +47 -0
- package/templates/official/content-strategist/config.json +23 -0
- package/templates/official/content-strategist/start-up.sh +23 -0
- package/templates/official/content-writer/CLAUDE.md +72 -0
- package/templates/official/content-writer/IDENTITY.md +30 -0
- package/templates/official/content-writer/SOUL.md +46 -0
- package/templates/official/content-writer/TOOLS.md +44 -0
- package/templates/official/content-writer/config.json +23 -0
- package/templates/official/content-writer/start-up.sh +23 -0
- package/templates/official/forward-deployed-engineer/CLAUDE.md +54 -0
- package/templates/official/forward-deployed-engineer/IDENTITY.md +37 -0
- package/templates/official/forward-deployed-engineer/SOUL.md +55 -0
- package/templates/official/forward-deployed-engineer/config.json +21 -0
- package/templates/official/lead/CLAUDE.md +33 -0
- package/templates/official/lead/IDENTITY.md +36 -0
- package/templates/official/lead/SOUL.md +51 -0
- package/templates/official/lead/config.json +22 -0
- package/templates/official/researcher/CLAUDE.md +46 -0
- package/templates/official/researcher/IDENTITY.md +28 -0
- package/templates/official/researcher/SOUL.md +43 -0
- package/templates/official/researcher/config.json +21 -0
- package/templates/official/reviewer/CLAUDE.md +63 -0
- package/templates/official/reviewer/IDENTITY.md +28 -0
- package/templates/official/reviewer/SOUL.md +45 -0
- package/templates/official/reviewer/config.json +21 -0
- package/templates/official/tester/CLAUDE.md +53 -0
- package/templates/official/tester/IDENTITY.md +28 -0
- package/templates/official/tester/SOUL.md +55 -0
- package/templates/official/tester/config.json +21 -0
- package/templates/schema.ts +35 -0
- package/.claude/settings.local.json +0 -115
- package/.dockerignore +0 -61
- package/.editorconfig +0 -15
- package/.env.docker.example +0 -39
- package/.env.example +0 -40
- package/.github/workflows/ci.yml +0 -76
- package/.github/workflows/docker-and-deploy.yml +0 -117
- package/.wts-config.json +0 -4
- package/.wts-setup.ts +0 -102
- package/CLAUDE.md +0 -104
- package/CONTRIBUTING.md +0 -270
- package/DEPLOYMENT.md +0 -605
- package/Dockerfile +0 -57
- package/Dockerfile.worker +0 -157
- package/FAQ.md +0 -19
- package/MCP.md +0 -406
- package/UI.md +0 -40
- package/assets/agent-swarm-logo-orange.png +0 -0
- package/assets/agent-swarm-logo.png +0 -0
- package/assets/agent-swarm.mp4 +0 -0
- package/assets/agent-swarm.png +0 -0
- package/biome.json +0 -39
- package/deploy/DEPLOY.md +0 -60
- package/deploy/agent-swarm.service +0 -17
- package/deploy/docker-push.ts +0 -30
- package/deploy/install.ts +0 -85
- package/deploy/prod-db.ts +0 -42
- package/deploy/uninstall.ts +0 -12
- package/deploy/update.ts +0 -21
- package/docker-compose.example.yml +0 -159
- package/docker-entrypoint.sh +0 -352
- package/ecosystem.config.cjs +0 -66
- package/plugin/README.md +0 -1
- package/plugin/hooks/hooks.json +0 -71
- package/pyproject.toml +0 -9
- package/scripts/generate-mcp-docs.ts +0 -415
- package/slack-manifest.json +0 -71
- package/src/tests/get-inbox-message.test.ts +0 -145
- package/src/tools/get-inbox-message.ts +0 -89
- package/src/tools/inbox-delegate.ts +0 -113
- package/thoughts/shared/plans/2025-12-18-slack-integration.md +0 -1195
- package/thoughts/shared/plans/2025-12-19-agent-log-streaming.md +0 -732
- package/thoughts/shared/plans/2025-12-19-role-based-swarm-plugin.md +0 -361
- package/thoughts/shared/plans/2025-12-20-mobile-responsive-ui.md +0 -501
- package/thoughts/shared/plans/2025-12-20-startup-team-swarm.md +0 -560
- package/thoughts/shared/plans/2025-12-23-runner-level-polling.md +0 -934
- package/thoughts/shared/plans/2025-12-23-runner-session-logs.md +0 -1000
- package/thoughts/shared/plans/2025-12-23-worker-lead-spawn-triggers.md +0 -568
- package/thoughts/shared/plans/2026-01-09-inverse-teleport.md +0 -1516
- package/thoughts/shared/plans/2026-01-12-agent-rename-pm2-control.md +0 -1133
- package/thoughts/shared/plans/2026-01-12-github-app-integration.md +0 -380
- package/thoughts/shared/plans/2026-01-12-lead-inbox-model.md +0 -876
- package/thoughts/shared/plans/2026-01-12-ralph-wiggum-integration.md +0 -463
- package/thoughts/shared/plans/2026-01-13-agent-concurrency.md +0 -691
- package/thoughts/shared/plans/2026-01-13-github-assignment-handling.md +0 -690
- package/thoughts/shared/plans/2026-01-13-prevent-duplicate-trigger-processing.md +0 -1071
- package/thoughts/shared/plans/2026-01-14-fix-slack-thread-context.md +0 -507
- package/thoughts/shared/plans/2026-01-15-scheduled-tasks-implementation.md +0 -565
- package/thoughts/shared/plans/2026-01-15-usage-cost-tracking-ui.md +0 -1479
- package/thoughts/shared/plans/2026-01-16-epics-feature-implementation.md +0 -1230
- package/thoughts/shared/research/.gitkeep +0 -0
- package/thoughts/shared/research/2025-01-09-inverse-teleport-plan-review.md +0 -420
- package/thoughts/shared/research/2025-12-18-slack-integration.md +0 -442
- package/thoughts/shared/research/2025-12-19-agent-log-streaming.md +0 -339
- package/thoughts/shared/research/2025-12-19-agent-secrets-cli-research.md +0 -390
- package/thoughts/shared/research/2025-12-21-gemini-cli-integration.md +0 -376
- package/thoughts/shared/research/2025-12-22-runner-loop-architecture.md +0 -582
- package/thoughts/shared/research/2025-12-22-setup-experience-improvements.md +0 -264
- package/thoughts/shared/research/2026-01-13-lead-duplicate-trigger-processing.md +0 -223
- package/thoughts/shared/research/2026-01-14-lead-slack-thread-context.md +0 -277
- package/thoughts/shared/research/2026-01-15-ai-tracker-agent-swarm-integration.md +0 -376
- package/thoughts/shared/research/2026-01-15-auto-starting-processes-in-worker-containers.md +0 -787
- package/thoughts/shared/research/2026-01-15-scheduled-tasks.md +0 -390
- package/thoughts/shared/research/2026-01-16-epics-feature-research.md +0 -437
- package/thoughts/taras/plans/2026-01-22-agent-swarm-schemas.md +0 -98
- package/thoughts/taras/plans/2026-01-28-per-worker-claude-md.md +0 -617
- package/thoughts/taras/plans/2026-01-28-sentry-cli-integration.md +0 -214
- package/thoughts/taras/research/2026-01-22-vercel-cli-integration.md +0 -287
- package/thoughts/taras/research/2026-01-27-excessive-polling-issue.md +0 -311
- package/thoughts/taras/research/2026-01-28-per-worker-claude-md.md +0 -383
- package/thoughts/taras/research/2026-01-28-sentry-cli-integration.md +0 -240
- package/tsconfig.json +0 -37
- package/ui/CLAUDE.md +0 -49
- package/ui/bun.lock +0 -771
- package/ui/index.html +0 -22
- package/ui/package-lock.json +0 -5290
- package/ui/package.json +0 -33
- package/ui/pnpm-lock.yaml +0 -3341
- package/ui/postcss.config.js +0 -6
- package/ui/public/logo.png +0 -0
- package/ui/src/App.tsx +0 -63
- package/ui/src/components/ActivityFeed.tsx +0 -440
- package/ui/src/components/AgentDetailPanel.tsx +0 -733
- package/ui/src/components/AgentsPanel.tsx +0 -815
- package/ui/src/components/ChatPanel.tsx +0 -1920
- package/ui/src/components/ConfigModal.tsx +0 -253
- package/ui/src/components/Dashboard.tsx +0 -832
- package/ui/src/components/EditAgentProfileModal.tsx +0 -433
- package/ui/src/components/EpicDetailPage.tsx +0 -741
- package/ui/src/components/EpicsPanel.tsx +0 -566
- package/ui/src/components/Header.tsx +0 -160
- package/ui/src/components/JsonViewer.tsx +0 -171
- package/ui/src/components/ScheduledTaskDetailPanel.tsx +0 -517
- package/ui/src/components/ScheduledTasksPanel.tsx +0 -639
- package/ui/src/components/ServicesPanel.tsx +0 -622
- package/ui/src/components/SessionLogPanel.tsx +0 -1219
- package/ui/src/components/StatsBar.tsx +0 -321
- package/ui/src/components/StatusBadge.tsx +0 -168
- package/ui/src/components/TaskDetailPanel.tsx +0 -903
- package/ui/src/components/TasksPanel.tsx +0 -614
- package/ui/src/components/UsageCharts.tsx +0 -216
- package/ui/src/components/UsageTab.tsx +0 -394
- package/ui/src/hooks/queries.ts +0 -353
- package/ui/src/hooks/useAutoScroll.ts +0 -83
- package/ui/src/index.css +0 -257
- package/ui/src/lib/api.ts +0 -268
- package/ui/src/lib/config.ts +0 -35
- package/ui/src/lib/contentPreview.ts +0 -208
- package/ui/src/lib/theme.ts +0 -214
- package/ui/src/lib/utils.ts +0 -88
- package/ui/src/main.tsx +0 -28
- package/ui/src/types/api.ts +0 -323
- package/ui/src/vite-env.d.ts +0 -1
- package/ui/tailwind.config.js +0 -37
- package/ui/tsconfig.json +0 -31
- package/ui/vite.config.ts +0 -35
- /package/{thoughts/shared/plans → templates/community}/.gitkeep +0 -0
|
@@ -1,1479 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
date: 2026-01-15T23:50:00Z
|
|
3
|
-
topic: "Usage/Cost Tracking UI - Frontend Implementation Plan"
|
|
4
|
-
author: "Coder (a09d19a4)"
|
|
5
|
-
status: "draft"
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
# Usage/Cost Tracking UI - Frontend Implementation Plan
|
|
9
|
-
|
|
10
|
-
## Overview
|
|
11
|
-
|
|
12
|
-
Implement a comprehensive usage and cost tracking UI for the Agent Swarm dashboard. This feature will visualize session cost data captured by the backend (PR #28) across multiple views, providing insights into token usage, costs, and trends per agent, task, and hive.
|
|
13
|
-
|
|
14
|
-
## Current State Analysis
|
|
15
|
-
|
|
16
|
-
### Backend API Available
|
|
17
|
-
- **Endpoint**: `GET /api/session-costs`
|
|
18
|
-
- **Filters**: `agentId`, `taskId`, `limit` (date range filtering not yet implemented in backend)
|
|
19
|
-
- **Data Fields**: `id`, `sessionId`, `taskId`, `agentId`, `totalCostUsd`, `inputTokens`, `outputTokens`, `cacheReadTokens`, `cacheWriteTokens`, `durationMs`, `numTurns`, `model`, `isError`, `createdAt`
|
|
20
|
-
|
|
21
|
-
### Frontend Patterns (Reference Files)
|
|
22
|
-
- **API Client**: `ui/src/lib/api.ts` - Class-based fetch wrapper
|
|
23
|
-
- **Query Hooks**: `ui/src/hooks/queries.ts` - TanStack React Query hooks
|
|
24
|
-
- **Types**: `ui/src/types/api.ts` - TypeScript interfaces
|
|
25
|
-
- **Stats Display**: `ui/src/components/StatsBar.tsx` - Hexagon stat widgets
|
|
26
|
-
- **Detail Panels**: `ui/src/components/AgentDetailPanel.tsx`, `TaskDetailPanel.tsx`
|
|
27
|
-
- **Dashboard Layout**: `ui/src/components/Dashboard.tsx` - Tab-based navigation
|
|
28
|
-
|
|
29
|
-
### UI Library
|
|
30
|
-
- **MUI Joy** (`@mui/joy`) - Primary component library
|
|
31
|
-
- **No charting library currently installed** - Will need to add one
|
|
32
|
-
|
|
33
|
-
### Key Discoveries
|
|
34
|
-
- Stats use custom hexagon CSS shapes with `clipPath`
|
|
35
|
-
- Detail panels support expandable layouts with horizontal/vertical switching
|
|
36
|
-
- Tables use MUI Joy `Table` with sticky headers and responsive cards for mobile
|
|
37
|
-
- React Query with 5-second auto-refresh is the standard data fetching pattern
|
|
38
|
-
- Color scheme supports dark/light mode via `useColorScheme()`
|
|
39
|
-
|
|
40
|
-
## Desired End State
|
|
41
|
-
|
|
42
|
-
A fully integrated cost tracking UI that:
|
|
43
|
-
1. Shows monthly totals in the home page stats bar
|
|
44
|
-
2. Displays per-agent monthly costs in the agents table
|
|
45
|
-
3. Provides detailed usage breakdowns in agent/task detail panels
|
|
46
|
-
4. Offers a dedicated "Usage" tab with comprehensive analytics and charts
|
|
47
|
-
|
|
48
|
-
## Charting Library Recommendation
|
|
49
|
-
|
|
50
|
-
**Recommended: Recharts**
|
|
51
|
-
|
|
52
|
-
Rationale:
|
|
53
|
-
- Built for React with declarative components
|
|
54
|
-
- Lightweight (~200KB gzipped)
|
|
55
|
-
- Good TypeScript support
|
|
56
|
-
- Simple API that matches MUI Joy's declarative style
|
|
57
|
-
- Supports all required chart types (line, bar, pie, area)
|
|
58
|
-
- Active maintenance and community
|
|
59
|
-
|
|
60
|
-
Alternative considered: Victory (heavier), Chart.js (imperative API), Nivo (complex)
|
|
61
|
-
|
|
62
|
-
## What We're NOT Doing
|
|
63
|
-
|
|
64
|
-
- Backend date range filtering (separate PR needed)
|
|
65
|
-
- Real-time cost streaming (polling is sufficient)
|
|
66
|
-
- Cost predictions/forecasting
|
|
67
|
-
- Export functionality
|
|
68
|
-
- Budget alerts/thresholds
|
|
69
|
-
- Multi-currency support
|
|
70
|
-
|
|
71
|
-
---
|
|
72
|
-
|
|
73
|
-
## Implementation Approach
|
|
74
|
-
|
|
75
|
-
### Data Flow Architecture
|
|
76
|
-
|
|
77
|
-
```
|
|
78
|
-
GET /api/session-costs → api.fetchSessionCosts() → useSessionCosts() hook → Components
|
|
79
|
-
useAgentUsage() hook
|
|
80
|
-
useTaskUsage() hook
|
|
81
|
-
useUsageStats() hook
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
### Aggregation Strategy
|
|
85
|
-
|
|
86
|
-
Client-side aggregation from raw session cost data:
|
|
87
|
-
- Monthly totals: Filter by `createdAt` month, sum values
|
|
88
|
-
- Daily/weekly breakdowns: Group by date, aggregate
|
|
89
|
-
- Per-agent/task: Filter by ID, aggregate
|
|
90
|
-
|
|
91
|
-
Note: For large datasets, consider adding backend aggregation endpoints in a future phase.
|
|
92
|
-
|
|
93
|
-
---
|
|
94
|
-
|
|
95
|
-
## Phase 1: Foundation - Types, API, and Hooks
|
|
96
|
-
|
|
97
|
-
### Overview
|
|
98
|
-
Add TypeScript types, API client methods, and React Query hooks for session costs.
|
|
99
|
-
|
|
100
|
-
### Changes Required
|
|
101
|
-
|
|
102
|
-
#### 1. Type Definitions
|
|
103
|
-
**File**: `ui/src/types/api.ts`
|
|
104
|
-
**Changes**: Add SessionCost interface and response types
|
|
105
|
-
|
|
106
|
-
```typescript
|
|
107
|
-
// Add after SessionLogsResponse (line ~93)
|
|
108
|
-
|
|
109
|
-
export interface SessionCost {
|
|
110
|
-
id: string;
|
|
111
|
-
sessionId: string;
|
|
112
|
-
taskId?: string;
|
|
113
|
-
agentId: string;
|
|
114
|
-
totalCostUsd: number;
|
|
115
|
-
inputTokens: number;
|
|
116
|
-
outputTokens: number;
|
|
117
|
-
cacheReadTokens: number;
|
|
118
|
-
cacheWriteTokens: number;
|
|
119
|
-
durationMs: number;
|
|
120
|
-
numTurns: number;
|
|
121
|
-
model: string;
|
|
122
|
-
isError: boolean;
|
|
123
|
-
createdAt: string;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
export interface SessionCostsResponse {
|
|
127
|
-
costs: SessionCost[];
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Aggregated usage types for UI
|
|
131
|
-
export interface UsageStats {
|
|
132
|
-
totalCostUsd: number;
|
|
133
|
-
totalTokens: number;
|
|
134
|
-
inputTokens: number;
|
|
135
|
-
outputTokens: number;
|
|
136
|
-
cacheReadTokens: number;
|
|
137
|
-
cacheWriteTokens: number;
|
|
138
|
-
sessionCount: number;
|
|
139
|
-
totalDurationMs: number;
|
|
140
|
-
avgCostPerSession: number;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
export interface DailyUsage {
|
|
144
|
-
date: string;
|
|
145
|
-
costUsd: number;
|
|
146
|
-
tokens: number;
|
|
147
|
-
sessions: number;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
export interface AgentUsageSummary {
|
|
151
|
-
agentId: string;
|
|
152
|
-
agentName?: string;
|
|
153
|
-
monthlyCostUsd: number;
|
|
154
|
-
monthlyTokens: number;
|
|
155
|
-
sessionCount: number;
|
|
156
|
-
}
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
#### 2. API Client Methods
|
|
160
|
-
**File**: `ui/src/lib/api.ts`
|
|
161
|
-
**Changes**: Add fetchSessionCosts method
|
|
162
|
-
|
|
163
|
-
```typescript
|
|
164
|
-
// Add to ApiClient class (after fetchServices method, ~line 182)
|
|
165
|
-
|
|
166
|
-
async fetchSessionCosts(filters?: {
|
|
167
|
-
agentId?: string;
|
|
168
|
-
taskId?: string;
|
|
169
|
-
limit?: number
|
|
170
|
-
}): Promise<SessionCostsResponse> {
|
|
171
|
-
const params = new URLSearchParams();
|
|
172
|
-
if (filters?.agentId) params.set("agentId", filters.agentId);
|
|
173
|
-
if (filters?.taskId) params.set("taskId", filters.taskId);
|
|
174
|
-
if (filters?.limit) params.set("limit", String(filters.limit));
|
|
175
|
-
const queryString = params.toString();
|
|
176
|
-
const url = `${this.getBaseUrl()}/api/session-costs${queryString ? `?${queryString}` : ""}`;
|
|
177
|
-
const res = await fetch(url, { headers: this.getHeaders() });
|
|
178
|
-
if (!res.ok) throw new Error(`Failed to fetch session costs: ${res.status}`);
|
|
179
|
-
return res.json();
|
|
180
|
-
}
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
#### 3. React Query Hooks
|
|
184
|
-
**File**: `ui/src/hooks/queries.ts`
|
|
185
|
-
**Changes**: Add usage-related hooks
|
|
186
|
-
|
|
187
|
-
```typescript
|
|
188
|
-
// Add at end of file
|
|
189
|
-
|
|
190
|
-
export interface SessionCostFilters {
|
|
191
|
-
agentId?: string;
|
|
192
|
-
taskId?: string;
|
|
193
|
-
limit?: number;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
export function useSessionCosts(filters?: SessionCostFilters) {
|
|
197
|
-
return useQuery({
|
|
198
|
-
queryKey: ["session-costs", filters],
|
|
199
|
-
queryFn: () => api.fetchSessionCosts(filters),
|
|
200
|
-
select: (data) => data.costs,
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// Hook for aggregated usage stats (monthly)
|
|
205
|
-
export function useMonthlyUsageStats() {
|
|
206
|
-
const { data: costs, ...rest } = useSessionCosts({ limit: 1000 });
|
|
207
|
-
|
|
208
|
-
const stats = useMemo(() => {
|
|
209
|
-
if (!costs) return null;
|
|
210
|
-
|
|
211
|
-
const now = new Date();
|
|
212
|
-
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
213
|
-
|
|
214
|
-
const monthlyCosts = costs.filter(
|
|
215
|
-
(c) => new Date(c.createdAt) >= startOfMonth
|
|
216
|
-
);
|
|
217
|
-
|
|
218
|
-
return {
|
|
219
|
-
totalCostUsd: monthlyCosts.reduce((sum, c) => sum + c.totalCostUsd, 0),
|
|
220
|
-
totalTokens: monthlyCosts.reduce(
|
|
221
|
-
(sum, c) => sum + c.inputTokens + c.outputTokens, 0
|
|
222
|
-
),
|
|
223
|
-
inputTokens: monthlyCosts.reduce((sum, c) => sum + c.inputTokens, 0),
|
|
224
|
-
outputTokens: monthlyCosts.reduce((sum, c) => sum + c.outputTokens, 0),
|
|
225
|
-
cacheReadTokens: monthlyCosts.reduce((sum, c) => sum + c.cacheReadTokens, 0),
|
|
226
|
-
cacheWriteTokens: monthlyCosts.reduce((sum, c) => sum + c.cacheWriteTokens, 0),
|
|
227
|
-
sessionCount: monthlyCosts.length,
|
|
228
|
-
totalDurationMs: monthlyCosts.reduce((sum, c) => sum + c.durationMs, 0),
|
|
229
|
-
avgCostPerSession: monthlyCosts.length > 0
|
|
230
|
-
? monthlyCosts.reduce((sum, c) => sum + c.totalCostUsd, 0) / monthlyCosts.length
|
|
231
|
-
: 0,
|
|
232
|
-
};
|
|
233
|
-
}, [costs]);
|
|
234
|
-
|
|
235
|
-
return { data: stats, ...rest };
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Hook for agent usage summary
|
|
239
|
-
export function useAgentUsageSummary(agentId: string) {
|
|
240
|
-
return useQuery({
|
|
241
|
-
queryKey: ["agent-usage", agentId],
|
|
242
|
-
queryFn: () => api.fetchSessionCosts({ agentId, limit: 500 }),
|
|
243
|
-
select: (data) => {
|
|
244
|
-
const costs = data.costs;
|
|
245
|
-
const now = new Date();
|
|
246
|
-
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
247
|
-
const startOfWeek = new Date(now);
|
|
248
|
-
startOfWeek.setDate(now.getDate() - now.getDay());
|
|
249
|
-
const startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
250
|
-
|
|
251
|
-
const filterByDate = (start: Date) =>
|
|
252
|
-
costs.filter((c) => new Date(c.createdAt) >= start);
|
|
253
|
-
|
|
254
|
-
return {
|
|
255
|
-
daily: aggregateUsage(filterByDate(startOfDay)),
|
|
256
|
-
weekly: aggregateUsage(filterByDate(startOfWeek)),
|
|
257
|
-
monthly: aggregateUsage(filterByDate(startOfMonth)),
|
|
258
|
-
all: aggregateUsage(costs),
|
|
259
|
-
};
|
|
260
|
-
},
|
|
261
|
-
enabled: !!agentId,
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// Hook for task usage
|
|
266
|
-
export function useTaskUsage(taskId: string) {
|
|
267
|
-
return useQuery({
|
|
268
|
-
queryKey: ["task-usage", taskId],
|
|
269
|
-
queryFn: () => api.fetchSessionCosts({ taskId }),
|
|
270
|
-
select: (data) => aggregateUsage(data.costs),
|
|
271
|
-
enabled: !!taskId,
|
|
272
|
-
});
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// Helper function for aggregation
|
|
276
|
-
function aggregateUsage(costs: SessionCost[]): UsageStats {
|
|
277
|
-
return {
|
|
278
|
-
totalCostUsd: costs.reduce((sum, c) => sum + c.totalCostUsd, 0),
|
|
279
|
-
totalTokens: costs.reduce((sum, c) => sum + c.inputTokens + c.outputTokens, 0),
|
|
280
|
-
inputTokens: costs.reduce((sum, c) => sum + c.inputTokens, 0),
|
|
281
|
-
outputTokens: costs.reduce((sum, c) => sum + c.outputTokens, 0),
|
|
282
|
-
cacheReadTokens: costs.reduce((sum, c) => sum + c.cacheReadTokens, 0),
|
|
283
|
-
cacheWriteTokens: costs.reduce((sum, c) => sum + c.cacheWriteTokens, 0),
|
|
284
|
-
sessionCount: costs.length,
|
|
285
|
-
totalDurationMs: costs.reduce((sum, c) => sum + c.durationMs, 0),
|
|
286
|
-
avgCostPerSession: costs.length > 0
|
|
287
|
-
? costs.reduce((sum, c) => sum + c.totalCostUsd, 0) / costs.length
|
|
288
|
-
: 0,
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
```
|
|
292
|
-
|
|
293
|
-
### Success Criteria
|
|
294
|
-
|
|
295
|
-
#### Automated Verification
|
|
296
|
-
- [ ] TypeScript compiles: `cd ui && npm run typecheck`
|
|
297
|
-
- [ ] Build succeeds: `cd ui && npm run build`
|
|
298
|
-
|
|
299
|
-
#### Manual Verification
|
|
300
|
-
- [ ] API calls return data when tested in browser dev tools
|
|
301
|
-
|
|
302
|
-
**Implementation Note**: After completing this phase, verify types compile before proceeding.
|
|
303
|
-
|
|
304
|
-
---
|
|
305
|
-
|
|
306
|
-
## Phase 2: StatsBar Enhancement - Monthly Usage Hives
|
|
307
|
-
|
|
308
|
-
### Overview
|
|
309
|
-
Add two new hexagon stats to the home page showing monthly token count and monthly cost.
|
|
310
|
-
|
|
311
|
-
### Changes Required
|
|
312
|
-
|
|
313
|
-
#### 1. StatsBar Component
|
|
314
|
-
**File**: `ui/src/components/StatsBar.tsx`
|
|
315
|
-
**Changes**: Add usage stats hexagons
|
|
316
|
-
|
|
317
|
-
```typescript
|
|
318
|
-
// Add import (line ~4)
|
|
319
|
-
import { useStats, useMonthlyUsageStats } from "../hooks/queries";
|
|
320
|
-
|
|
321
|
-
// Inside StatsBar component, add usage stats hook (after line 117)
|
|
322
|
-
const { data: usageStats } = useMonthlyUsageStats();
|
|
323
|
-
|
|
324
|
-
// Add to colors object (around line 123)
|
|
325
|
-
const colors = {
|
|
326
|
-
// ... existing colors
|
|
327
|
-
green: "#22C55E",
|
|
328
|
-
greenGlow: isDark ? "rgba(34, 197, 94, 0.5)" : "rgba(34, 197, 94, 0.25)",
|
|
329
|
-
};
|
|
330
|
-
|
|
331
|
-
// Add new stats to topRow array (after existing items, around line 159)
|
|
332
|
-
// Option A: Add to existing rows
|
|
333
|
-
// Option B: Create a third row for usage stats
|
|
334
|
-
|
|
335
|
-
// Recommended: Add to bottom row or create usage section
|
|
336
|
-
const usageRow = [
|
|
337
|
-
{
|
|
338
|
-
label: "MTD TOKENS",
|
|
339
|
-
value: usageStats ? formatCompactNumber(usageStats.totalTokens) : "—",
|
|
340
|
-
color: colors.green,
|
|
341
|
-
glowColor: colors.greenGlow,
|
|
342
|
-
},
|
|
343
|
-
{
|
|
344
|
-
label: "MTD COST",
|
|
345
|
-
value: usageStats ? `$${usageStats.totalCostUsd.toFixed(2)}` : "—",
|
|
346
|
-
color: colors.amber,
|
|
347
|
-
glowColor: colors.amberGlow,
|
|
348
|
-
},
|
|
349
|
-
];
|
|
350
|
-
```
|
|
351
|
-
|
|
352
|
-
#### 2. Utility Function for Number Formatting
|
|
353
|
-
**File**: `ui/src/lib/utils.ts`
|
|
354
|
-
**Changes**: Add compact number formatter
|
|
355
|
-
|
|
356
|
-
```typescript
|
|
357
|
-
// Add at end of file
|
|
358
|
-
|
|
359
|
-
export function formatCompactNumber(num: number): string {
|
|
360
|
-
if (num >= 1_000_000_000) return `${(num / 1_000_000_000).toFixed(1)}B`;
|
|
361
|
-
if (num >= 1_000_000) return `${(num / 1_000_000).toFixed(1)}M`;
|
|
362
|
-
if (num >= 1_000) return `${(num / 1_000).toFixed(1)}K`;
|
|
363
|
-
return num.toString();
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
export function formatCurrency(amount: number): string {
|
|
367
|
-
if (amount >= 1000) return `$${(amount / 1000).toFixed(1)}K`;
|
|
368
|
-
if (amount >= 1) return `$${amount.toFixed(2)}`;
|
|
369
|
-
return `$${amount.toFixed(4)}`;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
export function formatDuration(ms: number): string {
|
|
373
|
-
const seconds = Math.floor(ms / 1000);
|
|
374
|
-
const minutes = Math.floor(seconds / 60);
|
|
375
|
-
const hours = Math.floor(minutes / 60);
|
|
376
|
-
|
|
377
|
-
if (hours > 0) return `${hours}h ${minutes % 60}m`;
|
|
378
|
-
if (minutes > 0) return `${minutes}m ${seconds % 60}s`;
|
|
379
|
-
return `${seconds}s`;
|
|
380
|
-
}
|
|
381
|
-
```
|
|
382
|
-
|
|
383
|
-
### UI Mockup Description
|
|
384
|
-
|
|
385
|
-
The StatsBar will display two additional hexagons:
|
|
386
|
-
- **MTD TOKENS**: Green hexagon showing formatted total tokens (e.g., "1.2M")
|
|
387
|
-
- **MTD COST**: Amber hexagon showing formatted cost (e.g., "$45.23")
|
|
388
|
-
|
|
389
|
-
Layout options:
|
|
390
|
-
1. Add to existing honeycomb grid (extend bottom row to 6 hexagons)
|
|
391
|
-
2. Create separate "Usage" section below existing stats
|
|
392
|
-
3. Add hover tooltip with detailed breakdown
|
|
393
|
-
|
|
394
|
-
Recommended: Option 1 for consistency, with tooltip showing breakdown.
|
|
395
|
-
|
|
396
|
-
### Success Criteria
|
|
397
|
-
|
|
398
|
-
#### Automated Verification
|
|
399
|
-
- [ ] Build succeeds: `cd ui && npm run build`
|
|
400
|
-
- [ ] TypeScript compiles: `cd ui && npm run typecheck`
|
|
401
|
-
|
|
402
|
-
#### Manual Verification
|
|
403
|
-
- [ ] Stats bar shows MTD tokens and cost
|
|
404
|
-
- [ ] Values update when session costs are created
|
|
405
|
-
- [ ] Responsive layout works on mobile
|
|
406
|
-
|
|
407
|
-
---
|
|
408
|
-
|
|
409
|
-
## Phase 3: Agents Table - Monthly Usage Column
|
|
410
|
-
|
|
411
|
-
### Overview
|
|
412
|
-
Add a new column to the agents table showing each agent's monthly usage.
|
|
413
|
-
|
|
414
|
-
### Changes Required
|
|
415
|
-
|
|
416
|
-
#### 1. AgentsPanel Component
|
|
417
|
-
**File**: `ui/src/components/AgentsPanel.tsx`
|
|
418
|
-
**Changes**: Add monthly usage column
|
|
419
|
-
|
|
420
|
-
```typescript
|
|
421
|
-
// Add import for useSessionCosts
|
|
422
|
-
import { useAgents, useSessionCosts } from "../hooks/queries";
|
|
423
|
-
|
|
424
|
-
// Inside AgentsPanel, fetch all session costs
|
|
425
|
-
const { data: allCosts } = useSessionCosts({ limit: 2000 });
|
|
426
|
-
|
|
427
|
-
// Create agent usage map (memoized)
|
|
428
|
-
const agentUsageMap = useMemo(() => {
|
|
429
|
-
if (!allCosts) return new Map();
|
|
430
|
-
|
|
431
|
-
const now = new Date();
|
|
432
|
-
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
433
|
-
|
|
434
|
-
const map = new Map<string, { cost: number; tokens: number }>();
|
|
435
|
-
|
|
436
|
-
allCosts
|
|
437
|
-
.filter((c) => new Date(c.createdAt) >= startOfMonth)
|
|
438
|
-
.forEach((cost) => {
|
|
439
|
-
const existing = map.get(cost.agentId) || { cost: 0, tokens: 0 };
|
|
440
|
-
map.set(cost.agentId, {
|
|
441
|
-
cost: existing.cost + cost.totalCostUsd,
|
|
442
|
-
tokens: existing.tokens + cost.inputTokens + cost.outputTokens,
|
|
443
|
-
});
|
|
444
|
-
});
|
|
445
|
-
|
|
446
|
-
return map;
|
|
447
|
-
}, [allCosts]);
|
|
448
|
-
|
|
449
|
-
// Add column header in Table thead (after UPDATED column)
|
|
450
|
-
<th style={{ width: "100px" }}>MTD USAGE</th>
|
|
451
|
-
|
|
452
|
-
// Add column data in table row
|
|
453
|
-
<td>
|
|
454
|
-
<Box sx={{ display: "flex", flexDirection: "column", gap: 0.25 }}>
|
|
455
|
-
<Typography sx={{ fontFamily: "code", fontSize: "0.7rem", color: colors.amber }}>
|
|
456
|
-
{formatCurrency(agentUsageMap.get(agent.id)?.cost || 0)}
|
|
457
|
-
</Typography>
|
|
458
|
-
<Typography sx={{ fontFamily: "code", fontSize: "0.6rem", color: "text.tertiary" }}>
|
|
459
|
-
{formatCompactNumber(agentUsageMap.get(agent.id)?.tokens || 0)} tokens
|
|
460
|
-
</Typography>
|
|
461
|
-
</Box>
|
|
462
|
-
</td>
|
|
463
|
-
```
|
|
464
|
-
|
|
465
|
-
### UI Mockup Description
|
|
466
|
-
|
|
467
|
-
| NAME | ROLE | STATUS | CAPACITY | MTD USAGE | UPDATED |
|
|
468
|
-
|------|------|--------|----------|-----------|---------|
|
|
469
|
-
| Worker-1 | Coder | busy | 2/5 | **$12.34** <br/> 245K tokens | 2m ago |
|
|
470
|
-
| Worker-2 | Reviewer | idle | 0/3 | **$5.67** <br/> 89K tokens | 5m ago |
|
|
471
|
-
|
|
472
|
-
The MTD USAGE column shows:
|
|
473
|
-
- Primary: Cost in amber color
|
|
474
|
-
- Secondary: Token count in tertiary gray
|
|
475
|
-
|
|
476
|
-
### Success Criteria
|
|
477
|
-
|
|
478
|
-
#### Automated Verification
|
|
479
|
-
- [ ] Build succeeds: `cd ui && npm run build`
|
|
480
|
-
|
|
481
|
-
#### Manual Verification
|
|
482
|
-
- [ ] Agents table shows MTD usage column
|
|
483
|
-
- [ ] Values are correct per agent
|
|
484
|
-
- [ ] Column is sortable (optional enhancement)
|
|
485
|
-
|
|
486
|
-
---
|
|
487
|
-
|
|
488
|
-
## Phase 4: Agent Detail Sidepanel - Usage Breakdown
|
|
489
|
-
|
|
490
|
-
### Overview
|
|
491
|
-
Add daily/weekly/monthly usage breakdown to the agent detail panel.
|
|
492
|
-
|
|
493
|
-
### Changes Required
|
|
494
|
-
|
|
495
|
-
#### 1. AgentDetailPanel Component
|
|
496
|
-
**File**: `ui/src/components/AgentDetailPanel.tsx`
|
|
497
|
-
**Changes**: Add usage section
|
|
498
|
-
|
|
499
|
-
```typescript
|
|
500
|
-
// Add import
|
|
501
|
-
import { useAgent, useLogs, useAgentUsageSummary } from "../hooks/queries";
|
|
502
|
-
import { formatCurrency, formatCompactNumber, formatDuration } from "../lib/utils";
|
|
503
|
-
|
|
504
|
-
// Inside component, add usage hook (after existing hooks)
|
|
505
|
-
const { data: usage } = useAgentUsageSummary(agentId);
|
|
506
|
-
|
|
507
|
-
// Add UsageSection component (inside AgentDetailPanel, before return)
|
|
508
|
-
const UsageSection = () => (
|
|
509
|
-
<Box sx={{ p: { xs: 1.5, md: 2 } }}>
|
|
510
|
-
<Typography
|
|
511
|
-
sx={{
|
|
512
|
-
fontFamily: "code",
|
|
513
|
-
fontSize: "0.7rem",
|
|
514
|
-
color: "text.tertiary",
|
|
515
|
-
letterSpacing: "0.05em",
|
|
516
|
-
mb: 1.5,
|
|
517
|
-
}}
|
|
518
|
-
>
|
|
519
|
-
USAGE BREAKDOWN
|
|
520
|
-
</Typography>
|
|
521
|
-
|
|
522
|
-
{!usage ? (
|
|
523
|
-
<Typography sx={{ fontFamily: "code", fontSize: "0.75rem", color: "text.tertiary" }}>
|
|
524
|
-
Loading usage data...
|
|
525
|
-
</Typography>
|
|
526
|
-
) : (
|
|
527
|
-
<Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
|
|
528
|
-
{/* Daily */}
|
|
529
|
-
<UsageCard
|
|
530
|
-
title="TODAY"
|
|
531
|
-
cost={usage.daily.totalCostUsd}
|
|
532
|
-
tokens={usage.daily.totalTokens}
|
|
533
|
-
sessions={usage.daily.sessionCount}
|
|
534
|
-
color={colors.amber}
|
|
535
|
-
/>
|
|
536
|
-
|
|
537
|
-
{/* Weekly */}
|
|
538
|
-
<UsageCard
|
|
539
|
-
title="THIS WEEK"
|
|
540
|
-
cost={usage.weekly.totalCostUsd}
|
|
541
|
-
tokens={usage.weekly.totalTokens}
|
|
542
|
-
sessions={usage.weekly.sessionCount}
|
|
543
|
-
color={colors.gold}
|
|
544
|
-
/>
|
|
545
|
-
|
|
546
|
-
{/* Monthly */}
|
|
547
|
-
<UsageCard
|
|
548
|
-
title="THIS MONTH"
|
|
549
|
-
cost={usage.monthly.totalCostUsd}
|
|
550
|
-
tokens={usage.monthly.totalTokens}
|
|
551
|
-
sessions={usage.monthly.sessionCount}
|
|
552
|
-
color={colors.blue}
|
|
553
|
-
/>
|
|
554
|
-
</Box>
|
|
555
|
-
)}
|
|
556
|
-
</Box>
|
|
557
|
-
);
|
|
558
|
-
|
|
559
|
-
// UsageCard helper component
|
|
560
|
-
const UsageCard = ({ title, cost, tokens, sessions, color }: {
|
|
561
|
-
title: string;
|
|
562
|
-
cost: number;
|
|
563
|
-
tokens: number;
|
|
564
|
-
sessions: number;
|
|
565
|
-
color: string;
|
|
566
|
-
}) => (
|
|
567
|
-
<Box
|
|
568
|
-
sx={{
|
|
569
|
-
bgcolor: "background.level1",
|
|
570
|
-
border: "1px solid",
|
|
571
|
-
borderColor: "neutral.outlinedBorder",
|
|
572
|
-
borderRadius: 1,
|
|
573
|
-
p: 1.5,
|
|
574
|
-
}}
|
|
575
|
-
>
|
|
576
|
-
<Typography
|
|
577
|
-
sx={{
|
|
578
|
-
fontFamily: "code",
|
|
579
|
-
fontSize: "0.6rem",
|
|
580
|
-
color: "text.tertiary",
|
|
581
|
-
letterSpacing: "0.05em",
|
|
582
|
-
mb: 0.5,
|
|
583
|
-
}}
|
|
584
|
-
>
|
|
585
|
-
{title}
|
|
586
|
-
</Typography>
|
|
587
|
-
<Box sx={{ display: "flex", alignItems: "baseline", gap: 1 }}>
|
|
588
|
-
<Typography sx={{ fontFamily: "code", fontSize: "1rem", fontWeight: 600, color }}>
|
|
589
|
-
{formatCurrency(cost)}
|
|
590
|
-
</Typography>
|
|
591
|
-
<Typography sx={{ fontFamily: "code", fontSize: "0.7rem", color: "text.secondary" }}>
|
|
592
|
-
{formatCompactNumber(tokens)} tokens
|
|
593
|
-
</Typography>
|
|
594
|
-
</Box>
|
|
595
|
-
<Typography sx={{ fontFamily: "code", fontSize: "0.6rem", color: "text.tertiary", mt: 0.5 }}>
|
|
596
|
-
{sessions} session{sessions !== 1 ? "s" : ""}
|
|
597
|
-
</Typography>
|
|
598
|
-
</Box>
|
|
599
|
-
);
|
|
600
|
-
|
|
601
|
-
// Add UsageSection to the panel layout (after InfoSection, before ActivitySection)
|
|
602
|
-
// In collapsed mode:
|
|
603
|
-
<InfoSection />
|
|
604
|
-
<Divider sx={{ bgcolor: "neutral.outlinedBorder" }} />
|
|
605
|
-
<UsageSection />
|
|
606
|
-
<Divider sx={{ bgcolor: "neutral.outlinedBorder" }} />
|
|
607
|
-
<ActivitySection />
|
|
608
|
-
|
|
609
|
-
// In expanded mode: Add as middle column or section
|
|
610
|
-
```
|
|
611
|
-
|
|
612
|
-
### UI Mockup Description
|
|
613
|
-
|
|
614
|
-
```
|
|
615
|
-
┌─────────────────────────────────────┐
|
|
616
|
-
│ USAGE BREAKDOWN │
|
|
617
|
-
├─────────────────────────────────────┤
|
|
618
|
-
│ ┌─────────────────────────────────┐ │
|
|
619
|
-
│ │ TODAY │ │
|
|
620
|
-
│ │ $2.45 125K tokens │ │
|
|
621
|
-
│ │ 3 sessions │ │
|
|
622
|
-
│ └─────────────────────────────────┘ │
|
|
623
|
-
│ ┌─────────────────────────────────┐ │
|
|
624
|
-
│ │ THIS WEEK │ │
|
|
625
|
-
│ │ $12.34 456K tokens │ │
|
|
626
|
-
│ │ 15 sessions │ │
|
|
627
|
-
│ └─────────────────────────────────┘ │
|
|
628
|
-
│ ┌─────────────────────────────────┐ │
|
|
629
|
-
│ │ THIS MONTH │ │
|
|
630
|
-
│ │ $45.67 1.2M tokens │ │
|
|
631
|
-
│ │ 52 sessions │ │
|
|
632
|
-
│ └─────────────────────────────────┘ │
|
|
633
|
-
└─────────────────────────────────────┘
|
|
634
|
-
```
|
|
635
|
-
|
|
636
|
-
### Success Criteria
|
|
637
|
-
|
|
638
|
-
#### Automated Verification
|
|
639
|
-
- [ ] Build succeeds: `cd ui && npm run build`
|
|
640
|
-
|
|
641
|
-
#### Manual Verification
|
|
642
|
-
- [ ] Agent detail shows usage breakdown
|
|
643
|
-
- [ ] Daily/weekly/monthly values are accurate
|
|
644
|
-
- [ ] Updates when new sessions are created
|
|
645
|
-
|
|
646
|
-
---
|
|
647
|
-
|
|
648
|
-
## Phase 5: Agent Detail Page - Charts (Expanded View)
|
|
649
|
-
|
|
650
|
-
### Overview
|
|
651
|
-
Add charts to the expanded agent detail view showing usage trends and breakdowns.
|
|
652
|
-
|
|
653
|
-
### Changes Required
|
|
654
|
-
|
|
655
|
-
#### 1. Install Recharts
|
|
656
|
-
**File**: `ui/package.json`
|
|
657
|
-
**Changes**: Add recharts dependency
|
|
658
|
-
|
|
659
|
-
```bash
|
|
660
|
-
cd ui && npm install recharts
|
|
661
|
-
```
|
|
662
|
-
|
|
663
|
-
#### 2. Create UsageCharts Component
|
|
664
|
-
**File**: `ui/src/components/UsageCharts.tsx` (new file)
|
|
665
|
-
**Changes**: Create chart components
|
|
666
|
-
|
|
667
|
-
```typescript
|
|
668
|
-
import { useMemo } from "react";
|
|
669
|
-
import Box from "@mui/joy/Box";
|
|
670
|
-
import Typography from "@mui/joy/Typography";
|
|
671
|
-
import { useColorScheme } from "@mui/joy/styles";
|
|
672
|
-
import {
|
|
673
|
-
LineChart,
|
|
674
|
-
Line,
|
|
675
|
-
AreaChart,
|
|
676
|
-
Area,
|
|
677
|
-
BarChart,
|
|
678
|
-
Bar,
|
|
679
|
-
PieChart,
|
|
680
|
-
Pie,
|
|
681
|
-
Cell,
|
|
682
|
-
XAxis,
|
|
683
|
-
YAxis,
|
|
684
|
-
CartesianGrid,
|
|
685
|
-
Tooltip,
|
|
686
|
-
ResponsiveContainer,
|
|
687
|
-
Legend,
|
|
688
|
-
} from "recharts";
|
|
689
|
-
import type { SessionCost } from "../types/api";
|
|
690
|
-
|
|
691
|
-
interface UsageChartsProps {
|
|
692
|
-
costs: SessionCost[];
|
|
693
|
-
timeRange?: "7d" | "30d" | "90d";
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
export function CostTrendChart({ costs, timeRange = "30d" }: UsageChartsProps) {
|
|
697
|
-
const { mode } = useColorScheme();
|
|
698
|
-
const isDark = mode === "dark";
|
|
699
|
-
|
|
700
|
-
const chartData = useMemo(() => {
|
|
701
|
-
const days = timeRange === "7d" ? 7 : timeRange === "30d" ? 30 : 90;
|
|
702
|
-
const data: { date: string; cost: number; tokens: number }[] = [];
|
|
703
|
-
|
|
704
|
-
for (let i = days - 1; i >= 0; i--) {
|
|
705
|
-
const date = new Date();
|
|
706
|
-
date.setDate(date.getDate() - i);
|
|
707
|
-
const dateStr = date.toISOString().split("T")[0];
|
|
708
|
-
|
|
709
|
-
const dayCosts = costs.filter(
|
|
710
|
-
(c) => c.createdAt.startsWith(dateStr)
|
|
711
|
-
);
|
|
712
|
-
|
|
713
|
-
data.push({
|
|
714
|
-
date: date.toLocaleDateString("en-US", { month: "short", day: "numeric" }),
|
|
715
|
-
cost: dayCosts.reduce((sum, c) => sum + c.totalCostUsd, 0),
|
|
716
|
-
tokens: dayCosts.reduce((sum, c) => sum + c.inputTokens + c.outputTokens, 0),
|
|
717
|
-
});
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
return data;
|
|
721
|
-
}, [costs, timeRange]);
|
|
722
|
-
|
|
723
|
-
const colors = {
|
|
724
|
-
line: isDark ? "#F5A623" : "#D48806",
|
|
725
|
-
grid: isDark ? "#3D3020" : "#E5DDD0",
|
|
726
|
-
text: isDark ? "#8B7355" : "#6B5344",
|
|
727
|
-
};
|
|
728
|
-
|
|
729
|
-
return (
|
|
730
|
-
<Box sx={{ width: "100%", height: 250 }}>
|
|
731
|
-
<Typography sx={{ fontFamily: "code", fontSize: "0.7rem", color: "text.tertiary", mb: 1 }}>
|
|
732
|
-
COST TREND
|
|
733
|
-
</Typography>
|
|
734
|
-
<ResponsiveContainer>
|
|
735
|
-
<AreaChart data={chartData}>
|
|
736
|
-
<CartesianGrid strokeDasharray="3 3" stroke={colors.grid} />
|
|
737
|
-
<XAxis
|
|
738
|
-
dataKey="date"
|
|
739
|
-
tick={{ fontSize: 10, fill: colors.text }}
|
|
740
|
-
tickLine={false}
|
|
741
|
-
/>
|
|
742
|
-
<YAxis
|
|
743
|
-
tick={{ fontSize: 10, fill: colors.text }}
|
|
744
|
-
tickFormatter={(v) => `$${v.toFixed(2)}`}
|
|
745
|
-
tickLine={false}
|
|
746
|
-
/>
|
|
747
|
-
<Tooltip
|
|
748
|
-
contentStyle={{
|
|
749
|
-
backgroundColor: isDark ? "#1A130E" : "#FFFFFF",
|
|
750
|
-
border: `1px solid ${colors.grid}`,
|
|
751
|
-
borderRadius: 4,
|
|
752
|
-
fontFamily: "monospace",
|
|
753
|
-
fontSize: 12,
|
|
754
|
-
}}
|
|
755
|
-
formatter={(value: number) => [`$${value.toFixed(4)}`, "Cost"]}
|
|
756
|
-
/>
|
|
757
|
-
<Area
|
|
758
|
-
type="monotone"
|
|
759
|
-
dataKey="cost"
|
|
760
|
-
stroke={colors.line}
|
|
761
|
-
fill={`${colors.line}40`}
|
|
762
|
-
strokeWidth={2}
|
|
763
|
-
/>
|
|
764
|
-
</AreaChart>
|
|
765
|
-
</ResponsiveContainer>
|
|
766
|
-
</Box>
|
|
767
|
-
);
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
export function TokenDistributionChart({ costs }: { costs: SessionCost[] }) {
|
|
771
|
-
const { mode } = useColorScheme();
|
|
772
|
-
const isDark = mode === "dark";
|
|
773
|
-
|
|
774
|
-
const data = useMemo(() => {
|
|
775
|
-
const totals = costs.reduce(
|
|
776
|
-
(acc, c) => ({
|
|
777
|
-
input: acc.input + c.inputTokens,
|
|
778
|
-
output: acc.output + c.outputTokens,
|
|
779
|
-
cacheRead: acc.cacheRead + c.cacheReadTokens,
|
|
780
|
-
cacheWrite: acc.cacheWrite + c.cacheWriteTokens,
|
|
781
|
-
}),
|
|
782
|
-
{ input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }
|
|
783
|
-
);
|
|
784
|
-
|
|
785
|
-
return [
|
|
786
|
-
{ name: "Input", value: totals.input, color: "#3B82F6" },
|
|
787
|
-
{ name: "Output", value: totals.output, color: "#F5A623" },
|
|
788
|
-
{ name: "Cache Read", value: totals.cacheRead, color: "#22C55E" },
|
|
789
|
-
{ name: "Cache Write", value: totals.cacheWrite, color: "#D4A574" },
|
|
790
|
-
].filter((d) => d.value > 0);
|
|
791
|
-
}, [costs]);
|
|
792
|
-
|
|
793
|
-
return (
|
|
794
|
-
<Box sx={{ width: "100%", height: 250 }}>
|
|
795
|
-
<Typography sx={{ fontFamily: "code", fontSize: "0.7rem", color: "text.tertiary", mb: 1 }}>
|
|
796
|
-
TOKEN DISTRIBUTION
|
|
797
|
-
</Typography>
|
|
798
|
-
<ResponsiveContainer>
|
|
799
|
-
<PieChart>
|
|
800
|
-
<Pie
|
|
801
|
-
data={data}
|
|
802
|
-
dataKey="value"
|
|
803
|
-
nameKey="name"
|
|
804
|
-
cx="50%"
|
|
805
|
-
cy="50%"
|
|
806
|
-
outerRadius={80}
|
|
807
|
-
label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`}
|
|
808
|
-
labelLine={false}
|
|
809
|
-
>
|
|
810
|
-
{data.map((entry, index) => (
|
|
811
|
-
<Cell key={`cell-${index}`} fill={entry.color} />
|
|
812
|
-
))}
|
|
813
|
-
</Pie>
|
|
814
|
-
<Tooltip
|
|
815
|
-
formatter={(value: number) => [value.toLocaleString(), "Tokens"]}
|
|
816
|
-
contentStyle={{
|
|
817
|
-
backgroundColor: isDark ? "#1A130E" : "#FFFFFF",
|
|
818
|
-
borderRadius: 4,
|
|
819
|
-
fontFamily: "monospace",
|
|
820
|
-
fontSize: 12,
|
|
821
|
-
}}
|
|
822
|
-
/>
|
|
823
|
-
<Legend />
|
|
824
|
-
</PieChart>
|
|
825
|
-
</ResponsiveContainer>
|
|
826
|
-
</Box>
|
|
827
|
-
);
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
export function ModelUsageChart({ costs }: { costs: SessionCost[] }) {
|
|
831
|
-
const { mode } = useColorScheme();
|
|
832
|
-
const isDark = mode === "dark";
|
|
833
|
-
|
|
834
|
-
const data = useMemo(() => {
|
|
835
|
-
const byModel = new Map<string, { cost: number; sessions: number }>();
|
|
836
|
-
|
|
837
|
-
costs.forEach((c) => {
|
|
838
|
-
const existing = byModel.get(c.model) || { cost: 0, sessions: 0 };
|
|
839
|
-
byModel.set(c.model, {
|
|
840
|
-
cost: existing.cost + c.totalCostUsd,
|
|
841
|
-
sessions: existing.sessions + 1,
|
|
842
|
-
});
|
|
843
|
-
});
|
|
844
|
-
|
|
845
|
-
return Array.from(byModel.entries()).map(([model, data]) => ({
|
|
846
|
-
model,
|
|
847
|
-
cost: data.cost,
|
|
848
|
-
sessions: data.sessions,
|
|
849
|
-
}));
|
|
850
|
-
}, [costs]);
|
|
851
|
-
|
|
852
|
-
const colors = {
|
|
853
|
-
bar: isDark ? "#F5A623" : "#D48806",
|
|
854
|
-
grid: isDark ? "#3D3020" : "#E5DDD0",
|
|
855
|
-
text: isDark ? "#8B7355" : "#6B5344",
|
|
856
|
-
};
|
|
857
|
-
|
|
858
|
-
return (
|
|
859
|
-
<Box sx={{ width: "100%", height: 200 }}>
|
|
860
|
-
<Typography sx={{ fontFamily: "code", fontSize: "0.7rem", color: "text.tertiary", mb: 1 }}>
|
|
861
|
-
COST BY MODEL
|
|
862
|
-
</Typography>
|
|
863
|
-
<ResponsiveContainer>
|
|
864
|
-
<BarChart data={data} layout="vertical">
|
|
865
|
-
<CartesianGrid strokeDasharray="3 3" stroke={colors.grid} />
|
|
866
|
-
<XAxis
|
|
867
|
-
type="number"
|
|
868
|
-
tick={{ fontSize: 10, fill: colors.text }}
|
|
869
|
-
tickFormatter={(v) => `$${v.toFixed(2)}`}
|
|
870
|
-
/>
|
|
871
|
-
<YAxis
|
|
872
|
-
type="category"
|
|
873
|
-
dataKey="model"
|
|
874
|
-
tick={{ fontSize: 10, fill: colors.text }}
|
|
875
|
-
width={60}
|
|
876
|
-
/>
|
|
877
|
-
<Tooltip
|
|
878
|
-
formatter={(value: number) => [`$${value.toFixed(4)}`, "Cost"]}
|
|
879
|
-
contentStyle={{
|
|
880
|
-
backgroundColor: isDark ? "#1A130E" : "#FFFFFF",
|
|
881
|
-
borderRadius: 4,
|
|
882
|
-
fontFamily: "monospace",
|
|
883
|
-
fontSize: 12,
|
|
884
|
-
}}
|
|
885
|
-
/>
|
|
886
|
-
<Bar dataKey="cost" fill={colors.bar} radius={[0, 4, 4, 0]} />
|
|
887
|
-
</BarChart>
|
|
888
|
-
</ResponsiveContainer>
|
|
889
|
-
</Box>
|
|
890
|
-
);
|
|
891
|
-
}
|
|
892
|
-
```
|
|
893
|
-
|
|
894
|
-
#### 3. Update AgentDetailPanel (Expanded View)
|
|
895
|
-
**File**: `ui/src/components/AgentDetailPanel.tsx`
|
|
896
|
-
**Changes**: Add charts section in expanded mode
|
|
897
|
-
|
|
898
|
-
```typescript
|
|
899
|
-
// Add import
|
|
900
|
-
import { CostTrendChart, TokenDistributionChart, ModelUsageChart } from "./UsageCharts";
|
|
901
|
-
import { useSessionCosts } from "../hooks/queries";
|
|
902
|
-
|
|
903
|
-
// Inside component, add costs hook
|
|
904
|
-
const { data: agentCosts } = useSessionCosts({ agentId, limit: 500 });
|
|
905
|
-
|
|
906
|
-
// Add ChartsSection component
|
|
907
|
-
const ChartsSection = () => (
|
|
908
|
-
<Box sx={{ p: 2, display: "flex", flexDirection: "column", gap: 3 }}>
|
|
909
|
-
<Typography
|
|
910
|
-
sx={{
|
|
911
|
-
fontFamily: "code",
|
|
912
|
-
fontSize: "0.7rem",
|
|
913
|
-
color: "text.tertiary",
|
|
914
|
-
letterSpacing: "0.05em",
|
|
915
|
-
}}
|
|
916
|
-
>
|
|
917
|
-
USAGE ANALYTICS
|
|
918
|
-
</Typography>
|
|
919
|
-
|
|
920
|
-
{agentCosts && agentCosts.length > 0 ? (
|
|
921
|
-
<>
|
|
922
|
-
<CostTrendChart costs={agentCosts} timeRange="30d" />
|
|
923
|
-
<Box sx={{ display: "flex", gap: 2, flexWrap: "wrap" }}>
|
|
924
|
-
<Box sx={{ flex: 1, minWidth: 250 }}>
|
|
925
|
-
<TokenDistributionChart costs={agentCosts} />
|
|
926
|
-
</Box>
|
|
927
|
-
<Box sx={{ flex: 1, minWidth: 250 }}>
|
|
928
|
-
<ModelUsageChart costs={agentCosts} />
|
|
929
|
-
</Box>
|
|
930
|
-
</Box>
|
|
931
|
-
</>
|
|
932
|
-
) : (
|
|
933
|
-
<Typography sx={{ fontFamily: "code", fontSize: "0.75rem", color: "text.tertiary" }}>
|
|
934
|
-
No usage data available
|
|
935
|
-
</Typography>
|
|
936
|
-
)}
|
|
937
|
-
</Box>
|
|
938
|
-
);
|
|
939
|
-
|
|
940
|
-
// In expanded layout, add ChartsSection as a new column/section
|
|
941
|
-
{expanded && (
|
|
942
|
-
<>
|
|
943
|
-
{/* Existing columns */}
|
|
944
|
-
<Box sx={{ flex: 1, borderLeft: "1px solid", borderColor: "neutral.outlinedBorder", overflow: "auto" }}>
|
|
945
|
-
<ChartsSection />
|
|
946
|
-
</Box>
|
|
947
|
-
</>
|
|
948
|
-
)}
|
|
949
|
-
```
|
|
950
|
-
|
|
951
|
-
### Success Criteria
|
|
952
|
-
|
|
953
|
-
#### Automated Verification
|
|
954
|
-
- [ ] Build succeeds: `cd ui && npm run build`
|
|
955
|
-
- [ ] No TypeScript errors
|
|
956
|
-
|
|
957
|
-
#### Manual Verification
|
|
958
|
-
- [ ] Charts render correctly in expanded agent detail
|
|
959
|
-
- [ ] Charts are responsive
|
|
960
|
-
- [ ] Dark/light mode styling works
|
|
961
|
-
|
|
962
|
-
---
|
|
963
|
-
|
|
964
|
-
## Phase 6: Task Detail - Cost Totals
|
|
965
|
-
|
|
966
|
-
### Overview
|
|
967
|
-
Show cost totals for the task in the task detail panel.
|
|
968
|
-
|
|
969
|
-
### Changes Required
|
|
970
|
-
|
|
971
|
-
#### 1. TaskDetailPanel Component
|
|
972
|
-
**File**: `ui/src/components/TaskDetailPanel.tsx`
|
|
973
|
-
**Changes**: Add cost display
|
|
974
|
-
|
|
975
|
-
```typescript
|
|
976
|
-
// Add import
|
|
977
|
-
import { useTask, useAgents, useTaskSessionLogs, useTaskUsage } from "../hooks/queries";
|
|
978
|
-
import { formatCurrency, formatCompactNumber, formatDuration } from "../lib/utils";
|
|
979
|
-
|
|
980
|
-
// Inside component, add usage hook
|
|
981
|
-
const { data: taskUsage } = useTaskUsage(taskId);
|
|
982
|
-
|
|
983
|
-
// Add to DetailsSection, after Elapsed Time field (around line 243)
|
|
984
|
-
{taskUsage && taskUsage.sessionCount > 0 && (
|
|
985
|
-
<>
|
|
986
|
-
<Divider sx={{ my: 1.5 }} />
|
|
987
|
-
<Typography
|
|
988
|
-
sx={{
|
|
989
|
-
fontFamily: "code",
|
|
990
|
-
fontSize: "0.65rem",
|
|
991
|
-
color: "text.tertiary",
|
|
992
|
-
letterSpacing: "0.05em",
|
|
993
|
-
mb: 1,
|
|
994
|
-
}}
|
|
995
|
-
>
|
|
996
|
-
TASK COSTS
|
|
997
|
-
</Typography>
|
|
998
|
-
|
|
999
|
-
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
|
1000
|
-
<Typography sx={{ fontFamily: "code", fontSize: "0.75rem", color: "text.tertiary" }}>
|
|
1001
|
-
Total Cost
|
|
1002
|
-
</Typography>
|
|
1003
|
-
<Typography sx={{ fontFamily: "code", fontSize: "0.9rem", fontWeight: 600, color: colors.amber }}>
|
|
1004
|
-
{formatCurrency(taskUsage.totalCostUsd)}
|
|
1005
|
-
</Typography>
|
|
1006
|
-
</Box>
|
|
1007
|
-
|
|
1008
|
-
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
|
1009
|
-
<Typography sx={{ fontFamily: "code", fontSize: "0.75rem", color: "text.tertiary" }}>
|
|
1010
|
-
Total Tokens
|
|
1011
|
-
</Typography>
|
|
1012
|
-
<Typography sx={{ fontFamily: "code", fontSize: "0.8rem", color: "text.secondary" }}>
|
|
1013
|
-
{formatCompactNumber(taskUsage.totalTokens)}
|
|
1014
|
-
</Typography>
|
|
1015
|
-
</Box>
|
|
1016
|
-
|
|
1017
|
-
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
|
1018
|
-
<Typography sx={{ fontFamily: "code", fontSize: "0.75rem", color: "text.tertiary" }}>
|
|
1019
|
-
Sessions
|
|
1020
|
-
</Typography>
|
|
1021
|
-
<Typography sx={{ fontFamily: "code", fontSize: "0.8rem", color: "text.secondary" }}>
|
|
1022
|
-
{taskUsage.sessionCount}
|
|
1023
|
-
</Typography>
|
|
1024
|
-
</Box>
|
|
1025
|
-
|
|
1026
|
-
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
|
1027
|
-
<Typography sx={{ fontFamily: "code", fontSize: "0.75rem", color: "text.tertiary" }}>
|
|
1028
|
-
Compute Time
|
|
1029
|
-
</Typography>
|
|
1030
|
-
<Typography sx={{ fontFamily: "code", fontSize: "0.8rem", color: "text.secondary" }}>
|
|
1031
|
-
{formatDuration(taskUsage.totalDurationMs)}
|
|
1032
|
-
</Typography>
|
|
1033
|
-
</Box>
|
|
1034
|
-
|
|
1035
|
-
{/* Token breakdown */}
|
|
1036
|
-
<Box sx={{ mt: 1, p: 1, bgcolor: "background.level1", borderRadius: 1, border: "1px solid", borderColor: "neutral.outlinedBorder" }}>
|
|
1037
|
-
<Typography sx={{ fontFamily: "code", fontSize: "0.6rem", color: "text.tertiary", mb: 0.5 }}>
|
|
1038
|
-
TOKEN BREAKDOWN
|
|
1039
|
-
</Typography>
|
|
1040
|
-
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 1 }}>
|
|
1041
|
-
<Chip size="sm" sx={{ fontFamily: "code", fontSize: "0.6rem" }}>
|
|
1042
|
-
In: {formatCompactNumber(taskUsage.inputTokens)}
|
|
1043
|
-
</Chip>
|
|
1044
|
-
<Chip size="sm" sx={{ fontFamily: "code", fontSize: "0.6rem" }}>
|
|
1045
|
-
Out: {formatCompactNumber(taskUsage.outputTokens)}
|
|
1046
|
-
</Chip>
|
|
1047
|
-
{taskUsage.cacheReadTokens > 0 && (
|
|
1048
|
-
<Chip size="sm" sx={{ fontFamily: "code", fontSize: "0.6rem" }}>
|
|
1049
|
-
Cache R: {formatCompactNumber(taskUsage.cacheReadTokens)}
|
|
1050
|
-
</Chip>
|
|
1051
|
-
)}
|
|
1052
|
-
{taskUsage.cacheWriteTokens > 0 && (
|
|
1053
|
-
<Chip size="sm" sx={{ fontFamily: "code", fontSize: "0.6rem" }}>
|
|
1054
|
-
Cache W: {formatCompactNumber(taskUsage.cacheWriteTokens)}
|
|
1055
|
-
</Chip>
|
|
1056
|
-
)}
|
|
1057
|
-
</Box>
|
|
1058
|
-
</Box>
|
|
1059
|
-
</>
|
|
1060
|
-
)}
|
|
1061
|
-
```
|
|
1062
|
-
|
|
1063
|
-
### UI Mockup Description
|
|
1064
|
-
|
|
1065
|
-
```
|
|
1066
|
-
┌─────────────────────────────────────┐
|
|
1067
|
-
│ Status ● completed │
|
|
1068
|
-
│ Agent Worker-1 │
|
|
1069
|
-
│ Elapsed Time 12m 34s │
|
|
1070
|
-
├─────────────────────────────────────┤
|
|
1071
|
-
│ TASK COSTS │
|
|
1072
|
-
│ Total Cost $0.4523 │
|
|
1073
|
-
│ Total Tokens 125,432 │
|
|
1074
|
-
│ Sessions 3 │
|
|
1075
|
-
│ Compute Time 8m 12s │
|
|
1076
|
-
│ ┌─────────────────────────────────┐ │
|
|
1077
|
-
│ │ TOKEN BREAKDOWN │ │
|
|
1078
|
-
│ │ [In: 45K] [Out: 80K] [Cache: 5K]│ │
|
|
1079
|
-
│ └─────────────────────────────────┘ │
|
|
1080
|
-
└─────────────────────────────────────┘
|
|
1081
|
-
```
|
|
1082
|
-
|
|
1083
|
-
### Success Criteria
|
|
1084
|
-
|
|
1085
|
-
#### Automated Verification
|
|
1086
|
-
- [ ] Build succeeds: `cd ui && npm run build`
|
|
1087
|
-
|
|
1088
|
-
#### Manual Verification
|
|
1089
|
-
- [ ] Task detail shows cost totals
|
|
1090
|
-
- [ ] Values match actual session costs for the task
|
|
1091
|
-
- [ ] Displays gracefully when no costs exist
|
|
1092
|
-
|
|
1093
|
-
---
|
|
1094
|
-
|
|
1095
|
-
## Phase 7: Usage Tab - Dedicated Dashboard
|
|
1096
|
-
|
|
1097
|
-
### Overview
|
|
1098
|
-
Create a new "Usage" tab in the dashboard with comprehensive analytics.
|
|
1099
|
-
|
|
1100
|
-
### Changes Required
|
|
1101
|
-
|
|
1102
|
-
#### 1. Create UsagePanel Component
|
|
1103
|
-
**File**: `ui/src/components/UsagePanel.tsx` (new file)
|
|
1104
|
-
|
|
1105
|
-
```typescript
|
|
1106
|
-
import { useState, useMemo } from "react";
|
|
1107
|
-
import Box from "@mui/joy/Box";
|
|
1108
|
-
import Typography from "@mui/joy/Typography";
|
|
1109
|
-
import Select from "@mui/joy/Select";
|
|
1110
|
-
import Option from "@mui/joy/Option";
|
|
1111
|
-
import Card from "@mui/joy/Card";
|
|
1112
|
-
import Table from "@mui/joy/Table";
|
|
1113
|
-
import { useColorScheme } from "@mui/joy/styles";
|
|
1114
|
-
import { useSessionCosts, useAgents } from "../hooks/queries";
|
|
1115
|
-
import { formatCurrency, formatCompactNumber, formatDuration } from "../lib/utils";
|
|
1116
|
-
import { CostTrendChart, TokenDistributionChart, ModelUsageChart } from "./UsageCharts";
|
|
1117
|
-
|
|
1118
|
-
type TimeRange = "7d" | "30d" | "90d" | "all";
|
|
1119
|
-
|
|
1120
|
-
export default function UsagePanel() {
|
|
1121
|
-
const [timeRange, setTimeRange] = useState<TimeRange>("30d");
|
|
1122
|
-
const { data: allCosts, isLoading } = useSessionCosts({ limit: 5000 });
|
|
1123
|
-
const { data: agents } = useAgents();
|
|
1124
|
-
const { mode } = useColorScheme();
|
|
1125
|
-
const isDark = mode === "dark";
|
|
1126
|
-
|
|
1127
|
-
const colors = {
|
|
1128
|
-
amber: isDark ? "#F5A623" : "#D48806",
|
|
1129
|
-
gold: isDark ? "#D4A574" : "#8B6914",
|
|
1130
|
-
blue: "#3B82F6",
|
|
1131
|
-
green: "#22C55E",
|
|
1132
|
-
hoverBg: isDark ? "rgba(245, 166, 35, 0.08)" : "rgba(212, 136, 6, 0.08)",
|
|
1133
|
-
};
|
|
1134
|
-
|
|
1135
|
-
// Filter costs by time range
|
|
1136
|
-
const filteredCosts = useMemo(() => {
|
|
1137
|
-
if (!allCosts) return [];
|
|
1138
|
-
if (timeRange === "all") return allCosts;
|
|
1139
|
-
|
|
1140
|
-
const days = timeRange === "7d" ? 7 : timeRange === "30d" ? 30 : 90;
|
|
1141
|
-
const cutoff = new Date();
|
|
1142
|
-
cutoff.setDate(cutoff.getDate() - days);
|
|
1143
|
-
|
|
1144
|
-
return allCosts.filter((c) => new Date(c.createdAt) >= cutoff);
|
|
1145
|
-
}, [allCosts, timeRange]);
|
|
1146
|
-
|
|
1147
|
-
// Aggregate stats
|
|
1148
|
-
const stats = useMemo(() => {
|
|
1149
|
-
if (!filteredCosts.length) return null;
|
|
1150
|
-
|
|
1151
|
-
return {
|
|
1152
|
-
totalCost: filteredCosts.reduce((sum, c) => sum + c.totalCostUsd, 0),
|
|
1153
|
-
totalTokens: filteredCosts.reduce((sum, c) => sum + c.inputTokens + c.outputTokens, 0),
|
|
1154
|
-
totalSessions: filteredCosts.length,
|
|
1155
|
-
totalDuration: filteredCosts.reduce((sum, c) => sum + c.durationMs, 0),
|
|
1156
|
-
avgCostPerSession: filteredCosts.reduce((sum, c) => sum + c.totalCostUsd, 0) / filteredCosts.length,
|
|
1157
|
-
inputTokens: filteredCosts.reduce((sum, c) => sum + c.inputTokens, 0),
|
|
1158
|
-
outputTokens: filteredCosts.reduce((sum, c) => sum + c.outputTokens, 0),
|
|
1159
|
-
};
|
|
1160
|
-
}, [filteredCosts]);
|
|
1161
|
-
|
|
1162
|
-
// Per-agent breakdown
|
|
1163
|
-
const agentBreakdown = useMemo(() => {
|
|
1164
|
-
if (!filteredCosts.length || !agents) return [];
|
|
1165
|
-
|
|
1166
|
-
const byAgent = new Map<string, { cost: number; tokens: number; sessions: number }>();
|
|
1167
|
-
|
|
1168
|
-
filteredCosts.forEach((c) => {
|
|
1169
|
-
const existing = byAgent.get(c.agentId) || { cost: 0, tokens: 0, sessions: 0 };
|
|
1170
|
-
byAgent.set(c.agentId, {
|
|
1171
|
-
cost: existing.cost + c.totalCostUsd,
|
|
1172
|
-
tokens: existing.tokens + c.inputTokens + c.outputTokens,
|
|
1173
|
-
sessions: existing.sessions + 1,
|
|
1174
|
-
});
|
|
1175
|
-
});
|
|
1176
|
-
|
|
1177
|
-
return Array.from(byAgent.entries())
|
|
1178
|
-
.map(([agentId, data]) => ({
|
|
1179
|
-
agentId,
|
|
1180
|
-
agentName: agents.find((a) => a.id === agentId)?.name || agentId.slice(0, 8),
|
|
1181
|
-
...data,
|
|
1182
|
-
}))
|
|
1183
|
-
.sort((a, b) => b.cost - a.cost);
|
|
1184
|
-
}, [filteredCosts, agents]);
|
|
1185
|
-
|
|
1186
|
-
if (isLoading) {
|
|
1187
|
-
return (
|
|
1188
|
-
<Box sx={{ p: 3, textAlign: "center" }}>
|
|
1189
|
-
<Typography sx={{ fontFamily: "code", color: "text.tertiary" }}>
|
|
1190
|
-
Loading usage data...
|
|
1191
|
-
</Typography>
|
|
1192
|
-
</Box>
|
|
1193
|
-
);
|
|
1194
|
-
}
|
|
1195
|
-
|
|
1196
|
-
return (
|
|
1197
|
-
<Box sx={{ height: "100%", overflow: "auto", p: { xs: 1.5, md: 2 } }}>
|
|
1198
|
-
{/* Header with time range selector */}
|
|
1199
|
-
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center", mb: 3 }}>
|
|
1200
|
-
<Typography sx={{ fontFamily: "display", fontSize: "1.25rem", fontWeight: 600, color: colors.amber }}>
|
|
1201
|
-
USAGE ANALYTICS
|
|
1202
|
-
</Typography>
|
|
1203
|
-
<Select
|
|
1204
|
-
value={timeRange}
|
|
1205
|
-
onChange={(_, value) => value && setTimeRange(value)}
|
|
1206
|
-
size="sm"
|
|
1207
|
-
sx={{ fontFamily: "code", minWidth: 120 }}
|
|
1208
|
-
>
|
|
1209
|
-
<Option value="7d">Last 7 days</Option>
|
|
1210
|
-
<Option value="30d">Last 30 days</Option>
|
|
1211
|
-
<Option value="90d">Last 90 days</Option>
|
|
1212
|
-
<Option value="all">All time</Option>
|
|
1213
|
-
</Select>
|
|
1214
|
-
</Box>
|
|
1215
|
-
|
|
1216
|
-
{/* Summary Cards */}
|
|
1217
|
-
{stats && (
|
|
1218
|
-
<Box sx={{ display: "grid", gridTemplateColumns: { xs: "1fr 1fr", md: "repeat(4, 1fr)" }, gap: 2, mb: 3 }}>
|
|
1219
|
-
<SummaryCard
|
|
1220
|
-
title="TOTAL COST"
|
|
1221
|
-
value={formatCurrency(stats.totalCost)}
|
|
1222
|
-
color={colors.amber}
|
|
1223
|
-
/>
|
|
1224
|
-
<SummaryCard
|
|
1225
|
-
title="TOTAL TOKENS"
|
|
1226
|
-
value={formatCompactNumber(stats.totalTokens)}
|
|
1227
|
-
color={colors.blue}
|
|
1228
|
-
/>
|
|
1229
|
-
<SummaryCard
|
|
1230
|
-
title="SESSIONS"
|
|
1231
|
-
value={stats.totalSessions.toString()}
|
|
1232
|
-
color={colors.gold}
|
|
1233
|
-
/>
|
|
1234
|
-
<SummaryCard
|
|
1235
|
-
title="COMPUTE TIME"
|
|
1236
|
-
value={formatDuration(stats.totalDuration)}
|
|
1237
|
-
color={colors.green}
|
|
1238
|
-
/>
|
|
1239
|
-
</Box>
|
|
1240
|
-
)}
|
|
1241
|
-
|
|
1242
|
-
{/* Charts Row */}
|
|
1243
|
-
{filteredCosts.length > 0 && (
|
|
1244
|
-
<Box sx={{ display: "grid", gridTemplateColumns: { xs: "1fr", lg: "2fr 1fr" }, gap: 3, mb: 3 }}>
|
|
1245
|
-
<Card sx={{ p: 2 }}>
|
|
1246
|
-
<CostTrendChart costs={filteredCosts} timeRange={timeRange === "all" ? "90d" : timeRange} />
|
|
1247
|
-
</Card>
|
|
1248
|
-
<Card sx={{ p: 2 }}>
|
|
1249
|
-
<TokenDistributionChart costs={filteredCosts} />
|
|
1250
|
-
</Card>
|
|
1251
|
-
</Box>
|
|
1252
|
-
)}
|
|
1253
|
-
|
|
1254
|
-
{/* Model Usage */}
|
|
1255
|
-
{filteredCosts.length > 0 && (
|
|
1256
|
-
<Card sx={{ p: 2, mb: 3 }}>
|
|
1257
|
-
<ModelUsageChart costs={filteredCosts} />
|
|
1258
|
-
</Card>
|
|
1259
|
-
)}
|
|
1260
|
-
|
|
1261
|
-
{/* Agent Breakdown Table */}
|
|
1262
|
-
<Card sx={{ p: 2 }}>
|
|
1263
|
-
<Typography sx={{ fontFamily: "code", fontSize: "0.7rem", color: "text.tertiary", letterSpacing: "0.05em", mb: 2 }}>
|
|
1264
|
-
USAGE BY AGENT
|
|
1265
|
-
</Typography>
|
|
1266
|
-
<Table size="sm">
|
|
1267
|
-
<thead>
|
|
1268
|
-
<tr>
|
|
1269
|
-
<th>AGENT</th>
|
|
1270
|
-
<th style={{ textAlign: "right" }}>COST</th>
|
|
1271
|
-
<th style={{ textAlign: "right" }}>TOKENS</th>
|
|
1272
|
-
<th style={{ textAlign: "right" }}>SESSIONS</th>
|
|
1273
|
-
</tr>
|
|
1274
|
-
</thead>
|
|
1275
|
-
<tbody>
|
|
1276
|
-
{agentBreakdown.map((agent) => (
|
|
1277
|
-
<tr key={agent.agentId}>
|
|
1278
|
-
<td>
|
|
1279
|
-
<Typography sx={{ fontFamily: "code", fontSize: "0.8rem" }}>
|
|
1280
|
-
{agent.agentName}
|
|
1281
|
-
</Typography>
|
|
1282
|
-
</td>
|
|
1283
|
-
<td style={{ textAlign: "right" }}>
|
|
1284
|
-
<Typography sx={{ fontFamily: "code", fontSize: "0.8rem", color: colors.amber }}>
|
|
1285
|
-
{formatCurrency(agent.cost)}
|
|
1286
|
-
</Typography>
|
|
1287
|
-
</td>
|
|
1288
|
-
<td style={{ textAlign: "right" }}>
|
|
1289
|
-
<Typography sx={{ fontFamily: "code", fontSize: "0.8rem" }}>
|
|
1290
|
-
{formatCompactNumber(agent.tokens)}
|
|
1291
|
-
</Typography>
|
|
1292
|
-
</td>
|
|
1293
|
-
<td style={{ textAlign: "right" }}>
|
|
1294
|
-
<Typography sx={{ fontFamily: "code", fontSize: "0.8rem" }}>
|
|
1295
|
-
{agent.sessions}
|
|
1296
|
-
</Typography>
|
|
1297
|
-
</td>
|
|
1298
|
-
</tr>
|
|
1299
|
-
))}
|
|
1300
|
-
</tbody>
|
|
1301
|
-
</Table>
|
|
1302
|
-
</Card>
|
|
1303
|
-
</Box>
|
|
1304
|
-
);
|
|
1305
|
-
}
|
|
1306
|
-
|
|
1307
|
-
function SummaryCard({ title, value, color }: { title: string; value: string; color: string }) {
|
|
1308
|
-
return (
|
|
1309
|
-
<Card sx={{ p: 2, textAlign: "center" }}>
|
|
1310
|
-
<Typography sx={{ fontFamily: "code", fontSize: "0.6rem", color: "text.tertiary", letterSpacing: "0.05em", mb: 0.5 }}>
|
|
1311
|
-
{title}
|
|
1312
|
-
</Typography>
|
|
1313
|
-
<Typography sx={{ fontFamily: "code", fontSize: "1.5rem", fontWeight: 700, color }}>
|
|
1314
|
-
{value}
|
|
1315
|
-
</Typography>
|
|
1316
|
-
</Card>
|
|
1317
|
-
);
|
|
1318
|
-
}
|
|
1319
|
-
```
|
|
1320
|
-
|
|
1321
|
-
#### 2. Update Dashboard Component
|
|
1322
|
-
**File**: `ui/src/components/Dashboard.tsx`
|
|
1323
|
-
**Changes**: Add Usage tab
|
|
1324
|
-
|
|
1325
|
-
```typescript
|
|
1326
|
-
// Add import (after other component imports)
|
|
1327
|
-
import UsagePanel from "./UsagePanel";
|
|
1328
|
-
|
|
1329
|
-
// Update activeTab type (line ~104)
|
|
1330
|
-
const [activeTab, setActiveTab] = useState<"agents" | "tasks" | "chat" | "services" | "usage">("agents");
|
|
1331
|
-
|
|
1332
|
-
// Add Usage tab to TabList (after Services tab, around line 361)
|
|
1333
|
-
<Tab value="usage">USAGE</Tab>
|
|
1334
|
-
|
|
1335
|
-
// Add Usage TabPanel (after Services TabPanel, around line 529)
|
|
1336
|
-
<TabPanel
|
|
1337
|
-
value="usage"
|
|
1338
|
-
sx={{
|
|
1339
|
-
p: 0,
|
|
1340
|
-
pt: 2,
|
|
1341
|
-
flex: 1,
|
|
1342
|
-
minHeight: 0,
|
|
1343
|
-
"&[hidden]": {
|
|
1344
|
-
display: "none",
|
|
1345
|
-
},
|
|
1346
|
-
}}
|
|
1347
|
-
>
|
|
1348
|
-
<UsagePanel />
|
|
1349
|
-
</TabPanel>
|
|
1350
|
-
|
|
1351
|
-
// Update handleTabChange to handle "usage" (around line 192)
|
|
1352
|
-
} else if (tab === "usage") {
|
|
1353
|
-
setSelectedAgentId(null);
|
|
1354
|
-
setSelectedTaskId(null);
|
|
1355
|
-
setSelectedChannelId(null);
|
|
1356
|
-
setSelectedThreadId(null);
|
|
1357
|
-
setPreFilterAgentId(undefined);
|
|
1358
|
-
setAgentStatusFilter("all");
|
|
1359
|
-
setTaskStatusFilter("all");
|
|
1360
|
-
updateUrl({ tab: "usage", agent: null, task: null, channel: null, agentStatus: null, taskStatus: null, expand: false });
|
|
1361
|
-
}
|
|
1362
|
-
|
|
1363
|
-
// Update getUrlParams to handle "usage" tab (around line 26)
|
|
1364
|
-
tab: params.get("tab") as "agents" | "tasks" | "chat" | "services" | "usage" | null,
|
|
1365
|
-
```
|
|
1366
|
-
|
|
1367
|
-
### UI Mockup Description
|
|
1368
|
-
|
|
1369
|
-
```
|
|
1370
|
-
┌─────────────────────────────────────────────────────────────────────┐
|
|
1371
|
-
│ USAGE ANALYTICS [Last 30 days ▼] │
|
|
1372
|
-
├─────────────────────────────────────────────────────────────────────┤
|
|
1373
|
-
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
|
1374
|
-
│ │ TOTAL │ │ TOTAL │ │ SESSIONS │ │ COMPUTE │ │
|
|
1375
|
-
│ │ COST │ │ TOKENS │ │ │ │ TIME │ │
|
|
1376
|
-
│ │ $156.78 │ │ 4.2M │ │ 342 │ │ 12h 34m │ │
|
|
1377
|
-
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
|
|
1378
|
-
├─────────────────────────────────────────────────────────────────────┤
|
|
1379
|
-
│ ┌─────────────────────────────────────┐ ┌───────────────────────┐ │
|
|
1380
|
-
│ │ COST TREND │ │ TOKEN DISTRIBUTION │ │
|
|
1381
|
-
│ │ [Area Chart] │ │ [Pie Chart] │ │
|
|
1382
|
-
│ │ │ │ │ │
|
|
1383
|
-
│ └─────────────────────────────────────┘ └───────────────────────┘ │
|
|
1384
|
-
├─────────────────────────────────────────────────────────────────────┤
|
|
1385
|
-
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
|
1386
|
-
│ │ COST BY MODEL │ │
|
|
1387
|
-
│ │ [Horizontal Bar Chart] │ │
|
|
1388
|
-
│ └─────────────────────────────────────────────────────────────────┘ │
|
|
1389
|
-
├─────────────────────────────────────────────────────────────────────┤
|
|
1390
|
-
│ USAGE BY AGENT │
|
|
1391
|
-
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
|
1392
|
-
│ │ AGENT │ COST │ TOKENS │ SESSIONS │ │
|
|
1393
|
-
│ │ Worker-1 │ $45.23 │ 1.2M │ 89 │ │
|
|
1394
|
-
│ │ Worker-2 │ $34.56 │ 892K │ 67 │ │
|
|
1395
|
-
│ │ Lead │ $12.34 │ 345K │ 34 │ │
|
|
1396
|
-
│ └─────────────────────────────────────────────────────────────────┘ │
|
|
1397
|
-
└─────────────────────────────────────────────────────────────────────┘
|
|
1398
|
-
```
|
|
1399
|
-
|
|
1400
|
-
### Success Criteria
|
|
1401
|
-
|
|
1402
|
-
#### Automated Verification
|
|
1403
|
-
- [ ] Build succeeds: `cd ui && npm run build`
|
|
1404
|
-
- [ ] TypeScript compiles: `cd ui && npm run typecheck`
|
|
1405
|
-
|
|
1406
|
-
#### Manual Verification
|
|
1407
|
-
- [ ] Usage tab appears in navigation
|
|
1408
|
-
- [ ] All charts render correctly
|
|
1409
|
-
- [ ] Time range filter works
|
|
1410
|
-
- [ ] Agent breakdown table is accurate
|
|
1411
|
-
- [ ] Responsive layout works on mobile
|
|
1412
|
-
|
|
1413
|
-
---
|
|
1414
|
-
|
|
1415
|
-
## Quick Verification Reference
|
|
1416
|
-
|
|
1417
|
-
Common commands to verify the implementation:
|
|
1418
|
-
|
|
1419
|
-
```bash
|
|
1420
|
-
# TypeScript check
|
|
1421
|
-
cd ui && npm run typecheck
|
|
1422
|
-
|
|
1423
|
-
# Build
|
|
1424
|
-
cd ui && npm run build
|
|
1425
|
-
|
|
1426
|
-
# Development server
|
|
1427
|
-
cd ui && npm run dev
|
|
1428
|
-
```
|
|
1429
|
-
|
|
1430
|
-
Key files to check:
|
|
1431
|
-
- `ui/src/types/api.ts` - Type definitions
|
|
1432
|
-
- `ui/src/lib/api.ts` - API client
|
|
1433
|
-
- `ui/src/hooks/queries.ts` - React Query hooks
|
|
1434
|
-
- `ui/src/components/StatsBar.tsx` - Home page stats
|
|
1435
|
-
- `ui/src/components/AgentsPanel.tsx` - Agents table
|
|
1436
|
-
- `ui/src/components/AgentDetailPanel.tsx` - Agent detail
|
|
1437
|
-
- `ui/src/components/TaskDetailPanel.tsx` - Task detail
|
|
1438
|
-
- `ui/src/components/UsagePanel.tsx` - Usage tab (new)
|
|
1439
|
-
- `ui/src/components/UsageCharts.tsx` - Chart components (new)
|
|
1440
|
-
|
|
1441
|
-
---
|
|
1442
|
-
|
|
1443
|
-
## Testing Strategy
|
|
1444
|
-
|
|
1445
|
-
### Unit Tests
|
|
1446
|
-
- Test aggregation functions (`aggregateUsage`)
|
|
1447
|
-
- Test formatting utilities
|
|
1448
|
-
- Test time range filtering logic
|
|
1449
|
-
|
|
1450
|
-
### Integration Tests
|
|
1451
|
-
- Mock API responses and verify hook behavior
|
|
1452
|
-
- Test chart data transformation
|
|
1453
|
-
|
|
1454
|
-
### Manual Testing
|
|
1455
|
-
1. Create test session costs via API
|
|
1456
|
-
2. Verify all UI components display correct values
|
|
1457
|
-
3. Test with 0 data, small data, large data scenarios
|
|
1458
|
-
4. Test dark/light mode
|
|
1459
|
-
5. Test responsive layouts (mobile, tablet, desktop)
|
|
1460
|
-
|
|
1461
|
-
---
|
|
1462
|
-
|
|
1463
|
-
## Future Enhancements (Out of Scope)
|
|
1464
|
-
|
|
1465
|
-
1. **Backend date range API** - Add `from`/`to` parameters to GET /api/session-costs
|
|
1466
|
-
2. **Export functionality** - CSV/PDF export of usage data
|
|
1467
|
-
3. **Budget alerts** - Set cost thresholds with notifications
|
|
1468
|
-
4. **Cost predictions** - ML-based forecasting
|
|
1469
|
-
5. **Comparison views** - Compare agents or time periods
|
|
1470
|
-
6. **Real-time updates** - WebSocket for live cost streaming
|
|
1471
|
-
|
|
1472
|
-
---
|
|
1473
|
-
|
|
1474
|
-
## References
|
|
1475
|
-
|
|
1476
|
-
- Backend PR: [#28 Session Cost Tracking](https://github.com/desplega-ai/agent-swarm/pull/28)
|
|
1477
|
-
- API Endpoint: `GET /api/session-costs`
|
|
1478
|
-
- UI Library: [MUI Joy](https://mui.com/joy-ui/getting-started/)
|
|
1479
|
-
- Charting: [Recharts](https://recharts.org/)
|