diff --git a/.github/workflows/gateway-check.yml b/.github/workflows/gateway-check.yml new file mode 100644 index 000000000..7662b1d1c --- /dev/null +++ b/.github/workflows/gateway-check.yml @@ -0,0 +1,71 @@ +name: Gateway Checks + +# Guards the gateway's Go -> TypeScript codegen contract: +# - tygo-check regenerates ui/src/api/types.ts from the Go structs +# in gateway/internal/adminapi and fails on any diff, so a Go +# struct change can't land without its committed TS counterpart +# (drift would silently break the dashboard's typed fetch layer). +# - ui-build type-checks (tsc -b) and bundles the SPA, catching +# imports that the regenerated types.ts no longer satisfies. + +on: + pull_request: + paths: + - "gateway/**" + - ".github/workflows/gateway-check.yml" + push: + branches: [main] + paths: + - "gateway/**" + - ".github/workflows/gateway-check.yml" + +jobs: + tygo-check: + runs-on: ubuntu-latest + timeout-minutes: 15 + defaults: + run: + working-directory: gateway + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: gateway/go.mod + cache-dependency-path: gateway/go.sum + + # Pinned so the generated output is byte-stable — a tygo release + # changing its formatting would otherwise fail every PR. Bump in + # lockstep with the version noted in gateway/Makefile. + - name: Install tygo + run: go install github.com/gzuidhof/tygo@v0.2.21 + + - name: Check generated TS types are up to date + run: make tygo-check + + ui-build: + runs-on: ubuntu-latest + timeout-minutes: 15 + defaults: + run: + working-directory: gateway + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: 24 + cache: npm + # package-lock.json is git-ignored for this SPA, so key the + # npm cache on package.json instead. + cache-dependency-path: gateway/internal/adminapi/ui/package.json + + - name: Install SPA dependencies + run: make ui-install + + - name: Type-check and build SPA + run: make ui-build diff --git a/gateway/Makefile b/gateway/Makefile index 563923ff5..e8fa0439d 100644 --- a/gateway/Makefile +++ b/gateway/Makefile @@ -90,8 +90,9 @@ ui: ui-install ui-build # ─── tygo (Go -> TS struct codegen) ─────────────────────────────────── # -# Install once per dev machine: -# go install github.com/gzuidhof/tygo@latest +# Install once per dev machine (CI pins the same version in +# .github/workflows/gateway-check.yml — bump both together): +# go install github.com/gzuidhof/tygo@v0.2.21 tygo: tygo generate diff --git a/gateway/go.sum b/gateway/go.sum index 722d97016..0f6e99fce 100644 --- a/gateway/go.sum +++ b/gateway/go.sum @@ -54,6 +54,8 @@ github.com/mark3labs/mcp-go v0.43.2 h1:21PUSlWWiSbUPQwXIJ5WKlETixpFpq+WBpbMGDSVy github.com/mark3labs/mcp-go v0.43.2/go.mod h1:YnJfOL382MIWDx1kMY+2zsRHU/q78dBg9aFb8W6Thdw= github.com/maximhq/bifrost/core v1.5.18 h1:f1lX3sPesKTScUJHv+K9tWpd1wLLjd7CAzJY0BpbAaE= github.com/maximhq/bifrost/core v1.5.18/go.mod h1:7vry9xB5kmjT3smAVVQ2+mtfTmmeSN+7N1b8r1buaTI= +github.com/maximhq/bifrost/core v1.6.2 h1:dESW02/iyDznt/VnnjzYnevddUmplXw2YeaK/0dl3HA= +github.com/maximhq/bifrost/core v1.6.2/go.mod h1:GuRwPmx0Kh7lhZ+SnbLjVAclDI360DyKrJtioc3t5jg= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= diff --git a/gateway/internal/adminapi/evals.go b/gateway/internal/adminapi/evals.go index cf5df79ab..19f5aef5a 100644 --- a/gateway/internal/adminapi/evals.go +++ b/gateway/internal/adminapi/evals.go @@ -104,8 +104,9 @@ type evalRunRequest struct { Agent string `json:"agent,omitempty"` } -// evalRefResponse is the create/link acknowledgement. -type evalRefResponse struct { +// EvalRefResponse is the create/link acknowledgement. Exported so +// tygo emits the TS binding the SPA's eval mutations decode. +type EvalRefResponse struct { RefID string `json:"ref_id"` Linked bool `json:"linked,omitempty"` } @@ -234,7 +235,7 @@ func (h *evalHandlers) createOrLinkForAgent(w http.ResponseWriter, r *http.Reque writeError(w, http.StatusBadGateway, "catalog_write_failed", "neo4j write failed") return } - writeJSON(w, http.StatusOK, evalRefResponse{RefID: req.SetID, Linked: true}) + writeJSON(w, http.StatusOK, EvalRefResponse{RefID: req.SetID, Linked: true}) return } @@ -243,7 +244,7 @@ func (h *evalHandlers) createOrLinkForAgent(w http.ResponseWriter, r *http.Reque writeError(w, http.StatusBadRequest, "missing_field", "name (or set_id) is required") return } - var created evalRefResponse + var created EvalRefResponse if err := h.hive.call(ctx, http.MethodPost, "/api/gateway/evals", map[string]any{"name": req.Name, "description": req.Description}, &created); err != nil { relayHiveError(w, err) @@ -262,7 +263,7 @@ func (h *evalHandlers) createOrLinkForAgent(w http.ResponseWriter, r *http.Reque "set created but linking to agent failed") return } - writeJSON(w, http.StatusOK, evalRefResponse{RefID: created.RefID}) + writeJSON(w, http.StatusOK, EvalRefResponse{RefID: created.RefID}) } // linkEdge MERGEs HiveAgent-[:HAS_EVAL_SET]->EvalSet and clears any @@ -457,7 +458,7 @@ func (h *evalHandlers) createSet(w http.ResponseWriter, r *http.Request) { writeError(w, http.StatusBadRequest, "missing_field", "name is required") return } - var created evalRefResponse + var created EvalRefResponse if err := h.hive.call(r.Context(), http.MethodPost, "/api/gateway/evals", map[string]any{"name": req.Name, "description": req.Description}, &created); err != nil { relayHiveError(w, err) @@ -513,7 +514,7 @@ func (h *evalHandlers) createRequirement(w http.ResponseWriter, r *http.Request, writeError(w, http.StatusBadRequest, "missing_field", "name is required") return } - var created evalRefResponse + var created EvalRefResponse if err := h.hive.call(r.Context(), http.MethodPost, "/api/gateway/evals/"+urlSeg(setID)+"/requirements", req, &created); err != nil { relayHiveError(w, err) diff --git a/gateway/internal/adminapi/ui/dist/index.html b/gateway/internal/adminapi/ui/dist/index.html index c37010c7d..48d0c60e5 100644 --- a/gateway/internal/adminapi/ui/dist/index.html +++ b/gateway/internal/adminapi/ui/dist/index.html @@ -5,8 +5,8 @@ Agent Mothership - - + +
diff --git a/gateway/internal/adminapi/ui/src/api/client.ts b/gateway/internal/adminapi/ui/src/api/client.ts index a2a392b58..93d19a5d4 100644 --- a/gateway/internal/adminapi/ui/src/api/client.ts +++ b/gateway/internal/adminapi/ui/src/api/client.ts @@ -12,7 +12,7 @@ // or auth-refresh logic here lights the trap of having two HTTP // stacks; Tanstack Query gives us all of that one layer up. -import type { ApiError } from "./types"; +import type { ApiError } from "./manual"; const PLUGIN_PREFIX = "/_plugin"; diff --git a/gateway/internal/adminapi/ui/src/api/manual.ts b/gateway/internal/adminapi/ui/src/api/manual.ts new file mode 100644 index 000000000..44949e273 --- /dev/null +++ b/gateway/internal/adminapi/ui/src/api/manual.ts @@ -0,0 +1,169 @@ +// Hand-maintained types that tygo cannot generate. Everything the +// SPA decodes from an exported Go struct in internal/adminapi lives +// in the sibling types.ts, which is generated by `make tygo` and +// checked in CI by `make tygo-check` — never hand-edit that file. +// +// What belongs here instead, and why: +// - String-literal unions (Window / Bucket / Dimension): the Go +// side validates these as plain strings, so there is no struct +// for tygo to translate. +// - ApiError: the phase-7 error envelope is built inline by +// adminapi's writeError helper (a map, not a struct). +// - TrustOrg / TrustStatus: mirror gateway/internal/trust, a +// package outside tygo.yaml's adminapi scope, re-named for the +// dashboard (trust.Org -> TrustOrg, trust.StatusResponse -> +// TrustStatus). +// - Chat / usage shapes (ChatMessage, TokenUsage, CacheDebug, …): +// Bifrost pass-through fields that Go carries as +// json.RawMessage (tygo emits `any`); typed here to the depth +// the RunDetail drawer actually renders. +// +// Keep each block in lockstep with the Go source it names. + +// ─── enums validated Go-side as strings ───────────────────────────── + +// Window options the SPA exposes to the operator. Kept in lockstep +// with the Go-side validation in observability.go > parseWindow. +export type Window = "1h" | "6h" | "24h" | "7d" | "30d"; + +// Bucket options for the histogram endpoints — same source-of-truth +// note as Window. +export type Bucket = "1m" | "5m" | "10m" | "1h" | "6h" | "1d"; + +// Dimension values the histogram endpoint accepts. Phase 11 removed +// `realm-id` — every row in a swarm's logs.db is implicitly for +// that swarm's realm, and the realm is surfaced on the trust-status +// card instead of as a per-row column. +export type Dimension = + | "agent-name" + | "run-id" + | "session-id" + | "user-id"; + +// ─── error envelope ───────────────────────────────────────────────── + +// Phase-7 error envelope (returned on 4xx/5xx). Mirrors the map +// built by internal/adminapi's writeError helper. +export interface ApiError { + error: { + code: string; + message: string; + }; +} + +// ─── trust registry (gateway/internal/trust) ──────────────────────── + +// Trust-registry Org entry — mirrors gateway/internal/trust.Org. +// Surfaced on the dashboard's Provenance card so an operator can +// see which org's signature authorized a run, plus the pubkey / +// issuer URL the plugin would verify against. +export interface TrustOrg { + org_id: string; + pubkey: string; + issuer_url: string; + revocation_poll_seconds: number; + grace_pubkeys?: string[]; + grace_until?: string; +} + +// Trust-registry status — mirrors gateway/internal/trust.StatusResponse. +// The Provenance card on RunDetail uses `realm_id` to show the +// swarm's self-identity ("this run was processed by swarm w1"), +// since phase 11 dropped the per-row realm-id metadata column. +export interface TrustStatus { + claimed: boolean; + org_count: number; + orgs: string[]; + seed_source: "" | "env" | "api"; + last_modified: string; + /** Set on multi-swarm deployments; absent / empty on single-swarm. */ + realm_id?: string; +} + +// ─── Bifrost pass-through shapes (json.RawMessage on the Go side) ─── + +// One message in a chat-style input_history / output_message. +// Mirrors Bifrost's `schemas.ChatMessage` to the depth the drawer +// renders: role, content (string OR an array of content blocks), +// optional tool-call list (assistant) and tool_call_id (tool reply). +// Everything is optional because providers vary in which fields +// they populate, and the drawer falls back to JSON for anything it +// doesn't recognize. +export interface ChatMessage { + role?: string; + name?: string; + /** OpenAI/Anthropic-style: either a plain string or an array of + * typed content blocks. */ + content?: string | ChatContentBlock[] | null; + /** Tool messages: which prior tool_call this is the result for. */ + tool_call_id?: string; + /** Assistant tool calls. */ + tool_calls?: ChatToolCall[]; + /** Anthropic / OpenAI reasoning summaries. */ + reasoning?: string; + refusal?: string; +} + +export interface ChatContentBlock { + type: string; + text?: string; + refusal?: string; + /** Anthropic-style cache marker. When present on a block the + * provider charged this block as a cache write (or read on a + * subsequent call). */ + cache_control?: { type?: string } | null; + cachePoint?: { type?: string } | null; + image_url?: unknown; + input_audio?: unknown; + file?: unknown; +} + +export interface ChatToolCall { + id?: string; + type?: string; + function?: { name?: string; arguments?: string }; +} + +/** Provider-reported usage breakdown. Source: Bifrost + * `schemas.BifrostLLMUsage`. The `prompt_tokens_details` sub- + * object is where the cache split lives — Anthropic populates + * `cached_write_tokens` (prompt cache writes) and + * `cached_read_tokens`; OpenAI uses `cached_read_tokens` only. */ +export interface TokenUsage { + prompt_tokens?: number; + completion_tokens?: number; + total_tokens?: number; + prompt_tokens_details?: { + text_tokens?: number; + audio_tokens?: number; + image_tokens?: number; + cached_read_tokens?: number; + cached_write_tokens?: number; + cached_write_token_details?: { + cached_write_tokens_5m?: number; + cached_write_tokens_1h?: number; + }; + }; + completion_tokens_details?: { + reasoning_tokens?: number; + accepted_prediction_tokens?: number; + }; + cost?: unknown; +} + +/** Semantic cache verdict. Source: `schemas.BifrostCacheDebug`. + * Distinct from prompt-cache token splits (those live in + * TokenUsage above). Present only when a semantic cache plugin + * is configured. */ +export interface CacheDebug { + cache_hit: boolean; + cache_id?: string; + hit_type?: string; + requested_provider?: string; + requested_model?: string; + provider_used?: string; + model_used?: string; + input_tokens?: number; + threshold?: number; + similarity?: number; +} diff --git a/gateway/internal/adminapi/ui/src/api/queries.ts b/gateway/internal/adminapi/ui/src/api/queries.ts index cd87946f0..42e77361f 100644 --- a/gateway/internal/adminapi/ui/src/api/queries.ts +++ b/gateway/internal/adminapi/ui/src/api/queries.ts @@ -25,13 +25,15 @@ import type { SpendByAgentResponse, SpendByAgentUserResponse, SpendByUserResponse, - TrustOrg, - TrustStatus, UserDetailResponse, - Window, +} from "./types"; +import type { Bucket, Dimension, -} from "./types"; + TrustOrg, + TrustStatus, + Window, +} from "./manual"; // ─── /me ───────────────────────────────────────────────────────────── // Fires once at boot, plus on tab refocus (Tanstack default). Cheap diff --git a/gateway/internal/adminapi/ui/src/api/types.ts b/gateway/internal/adminapi/ui/src/api/types.ts index f7bd8e94a..5997805ec 100644 --- a/gateway/internal/adminapi/ui/src/api/types.ts +++ b/gateway/internal/adminapi/ui/src/api/types.ts @@ -1,216 +1,394 @@ // Code generated by tygo. DO NOT EDIT. // -// To regenerate from the Go source of truth: -// make tygo (or: tygo generate from gateway/) -// // Source: github.com/stakwork/stakgraph/gateway/internal/adminapi -// -// Phase 8 hand-maintains this file as a stop-gap until the tygo -// codegen runs in CI. The shapes below MUST stay byte-equivalent to -// the Go structs they mirror (LoginResponse, MeResponse, -// SpendByAgentResponse, SpendByUserResponse, HistogramCostResponse, -// RunDetailResponse and friends). Any drift fails the matching -// adminapi Go tests on next run. - +// Regenerate with `make tygo`; CI enforces via `make tygo-check`. +// Hand-maintained companions (unions, trust mirrors) live in +// manual.ts. /* eslint-disable */ +////////// +// source: budgets.go + +/** + * AgentBudgetResponse is the wire shape for /_plugin/agents/:name/budget. + * Phase-8.5 read-only view — phase 9 grows the matching PUT/DELETE + * mutations on `/_plugin/config/agent_budgets/:name`. + * `CapUSD` and `Window` come from the plugin config (YAML / Redis + * override merged); `SpentUSD` comes from the live phase-6 Redis + * accumulator `bifrost:cost:agent::`. Either source + * being absent is fine — the response surfaces nil so the UI can + * render "no budget" or "no spend yet" rather than misleading zeros. + */ +export interface AgentBudgetResponse { + agent_name: string; + /** + * Cap is the configured maximum spend for the current window. + * Null when the agent has no entry in `agent_budgets`. + */ + cap_usd?: number /* float64 */; + /** + * Window is the Bifrost duration string the cap applies to + * (e.g. "1d", "1h"). Empty when no cap is set. + */ + window: string; + /** + * PeriodStart / PeriodEnd bracket the active bucket. Both are + * RFC3339 strings, UTC. Phase 8 only renders the start; future + * "resets in: 23h" badges can read end. + */ + period_start?: string; + period_end?: string; + /** + * SpentUSD is the running total against the cap for the + * current bucket. 0 (not null) when the bucket key simply + * doesn't exist yet — that's a real-world "fresh window, no + * calls yet" state, not a missing-data state. + */ + spent_usd: number /* float64 */; + /** + * RemainingUSD = max(cap - spent, 0). Null when cap is null. + */ + remaining_usd?: number /* float64 */; + /** + * Ratio = spent / cap, clamped to [0, 1+] (we don't clamp the + * upper bound so a runaway "150%" reads honestly). Null when + * cap is null. + */ + ratio?: number /* float64 */; +} + +////////// +// source: catalog.go + +/** + * AgentCatalogResponse is the merged view across all sources for one + * agent — what the agent is _made of_ (prompts/tools/skills), as + * opposed to the budget view's what it's _allowed to spend_. + */ +export interface AgentCatalogResponse { + name: string; + display_name?: string; + description?: string; + default_model?: string; + sources: string[]; + prompts: CatalogPrompt[]; + tools: CatalogTool[]; + skills: CatalogSkill[]; +} +/** + * CatalogAgentSummary is one row of the catalog list — enough to merge + * the registry into the spend-derived /agents table without pulling + * every agent's full prompt/tool/skill bodies. Counts let the list + * render "📄 2 · 🔧 5 · ✦ 1" badges cheaply. + */ +export interface CatalogAgentSummary { + name: string; + display_name?: string; + description?: string; + default_model?: string; + sources: string[]; + prompts: number /* int */; + tools: number /* int */; + skills: number /* int */; + updated_at: string; +} +/** + * CatalogListResponse is the whole registry — every HiveAgent node, + * traffic or not. The UI unions this with spend-by-agent so seeded + * agents that have never been invoked still appear. + */ +export interface CatalogListResponse { + agents: CatalogAgentSummary[]; +} +/** + * CatalogPrompt is one prompt linked to an agent. name/body come from + * the shared `:Prompt` node the agent links to; source/updated_at come + * from the HAS_PROMPT relationship (which system wired it, and when). + */ +export interface CatalogPrompt { + name: string; + body: string; + /** + * Role is the prompt's slot for this agent — "SYSTEM" or "USER" + * (the main/task prompt). Stored on the HAS_PROMPT relationship; + * empty when the wiring source didn't classify it. + */ + role?: string; + source: string; + updated_at: string; +} +export interface CatalogTool { + name: string; + description: string; + schema?: any /* json.RawMessage */; + source: string; + version?: string; + /** + * Enabled is the per-swarm operator toggle, mirroring skills: + * seeded enabled, flipped in the dashboard, preserved across + * Hive re-seeds. Legacy nodes with no value read back as true. + */ + enabled: boolean; + updated_at: string; +} +export interface CatalogSkill { + name: string; + description: string; + source: string; + version?: string; + /** + * Enabled is the per-swarm operator toggle. Skills seed enabled by + * default; an operator flips this in the dashboard and the gateway + * preserves it across Hive re-seeds (a push only refreshes the + * palette + metadata, never the toggle). Legacy nodes with no + * stored value read back as true (coalesce on the read query). + */ + enabled: boolean; + updated_at: string; +} + +////////// +// source: evals.go + +/** + * EvalSetSummary is one row of an agent's eval-set list. + */ +export interface EvalSetSummary { + ref_id: string; + name?: string; + description?: string; + requirements: number /* int */; +} +/** + * AgentEvalsResponse is the agent-detail Evals tab payload — the sets + * linked to this agent via HAS_EVAL_SET. + */ +export interface AgentEvalsResponse { + agent: string; + sets: EvalSetSummary[]; +} +/** + * EvalTriggerSummary is one captured trigger under a requirement, plus + * the outcome of its most-recent run (nil-ish fields when never run). + */ +export interface EvalTriggerSummary { + ref_id: string; + agent?: string; + source?: string; + environment?: string; + change_type?: string; + last_result?: string; // "pass" / "fail" / "" + last_score?: number /* float64 */; + last_notes?: string; + last_attempt?: number /* int */; +} +/** + * EvalRequirementDetail is a requirement with its triggers. + */ +export interface EvalRequirementDetail { + ref_id: string; + name?: string; + description?: string; + prompt_snippet?: string; + order: number /* int */; + triggers: EvalTriggerSummary[]; +} +/** + * EvalSetDetailResponse is the expanded view of one set. + */ +export interface EvalSetDetailResponse { + ref_id: string; + name?: string; + description?: string; + requirements: EvalRequirementDetail[]; +} +/** + * EvalRefResponse is the create/link acknowledgement. Exported so + * tygo emits the TS binding the SPA's eval mutations decode. + */ +export interface EvalRefResponse { + ref_id: string; + linked?: boolean; +} + +////////// +// source: hivecallback.go + + +////////// +// source: login.go + +/** + * LoginResponse is the JSON body returned by `POST /_plugin/login` + * on success. The SPA stashes the username so the topbar can render + * it without an extra `/me` round-trip. + * Exported because tygo emits TS bindings from this declaration + * (see gateway/tygo.yaml); rename in lockstep with the frontend. + */ export interface LoginResponse { user: string; } - +/** + * MeResponse is `GET /_plugin/me` — the SPA's boot probe to decide + * "am I authenticated?". Unix timestamps (seconds) keep the wire + * format compact and language-agnostic; the frontend converts to + * Date once. + */ export interface MeResponse { user: string; - iat: number; - last_seen: number; + iat: number /* int64 */; + last_seen: number /* int64 */; } +////////// +// source: logstore_client.go + + +////////// +// source: observability.go + +/** + * AgentSpend is one row of /_plugin/spend/by-agent. + */ export interface AgentSpend { agent_name: string; - total_cost: number; - total_tokens: number; - request_count: number; + total_cost: number /* float64 */; + total_tokens: number /* int64 */; + request_count: number /* int64 */; } - +/** + * SpendByAgentResponse is the envelope for /_plugin/spend/by-agent. + */ export interface SpendByAgentResponse { window: string; results: AgentSpend[]; } - +/** + * UserSpend is one row of /_plugin/spend/by-user. + */ export interface UserSpend { user_id: string; user_name: string; - total_cost: number; - total_tokens: number; - request_count: number; + total_cost: number /* float64 */; + total_tokens: number /* int64 */; + request_count: number /* int64 */; } - +/** + * SpendByUserResponse is the envelope for /_plugin/spend/by-user. + */ export interface SpendByUserResponse { window: string; results: UserSpend[]; } - -// Per-provider slice inside an AgentUserSpend row. Lets the canvas -// drive gateway→provider edge widths from real spend; same Bifrost -// call that computes the (agent × user) rollup also fills these, -// so no extra round-trips. +/** + * ProviderSpend is the per-provider slice carried inside an + * AgentUserSpend row. Lets the canvas drive gateway→provider edge + * widths (and the provider drawer) from real spend without a second + * round-trip — the data is already on every Bifrost log row, so + * surfacing it costs only a second bucket in the same pass. + */ export interface ProviderSpend { provider: string; - total_cost: number; - request_count: number; -} - -// One row of /_plugin/spend/by-agent-user — the (agent × user) fan-out -// the Canvas page uses to render one box per pairing without N round -// trips. Rows missing either dim are excluded server-side. -// -// `providers` sums to `total_cost` / `request_count` and is sorted by -// cost desc, with provider name as a stable tiebreaker. + total_cost: number /* float64 */; + request_count: number /* int64 */; +} +/** + * AgentUserSpend is one row of /_plugin/spend/by-agent-user — the + * fan-out crossing of (agent-name × user-id) so the flowchart UI can + * render one box per pairing in a single round-trip. Rows missing + * either dim are excluded (same policy as by-agent / by-user). + * `Providers` is the breakdown across providers for this pairing. + * Sums of `Providers[*].TotalCost` and `Providers[*].RequestCount` + * equal `TotalCost` and `RequestCount` — the breakdown is additive + * to the top-line totals, not a replacement. + */ export interface AgentUserSpend { agent_name: string; user_id: string; user_name: string; - total_cost: number; - total_tokens: number; - request_count: number; + total_cost: number /* float64 */; + total_tokens: number /* int64 */; + request_count: number /* int64 */; providers: ProviderSpend[]; } - +/** + * SpendByAgentUserResponse is the envelope for + * /_plugin/spend/by-agent-user. + */ export interface SpendByAgentUserResponse { window: string; results: AgentUserSpend[]; } - +/** + * HistogramPoint is one (timestamp, cost) datum in a per-dimension + * series. Timestamp is the bucket's start time in RFC3339. + */ export interface HistogramPoint { ts: string; - cost: number; + cost: number /* float64 */; } - +/** + * HistogramSeries is one line on a stacked-area chart. + */ export interface HistogramSeries { dimension_value: string; points: HistogramPoint[]; } - +/** + * HistogramCostResponse is the envelope for + * /_plugin/histogram/cost. + */ export interface HistogramCostResponse { - bucket_size_seconds: number; + bucket_size_seconds: number /* int64 */; dimension: string; series: HistogramSeries[]; } - +/** + * RunLogEntry is one row inside a RunDetailResponse's `logs` field. + * A trimmed view of Bifrost's Log — phase 8 only renders the columns + * the call-log table actually shows. + */ export interface RunLogEntry { id: string; timestamp: string; provider: string; model: string; status: string; - cost: number; - latency: number; - metadata: Record; + cost: number /* float64 */; + latency: number /* float64 */; + metadata: { [key: string]: string}; } - +/** + * RunStats is the aggregate-card summary at the top of /runs/:id. + */ export interface RunStats { - total_requests: number; - total_cost: number; - total_tokens: number; + total_requests: number /* int64 */; + total_cost: number /* float64 */; + total_tokens: number /* int64 */; } - +/** + * RunDetailResponse is the envelope for /_plugin/runs/:run_id. + */ export interface RunDetailResponse { run_id: string; logs: RunLogEntry[]; stats: RunStats; } - -// One message in a chat-style input_history / output_message. -// Mirrors Bifrost's `schemas.ChatMessage` to the depth the drawer -// renders: role, content (string OR an array of content blocks), -// optional tool-call list (assistant) and tool_call_id (tool reply). -// Everything is optional because providers vary in which fields -// they populate, and the drawer falls back to JSON for anything it -// doesn't recognize. -export interface ChatMessage { - role?: string; - name?: string; - /** OpenAI/Anthropic-style: either a plain string or an array of - * typed content blocks. */ - content?: string | ChatContentBlock[] | null; - /** Tool messages: which prior tool_call this is the result for. */ - tool_call_id?: string; - /** Assistant tool calls. */ - tool_calls?: ChatToolCall[]; - /** Anthropic / OpenAI reasoning summaries. */ - reasoning?: string; - refusal?: string; -} - -export interface ChatContentBlock { - type: string; - text?: string; - refusal?: string; - /** Anthropic-style cache marker. When present on a block the - * provider charged this block as a cache write (or read on a - * subsequent call). */ - cache_control?: { type?: string } | null; - cachePoint?: { type?: string } | null; - image_url?: unknown; - input_audio?: unknown; - file?: unknown; -} - -export interface ChatToolCall { - id?: string; - type?: string; - function?: { name?: string; arguments?: string }; -} - -/** Provider-reported usage breakdown. Source: Bifrost - * `schemas.BifrostLLMUsage`. The `prompt_tokens_details` sub- - * object is where the cache split lives — Anthropic populates - * `cached_write_tokens` (prompt cache writes) and - * `cached_read_tokens`; OpenAI uses `cached_read_tokens` only. */ -export interface TokenUsage { - prompt_tokens?: number; - completion_tokens?: number; - total_tokens?: number; - prompt_tokens_details?: { - text_tokens?: number; - audio_tokens?: number; - image_tokens?: number; - cached_read_tokens?: number; - cached_write_tokens?: number; - cached_write_token_details?: { - cached_write_tokens_5m?: number; - cached_write_tokens_1h?: number; - }; - }; - completion_tokens_details?: { - reasoning_tokens?: number; - accepted_prediction_tokens?: number; - }; - cost?: unknown; -} - -/** Semantic cache verdict. Source: `schemas.BifrostCacheDebug`. - * Distinct from prompt-cache token splits (those live in - * TokenUsage above). Present only when a semantic cache plugin - * is configured. */ -export interface CacheDebug { - cache_hit: boolean; - cache_id?: string; - hit_type?: string; - requested_provider?: string; - requested_model?: string; - provider_used?: string; - model_used?: string; - input_tokens?: number; - threshold?: number; - similarity?: number; -} - -// CallDetailResponse — /_plugin/runs/:run_id/calls/:call_id. -// -// The full body of one LLM call: same fields as a list row, plus -// the heavy JSON columns Bifrost strips from /api/logs. Chat-shaped -// fields (input_history / output_message) are typed so the drawer -// can render bubble UIs; everything else stays opaque. +/** + * CallDetailResponse is the envelope for + * /_plugin/runs/:run_id/calls/:call_id — the full request/response + * content for a single LLM call, fetched on-demand when the + * operator clicks a row in the RunDetail call log. + * Run-scoping rationale: we verify that the fetched log's + * metadata.run-id matches the URL's run_id before returning, so a + * caller can't enumerate other workspaces' logs by guessing IDs. + * Bifrost's /api/logs/{id} doesn't do this check itself (it just + * looks up by primary key), so the plugin enforces it. + * Body fields are pass-through json.RawMessage from Bifrost — the + * SPA pretty-prints them; the plugin doesn't introspect them. This + * keeps the schema coupling minimal: new fields upstream surface in + * the UI without code changes here. + */ export interface CallDetailResponse { id: string; run_id: string; @@ -218,251 +396,154 @@ export interface CallDetailResponse { provider: string; model: string; status: string; - cost: number; - latency: number; + cost: number /* float64 */; + latency: number /* float64 */; customer_id: string; - metadata: Record; - + metadata: { [key: string]: string}; + /** + * Per-request descriptors stamped by Bifrost. `stop_reason` + * tells the operator why the model stopped (stop, length, + * content_filter, tool_calls, refusal). `stream` flags + * streaming responses (which lack a single output_message and + * surface their content via Bifrost's stream chunk replay). + * Retries / fallback_index are zero on the happy path; non-zero + * means Bifrost had to retry the call or fall back to a + * different provider, which is useful provenance. + */ stop_reason?: string; stream: boolean; - number_of_retries: number; - fallback_index: number; - - token_usage?: TokenUsage; - cache_debug?: CacheDebug; - - input_history?: ChatMessage[]; - output_message?: ChatMessage; - params?: unknown; - tools?: unknown; - error_details?: unknown; + number_of_retries: number /* int */; + fallback_index: number /* int */; + /** + * TokenUsage is the provider-reported usage breakdown + * (BifrostLLMUsage). Includes prompt/completion/total totals + * plus cached read/write splits (Anthropic prompt-cache, + * OpenAI cached_tokens), audio/image token counts, and a + * per-call cost record. Pass-through JSON — the SPA introspects + * it. Bifrost's row-level prompt_tokens / completion_tokens / + * total_tokens columns are denormalized helpers tagged + * `json:"-"`, so this is the only place token data is on the + * wire. + */ + token_usage?: any /* json.RawMessage */; + /** + * CacheDebug carries Bifrost's *semantic* cache verdict for + * this call (hit/miss + similarity score). Distinct from + * prompt-cache tokens, which live in TokenUsage above. Absent + * when no semantic cache is configured for this swarm. + */ + cache_debug?: any /* json.RawMessage */; + /** + * All optional; missing on failures, realtime turns, or rows + * recorded before a given column existed. + */ + input_history?: any /* json.RawMessage */; + output_message?: any /* json.RawMessage */; + params?: any /* json.RawMessage */; + tools?: any /* json.RawMessage */; + error_details?: any /* json.RawMessage */; raw_request?: string; raw_response?: string; content_summary?: string; } -// One row of UserDetailResponse.AgentsUsed — which agents the user -// invoked in the window, and how much each cost. -export interface UserAgentUsage { - agent_name: string; - total_cost: number; - request_count: number; - last_seen?: string; -} +////////// +// source: ratelimit.go -// One row of UserDetailResponse.RecentRuns — links into RunDetail. -export interface UserRunSummary { - run_id: string; - agent_name: string; - total_cost: number; - request_count: number; - first_seen?: string; - last_seen?: string; -} -// /_plugin/users/:id response. -export interface UserDetailResponse { - user_id: string; - window: string; - total_cost: number; - request_count: number; - agents_used: UserAgentUsage[]; - recent_runs: UserRunSummary[]; - first_seen?: string; - last_seen?: string; -} +////////// +// source: server.go +/* +Package adminapi is the gateway plugin's in-process HTTP server, +hosting the `/_plugin/*` route namespace. -// Trust-registry Org entry — mirrors gateway/internal/trust.Org. -// Surfaced on the dashboard's Provenance card so an operator can -// see which org's signature authorized a run, plus the pubkey / -// issuer URL the plugin would verify against. -export interface TrustOrg { - org_id: string; - pubkey: string; - issuer_url: string; - revocation_poll_seconds: number; - grace_pubkeys?: string[]; - grace_until?: string; -} +The wrapper binary (gateway/wrapper) reverse-proxies traffic on +`/_plugin/*` to this server (loopback only). Why a separate server +instead of routes registered through Bifrost's router: Bifrost +plugins (.so) cannot register arbitrary HTTP routes through +Bifrost's own router, and we also want routes that aren't behind +Bifrost's AuthMiddleware so Hive can bootstrap on a fresh swarm. -// Trust-registry status — mirrors gateway/internal/trust.StatusResponse. -// The Provenance card on RunDetail uses `realm_id` to show the -// swarm's self-identity ("this run was processed by swarm w1"), -// since phase 11 dropped the per-row realm-id metadata column. -export interface TrustStatus { - claimed: boolean; - org_count: number; - orgs: string[]; - seed_source: "" | "env" | "api"; - last_modified: string; - /** Set on multi-swarm deployments; absent / empty on single-swarm. */ - realm_id?: string; -} +Lifecycle +--------- + - Start() is called from the plugin's Init(). + - Stop() is called from the plugin's Cleanup() with a small grace + period. -// Per-agent budget (phase-8.5). Cap and the derived fields can be -// null when no budget is configured for the agent — the UI renders -// "no budget" rather than "$0". -export interface AgentBudgetResponse { - agent_name: string; - cap_usd: number | null; - window: string; - period_start?: string; - period_end?: string; - spent_usd: number; - remaining_usd: number | null; - ratio: number | null; -} +One server per process. Re-calling Start() is a no-op. +*/ -// ─── agent catalog ────────────────────────────────────────────────── -// Mirrors gateway/internal/adminapi/catalog.go. The catalog answers -// "what is this agent _made of_?" (prompts/tools/skills), as opposed to -// the budget view's "what is it _allowed to spend_?". Sourced from the -// neo4j Hive* catalog subgraph via GET /_plugin/agents/:name/catalog. -// A prompt linked to an agent. name/body come from the shared `:Prompt` -// node the agent links to (authored by the Stakwork prompt workflow); -// source/updated_at come from the HAS_PROMPT relationship. -export interface CatalogPrompt { - name: string; - body: string; - /** Prompt slot for this agent: "SYSTEM" or "USER" (the main/task - * prompt). Absent when the wiring source didn't classify it. */ - role?: string; - source: string; - updated_at: string; -} - -export interface CatalogTool { - name: string; - description: string; - /** JSON parameter schema, passed through opaque — the UI renders it - * as pretty-printed JSON. Absent when the source didn't supply one. */ - schema?: unknown; - source: string; - version?: string; - /** Per-swarm operator toggle. Seeded enabled; preserved across - * Hive re-seeds. Flip via PATCH /_plugin/agents/:name/tools. */ - enabled: boolean; - updated_at: string; -} - -export interface CatalogSkill { - name: string; - description: string; - source: string; - version?: string; - /** Per-swarm operator toggle. Seeded enabled; preserved across - * Hive re-seeds. Flip via PATCH /_plugin/agents/:name/skills. */ - enabled: boolean; - updated_at: string; -} - -// One row of the catalog list (GET /_plugin/agents/catalog) — identity -// + child counts, enough to merge the registry into the spend-derived -// /agents table without pulling every prompt/tool/skill body. -export interface CatalogAgentSummary { - name: string; - display_name?: string; - description?: string; - default_model?: string; - sources: string[]; - prompts: number; - tools: number; - skills: number; - updated_at: string; -} - -// The whole registry — every catalog agent, traffic or not. -export interface CatalogListResponse { - agents: CatalogAgentSummary[]; -} +////////// +// source: session.go -// Merged catalog view across all contributing sources for one agent. -export interface AgentCatalogResponse { - name: string; - display_name?: string; - description?: string; - /** Default LLM used for this agent (model shortcut or full id). */ - default_model?: string; - sources: string[]; - prompts: CatalogPrompt[]; - tools: CatalogTool[]; - skills: CatalogSkill[]; -} -// ─── Evals (agent-detail tab) ─────────────────────────────────────── -// Mirror of the Go structs in internal/adminapi/evals.go. Eval sets are -// Jarvis-authored nodes surfaced under an agent via HAS_EVAL_SET; the -// gateway reads them from neo4j and delegates writes/runs to Hive. +////////// +// source: tickets.go -export interface EvalSetSummary { - ref_id: string; - name?: string; - description?: string; - requirements: number; +/** + * TicketResponse is the JSON body returned by POST /_plugin/auth/ticket. + * Hive embeds the ticket in the iframe src as `?ticket=`. + */ +export interface TicketResponse { + ticket: string; + expires_in: number /* int */; // seconds; mirrors ticketTTL } -export interface AgentEvalsResponse { - agent: string; - sets: EvalSetSummary[]; -} +////////// +// source: trust.go -export interface EvalTriggerSummary { - ref_id: string; - agent?: string; - source?: string; - environment?: string; - change_type?: string; - last_result?: string; // "pass" | "fail" | "" - last_score?: number; - last_notes?: string; - last_attempt?: number; -} -export interface EvalRequirementDetail { - ref_id: string; - name?: string; - description?: string; - prompt_snippet?: string; - order: number; - triggers: EvalTriggerSummary[]; -} +////////// +// source: users.go -export interface EvalSetDetailResponse { - ref_id: string; - name?: string; - description?: string; - requirements: EvalRequirementDetail[]; +/** + * UserAgentUsage is one row in `UserDetailResponse.AgentsUsed` — + * a per-agent rollup scoped to this user's traffic in the window. + */ +export interface UserAgentUsage { + agent_name: string; + total_cost: number /* float64 */; + request_count: number /* int64 */; + last_seen?: string; } - -// Acknowledgement for create/link (ref_id of the set/requirement). -export interface EvalRefResponse { - ref_id: string; - linked?: boolean; +/** + * UserRunSummary is one row in `UserDetailResponse.RecentRuns`. + * Phase 8 keeps this lightweight — the dashboard renders cost + + * agent + first/last-seen and links into RunDetail for the full + * call log. + */ +export interface UserRunSummary { + run_id: string; + agent_name: string; + total_cost: number /* float64 */; + request_count: number /* int64 */; + first_seen?: string; + last_seen?: string; } - -// Phase-7 error envelope (returned on 4xx/5xx). -export interface ApiError { - error: { - code: string; - message: string; - }; +/** + * UserDetailResponse is the wire shape for /_plugin/users/:id. + * Composition + * ----------- + * Everything is derived from a single paged scan of `logs.db` + * filtered by `metadata.user-id = ` (with a fallback to the + * indexed `customer_id` column on Bifrost's logs table, which + * equals the user-id per the v2 invariant). One round-trip to + * Bifrost, one in-memory aggregation pass; the dashboard renders + * the result without further fan-out. + * Once phase 6's PostLLMHook fills Redis cost accumulators, the + * `total_cost` field could be sourced from the Redis hash instead + * of summing logs — same number, less work. Phase 8 doesn't take + * that shortcut yet because the Redis bucket is per-(agent, day) + * not per-user. + */ +export interface UserDetailResponse { + user_id: string; + window: string; + total_cost: number /* float64 */; + request_count: number /* int64 */; + agents_used: UserAgentUsage[]; + recent_runs: UserRunSummary[]; + first_seen?: string; + last_seen?: string; } - -// Window options the SPA exposes to the operator. Kept in lockstep -// with the Go-side validation in observability.go > parseWindow. -export type Window = "1h" | "6h" | "24h" | "7d" | "30d"; - -// Bucket options for the histogram endpoints — same source-of-truth -// note as Window. -export type Bucket = "1m" | "5m" | "10m" | "1h" | "6h" | "1d"; - -// Dimension values the histogram endpoint accepts. Phase 11 removed -// `realm-id` — every row in a swarm's logs.db is implicitly for -// that swarm's realm, and the realm is surfaced on the trust-status -// card instead of as a per-row column. -export type Dimension = - | "agent-name" - | "run-id" - | "session-id" - | "user-id"; diff --git a/gateway/internal/adminapi/ui/src/api/window.ts b/gateway/internal/adminapi/ui/src/api/window.ts index 57e45d414..cff5b3979 100644 --- a/gateway/internal/adminapi/ui/src/api/window.ts +++ b/gateway/internal/adminapi/ui/src/api/window.ts @@ -2,7 +2,7 @@ // because the backend's parseWindow is the authority on this set; if // it grows a new option, this mapping needs to grow with it. -import type { Window } from "./types"; +import type { Window } from "./manual"; export function windowToSeconds(w: Window): number { switch (w) { diff --git a/gateway/internal/adminapi/ui/src/components/controls/WindowPicker.tsx b/gateway/internal/adminapi/ui/src/components/controls/WindowPicker.tsx index 7f0d26435..1d5aca09f 100644 --- a/gateway/internal/adminapi/ui/src/components/controls/WindowPicker.tsx +++ b/gateway/internal/adminapi/ui/src/components/controls/WindowPicker.tsx @@ -3,7 +3,7 @@ // set stays consistent (and stays in lockstep with the Go-side // parseWindow whitelist). -import type { Window } from "../../api/types"; +import type { Window } from "../../api/manual"; const OPTIONS: Window[] = ["1h", "24h", "7d", "30d"]; diff --git a/gateway/internal/adminapi/ui/src/pages/AgentDetail.tsx b/gateway/internal/adminapi/ui/src/pages/AgentDetail.tsx index 20120ed98..1d6817927 100644 --- a/gateway/internal/adminapi/ui/src/pages/AgentDetail.tsx +++ b/gateway/internal/adminapi/ui/src/pages/AgentDetail.tsx @@ -31,7 +31,8 @@ import { useToggleSkill, useToggleTool, } from "../api/queries"; -import type { HistogramCostResponse, Window } from "../api/types"; +import type { HistogramCostResponse } from "../api/types"; +import type { Window } from "../api/manual"; import { windowToSeconds } from "../api/window"; import { EvalsView } from "./EvalsView"; diff --git a/gateway/internal/adminapi/ui/src/pages/Agents.tsx b/gateway/internal/adminapi/ui/src/pages/Agents.tsx index cb0c4e044..834365041 100644 --- a/gateway/internal/adminapi/ui/src/pages/Agents.tsx +++ b/gateway/internal/adminapi/ui/src/pages/Agents.tsx @@ -15,7 +15,7 @@ import { useAgentCatalogList, useSpendByAgent, } from "../api/queries"; -import type { Window } from "../api/types"; +import type { Window } from "../api/manual"; // A merged agent row: spend metrics (zeroed when registry-only) plus // catalog identity/counts (absent when traffic-only). diff --git a/gateway/internal/adminapi/ui/src/pages/Canvas.tsx b/gateway/internal/adminapi/ui/src/pages/Canvas.tsx index e40b4d839..99adb7c48 100644 --- a/gateway/internal/adminapi/ui/src/pages/Canvas.tsx +++ b/gateway/internal/adminapi/ui/src/pages/Canvas.tsx @@ -35,7 +35,8 @@ import { useTrustOrg, useTrustStatus, } from "../api/queries"; -import type { AgentUserSpend, TrustOrg, Window } from "../api/types"; +import type { AgentUserSpend } from "../api/types"; +import type { TrustOrg, Window } from "../api/manual"; import { WindowPicker } from "../components/controls/WindowPicker"; import { canvasTheme, PROVIDER_DISPLAY, providerIcon } from "./canvasTheme"; diff --git a/gateway/internal/adminapi/ui/src/pages/Dashboard.tsx b/gateway/internal/adminapi/ui/src/pages/Dashboard.tsx index 94be8c10c..6e970a5bb 100644 --- a/gateway/internal/adminapi/ui/src/pages/Dashboard.tsx +++ b/gateway/internal/adminapi/ui/src/pages/Dashboard.tsx @@ -17,7 +17,8 @@ import { useSpendByAgent, useSpendByUser, } from "../api/queries"; -import type { AgentSpend, UserSpend, Window } from "../api/types"; +import type { AgentSpend, UserSpend } from "../api/types"; +import type { Window } from "../api/manual"; import { windowToSeconds } from "../api/window"; // LLM call costs can be fractions of a cent; clamping to 2 decimals diff --git a/gateway/internal/adminapi/ui/src/pages/People.tsx b/gateway/internal/adminapi/ui/src/pages/People.tsx index 091cdb976..7173254c0 100644 --- a/gateway/internal/adminapi/ui/src/pages/People.tsx +++ b/gateway/internal/adminapi/ui/src/pages/People.tsx @@ -14,7 +14,8 @@ import { WindowPicker } from "../components/controls/WindowPicker"; import { UserIcon } from "../components/icons"; import { getErrorMessage } from "../api/client"; import { useSpendByUser } from "../api/queries"; -import type { UserSpend, Window } from "../api/types"; +import type { UserSpend } from "../api/types"; +import type { Window } from "../api/manual"; const fmtUSD = (v: number) => { if (v === 0) return "$0.00"; diff --git a/gateway/internal/adminapi/ui/src/pages/RunDetail.tsx b/gateway/internal/adminapi/ui/src/pages/RunDetail.tsx index 4a4a92853..4f9e7820a 100644 --- a/gateway/internal/adminapi/ui/src/pages/RunDetail.tsx +++ b/gateway/internal/adminapi/ui/src/pages/RunDetail.tsx @@ -12,16 +12,15 @@ import { DataTable } from "../components/tables/DataTable"; import { BotIcon, UserIcon } from "../components/icons"; import { getErrorMessage } from "../api/client"; import { useRunCall, useRunDetail, useTrustOrg, useTrustStatus } from "../api/queries"; +import type { CallDetailResponse, RunLogEntry } from "../api/types"; import type { CacheDebug, - CallDetailResponse, ChatContentBlock, ChatMessage, ChatToolCall, - RunLogEntry, TokenUsage, TrustOrg, -} from "../api/types"; +} from "../api/manual"; interface Props { runID: string; diff --git a/gateway/internal/adminapi/ui/src/pages/UserDetail.tsx b/gateway/internal/adminapi/ui/src/pages/UserDetail.tsx index d271ebf68..f026177a8 100644 --- a/gateway/internal/adminapi/ui/src/pages/UserDetail.tsx +++ b/gateway/internal/adminapi/ui/src/pages/UserDetail.tsx @@ -22,7 +22,8 @@ import { WindowPicker } from "../components/controls/WindowPicker"; import { BotIcon, UserIcon } from "../components/icons"; import { getErrorMessage } from "../api/client"; import { useHistogramCost, useUserDetail } from "../api/queries"; -import type { UserAgentUsage, UserRunSummary, Window } from "../api/types"; +import type { UserAgentUsage, UserRunSummary } from "../api/types"; +import type { Window } from "../api/manual"; import { windowToSeconds } from "../api/window"; interface Props { diff --git a/gateway/internal/adminapi/ui/tsconfig.tsbuildinfo b/gateway/internal/adminapi/ui/tsconfig.tsbuildinfo index be7c070c3..d42130d52 100644 --- a/gateway/internal/adminapi/ui/tsconfig.tsbuildinfo +++ b/gateway/internal/adminapi/ui/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/app.tsx","./src/main.tsx","./src/api/client.ts","./src/api/queries.ts","./src/api/types.ts","./src/api/window.ts","./src/components/emptystate.tsx","./src/components/errorboundary.tsx","./src/components/icons.tsx","./src/components/charts/costhistogram.tsx","./src/components/charts/uplotchart.tsx","./src/components/controls/windowpicker.tsx","./src/components/layout/shell.tsx","./src/components/layout/sidebar.tsx","./src/components/layout/topbar.tsx","./src/components/tables/datatable.tsx","./src/pages/agentdetail.tsx","./src/pages/agents.tsx","./src/pages/canvas.tsx","./src/pages/dashboard.tsx","./src/pages/login.tsx","./src/pages/notfound.tsx","./src/pages/people.tsx","./src/pages/rundetail.tsx","./src/pages/userdetail.tsx","./src/pages/canvastheme.ts"],"version":"5.6.3"} \ No newline at end of file +{"root":["./src/app.tsx","./src/main.tsx","./src/api/client.ts","./src/api/manual.ts","./src/api/queries.ts","./src/api/types.ts","./src/api/window.ts","./src/components/emptystate.tsx","./src/components/errorboundary.tsx","./src/components/icons.tsx","./src/components/charts/costhistogram.tsx","./src/components/charts/uplotchart.tsx","./src/components/controls/windowpicker.tsx","./src/components/layout/shell.tsx","./src/components/layout/sidebar.tsx","./src/components/layout/topbar.tsx","./src/components/tables/datatable.tsx","./src/pages/agentdetail.tsx","./src/pages/agents.tsx","./src/pages/canvas.tsx","./src/pages/dashboard.tsx","./src/pages/evalsview.tsx","./src/pages/login.tsx","./src/pages/notfound.tsx","./src/pages/people.tsx","./src/pages/rundetail.tsx","./src/pages/userdetail.tsx","./src/pages/canvastheme.ts"],"version":"5.6.3"} \ No newline at end of file diff --git a/gateway/tygo.yaml b/gateway/tygo.yaml index 6e104565c..7ac359e51 100644 --- a/gateway/tygo.yaml +++ b/gateway/tygo.yaml @@ -11,7 +11,9 @@ # Source-of-truth: every type the SPA decodes is declared in # gateway/internal/adminapi/ as an exported Go struct with `json:` # tags. tygo translates those into TS interfaces; no hand-editing -# of types.ts. +# of types.ts. Types with no adminapi Go struct (string-literal +# unions, the trust-package mirrors, Bifrost pass-through shapes) +# are hand-maintained in ui/src/api/manual.ts instead. # # Install tygo (once, per dev machine): # go install github.com/gzuidhof/tygo@latest @@ -19,13 +21,14 @@ packages: - path: "github.com/stakwork/stakgraph/gateway/internal/adminapi" output_path: "internal/adminapi/ui/src/api/types.ts" - # frontmatter is prepended to the generated file. We mark it - # generated so editors / reviewers know not to hand-edit. + # frontmatter is prepended after tygo's own "Code generated" + # banner, so editors / reviewers know not to hand-edit. frontmatter: | - // Code generated by tygo. DO NOT EDIT. // // Source: github.com/stakwork/stakgraph/gateway/internal/adminapi - // Regenerate with `make tygo`. + // Regenerate with `make tygo`; CI enforces via `make tygo-check`. + // Hand-maintained companions (unions, trust mirrors) live in + // manual.ts. /* eslint-disable */ type_mappings: "time.Time": "string"