@stigmer/react 3.12.3 → 3.12.4
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/agent/steps/IdentityStep.d.ts.map +1 -1
- package/agent/steps/IdentityStep.js +2 -1
- package/agent/steps/IdentityStep.js.map +1 -1
- package/agent-instance/AgentInstanceDetailPanel.d.ts.map +1 -1
- package/agent-instance/AgentInstanceDetailPanel.js +2 -1
- package/agent-instance/AgentInstanceDetailPanel.js.map +1 -1
- package/api-key/CreateApiKeyForm.d.ts.map +1 -1
- package/api-key/CreateApiKeyForm.js +2 -1
- package/api-key/CreateApiKeyForm.js.map +1 -1
- package/channel/ChannelConversationsDialog.d.ts.map +1 -1
- package/channel/ChannelConversationsDialog.js +2 -1
- package/channel/ChannelConversationsDialog.js.map +1 -1
- package/channel/ChannelTemplatesDialog.d.ts.map +1 -1
- package/channel/ChannelTemplatesDialog.js +2 -1
- package/channel/ChannelTemplatesDialog.js.map +1 -1
- package/channel/ConnectSlackDialog.d.ts.map +1 -1
- package/channel/ConnectSlackDialog.js +2 -1
- package/channel/ConnectSlackDialog.js.map +1 -1
- package/channel/connect/ServingAppSection.d.ts.map +1 -1
- package/channel/connect/ServingAppSection.js +2 -1
- package/channel/connect/ServingAppSection.js.map +1 -1
- package/channel-app/CreateChannelAppForm.d.ts.map +1 -1
- package/channel-app/CreateChannelAppForm.js +2 -1
- package/channel-app/CreateChannelAppForm.js.map +1 -1
- package/conversation/ConversationListPane.d.ts.map +1 -1
- package/conversation/ConversationListPane.js +2 -1
- package/conversation/ConversationListPane.js.map +1 -1
- package/conversation/ConversationTimelineView.d.ts.map +1 -1
- package/conversation/ConversationTimelineView.js +2 -1
- package/conversation/ConversationTimelineView.js.map +1 -1
- package/cursor-accounts/CursorAccountsConsole.d.ts.map +1 -1
- package/cursor-accounts/CursorAccountsConsole.js +2 -1
- package/cursor-accounts/CursorAccountsConsole.js.map +1 -1
- package/cursor-accounts/MemberKeysPanel.d.ts.map +1 -1
- package/cursor-accounts/MemberKeysPanel.js +2 -1
- package/cursor-accounts/MemberKeysPanel.js.map +1 -1
- package/dashboard/DashboardFailedRuns.d.ts.map +1 -1
- package/dashboard/DashboardFailedRuns.js +2 -1
- package/dashboard/DashboardFailedRuns.js.map +1 -1
- package/dependency-graph/DependencyGraph.d.ts.map +1 -1
- package/dependency-graph/DependencyGraph.js +2 -1
- package/dependency-graph/DependencyGraph.js.map +1 -1
- package/dependency-graph/DependencyTreeNode.d.ts.map +1 -1
- package/dependency-graph/DependencyTreeNode.js +2 -1
- package/dependency-graph/DependencyTreeNode.js.map +1 -1
- package/environment/EnvironmentPicker.d.ts.map +1 -1
- package/environment/EnvironmentPicker.js +2 -1
- package/environment/EnvironmentPicker.js.map +1 -1
- package/error/SecretFlowErrorGuide.d.ts.map +1 -1
- package/error/SecretFlowErrorGuide.js +2 -1
- package/error/SecretFlowErrorGuide.js.map +1 -1
- package/execution/ArtifactContentBody.d.ts.map +1 -1
- package/execution/ArtifactContentBody.js +2 -1
- package/execution/ArtifactContentBody.js.map +1 -1
- package/execution/ArtifactsWidget.d.ts.map +1 -1
- package/execution/ArtifactsWidget.js +2 -1
- package/execution/ArtifactsWidget.js.map +1 -1
- package/execution/FileChangeProgressBar.d.ts.map +1 -1
- package/execution/FileChangeProgressBar.js +2 -1
- package/execution/FileChangeProgressBar.js.map +1 -1
- package/execution/TodoList.d.ts.map +1 -1
- package/execution/TodoList.js +2 -1
- package/execution/TodoList.js.map +1 -1
- package/execution/WriteBacksWidget.d.ts.map +1 -1
- package/execution/WriteBacksWidget.js +2 -1
- package/execution/WriteBacksWidget.js.map +1 -1
- package/execution/tool-categories.d.ts +5 -22
- package/execution/tool-categories.d.ts.map +1 -1
- package/execution/tool-categories.js +8 -38
- package/execution/tool-categories.js.map +1 -1
- package/iam-policy/PeopleWithAccess.d.ts.map +1 -1
- package/iam-policy/PeopleWithAccess.js +2 -1
- package/iam-policy/PeopleWithAccess.js.map +1 -1
- package/iam-policy/PrincipalPicker.d.ts.map +1 -1
- package/iam-policy/PrincipalPicker.js +2 -1
- package/iam-policy/PrincipalPicker.js.map +1 -1
- package/iam-policy/RoleSelector.d.ts.map +1 -1
- package/iam-policy/RoleSelector.js +2 -1
- package/iam-policy/RoleSelector.js.map +1 -1
- package/identity-provider/CreateIdentityProviderForm.d.ts.map +1 -1
- package/identity-provider/CreateIdentityProviderForm.js +2 -1
- package/identity-provider/CreateIdentityProviderForm.js.map +1 -1
- package/identity-provider/IdentityProviderDetailPanel.d.ts.map +1 -1
- package/identity-provider/IdentityProviderDetailPanel.js +2 -1
- package/identity-provider/IdentityProviderDetailPanel.js.map +1 -1
- package/identity-provider/IdentityProviderWizard.d.ts.map +1 -1
- package/identity-provider/IdentityProviderWizard.js +2 -1
- package/identity-provider/IdentityProviderWizard.js.map +1 -1
- package/index.d.ts +3 -1
- package/index.d.ts.map +1 -1
- package/index.js +3 -0
- package/index.js.map +1 -1
- package/internal/element-resets.d.ts +17 -0
- package/internal/element-resets.d.ts.map +1 -0
- package/internal/element-resets.js +32 -0
- package/internal/element-resets.js.map +1 -0
- package/internal/file-tree/FileTreeNode.d.ts.map +1 -1
- package/internal/file-tree/FileTreeNode.js +2 -1
- package/internal/file-tree/FileTreeNode.js.map +1 -1
- package/internal/markdown-components.d.ts +7 -0
- package/internal/markdown-components.d.ts.map +1 -1
- package/internal/markdown-components.js +15 -5
- package/internal/markdown-components.js.map +1 -1
- package/invitation/InvitationManager.d.ts.map +1 -1
- package/invitation/InvitationManager.js +2 -1
- package/invitation/InvitationManager.js.map +1 -1
- package/manifest/ApplyManifestDialog.d.ts.map +1 -1
- package/manifest/ApplyManifestDialog.js +2 -1
- package/manifest/ApplyManifestDialog.js.map +1 -1
- package/mcp-server/steps/IdentityTransportStep.d.ts.map +1 -1
- package/mcp-server/steps/IdentityTransportStep.js +2 -1
- package/mcp-server/steps/IdentityTransportStep.js.map +1 -1
- package/package.json +4 -4
- package/platform-client/CreatePlatformClientForm.d.ts.map +1 -1
- package/platform-client/CreatePlatformClientForm.js +2 -1
- package/platform-client/CreatePlatformClientForm.js.map +1 -1
- package/platform-client/PlatformClientDetailPanel.d.ts.map +1 -1
- package/platform-client/PlatformClientDetailPanel.js +2 -1
- package/platform-client/PlatformClientDetailPanel.js.map +1 -1
- package/pricing-governance/BaselineEditor.d.ts.map +1 -1
- package/pricing-governance/BaselineEditor.js +2 -1
- package/pricing-governance/BaselineEditor.js.map +1 -1
- package/pricing-governance/PricingGovernanceConsole.d.ts.map +1 -1
- package/pricing-governance/PricingGovernanceConsole.js +2 -1
- package/pricing-governance/PricingGovernanceConsole.js.map +1 -1
- package/provider-standing/ProviderStandingAccessNotice.d.ts +11 -0
- package/provider-standing/ProviderStandingAccessNotice.d.ts.map +1 -0
- package/provider-standing/ProviderStandingAccessNotice.js +14 -0
- package/provider-standing/ProviderStandingAccessNotice.js.map +1 -0
- package/provider-standing/ProviderStandingConsole.d.ts +24 -0
- package/provider-standing/ProviderStandingConsole.d.ts.map +1 -0
- package/provider-standing/ProviderStandingConsole.js +98 -0
- package/provider-standing/ProviderStandingConsole.js.map +1 -0
- package/provider-standing/index.d.ts +6 -0
- package/provider-standing/index.d.ts.map +1 -0
- package/provider-standing/index.js +6 -0
- package/provider-standing/index.js.map +1 -0
- package/provider-standing/useProviderStanding.d.ts +34 -0
- package/provider-standing/useProviderStanding.d.ts.map +1 -0
- package/provider-standing/useProviderStanding.js +26 -0
- package/provider-standing/useProviderStanding.js.map +1 -0
- package/provider.d.ts +12 -0
- package/provider.d.ts.map +1 -1
- package/provider.js +12 -0
- package/provider.js.map +1 -1
- package/resource-creation/StepIndicator.d.ts.map +1 -1
- package/resource-creation/StepIndicator.js +2 -1
- package/resource-creation/StepIndicator.js.map +1 -1
- package/schedule/ScheduleDetailView.d.ts.map +1 -1
- package/schedule/ScheduleDetailView.js +3 -2
- package/schedule/ScheduleDetailView.js.map +1 -1
- package/schedule/ScheduleRunsTable.d.ts.map +1 -1
- package/schedule/ScheduleRunsTable.js +2 -1
- package/schedule/ScheduleRunsTable.js.map +1 -1
- package/session/facets/ArtifactsTab.d.ts.map +1 -1
- package/session/facets/ArtifactsTab.js +2 -1
- package/session/facets/ArtifactsTab.js.map +1 -1
- package/session/facets/ChangesTab.d.ts.map +1 -1
- package/session/facets/ChangesTab.js +2 -1
- package/session/facets/ChangesTab.js.map +1 -1
- package/session/facets/UsageTab.d.ts +10 -3
- package/session/facets/UsageTab.d.ts.map +1 -1
- package/session/facets/UsageTab.js +42 -6
- package/session/facets/UsageTab.js.map +1 -1
- package/session/index.d.ts +1 -1
- package/session/index.d.ts.map +1 -1
- package/session/index.js.map +1 -1
- package/session/useSessionUsage.d.ts +25 -0
- package/session/useSessionUsage.d.ts.map +1 -1
- package/session/useSessionUsage.js +11 -0
- package/session/useSessionUsage.js.map +1 -1
- package/settings/settings-nav.d.ts.map +1 -1
- package/settings/settings-nav.js +7 -1
- package/settings/settings-nav.js.map +1 -1
- package/settings/useSettingsNavGroups.d.ts.map +1 -1
- package/settings/useSettingsNavGroups.js +4 -1
- package/settings/useSettingsNavGroups.js.map +1 -1
- package/sharing/ShareAgentDialog.d.ts.map +1 -1
- package/sharing/ShareAgentDialog.js +2 -1
- package/sharing/ShareAgentDialog.js.map +1 -1
- package/sidebar/WorkspaceSidebar.d.ts.map +1 -1
- package/sidebar/WorkspaceSidebar.js +2 -1
- package/sidebar/WorkspaceSidebar.js.map +1 -1
- package/skill/SkillFileBrowser.d.ts.map +1 -1
- package/skill/SkillFileBrowser.js +2 -1
- package/skill/SkillFileBrowser.js.map +1 -1
- package/skill/internal/fetchAndUnpackArtifact.d.ts +1 -1
- package/skill/internal/fetchAndUnpackArtifact.d.ts.map +1 -1
- package/skill/internal/fetchAndUnpackArtifact.js +47 -3
- package/skill/internal/fetchAndUnpackArtifact.js.map +1 -1
- package/skill/useSkillDiff.d.ts.map +1 -1
- package/skill/useSkillDiff.js +7 -1
- package/skill/useSkillDiff.js.map +1 -1
- package/skill/useSkillUpload.d.ts.map +1 -1
- package/skill/useSkillUpload.js +22 -4
- package/skill/useSkillUpload.js.map +1 -1
- package/src/__tests__/bare-host-resets.layout.test.tsx +184 -0
- package/src/agent/steps/IdentityStep.tsx +2 -1
- package/src/agent-instance/AgentInstanceDetailPanel.tsx +2 -1
- package/src/api-key/CreateApiKeyForm.tsx +2 -1
- package/src/channel/ChannelConversationsDialog.tsx +2 -1
- package/src/channel/ChannelTemplatesDialog.tsx +2 -1
- package/src/channel/ConnectSlackDialog.tsx +2 -1
- package/src/channel/connect/ServingAppSection.tsx +2 -1
- package/src/channel-app/CreateChannelAppForm.tsx +2 -1
- package/src/conversation/ConversationListPane.tsx +2 -5
- package/src/conversation/ConversationTimelineView.tsx +2 -1
- package/src/cursor-accounts/CursorAccountsConsole.tsx +2 -1
- package/src/cursor-accounts/MemberKeysPanel.tsx +2 -1
- package/src/dashboard/DashboardFailedRuns.tsx +2 -1
- package/src/dependency-graph/DependencyGraph.tsx +2 -1
- package/src/dependency-graph/DependencyTreeNode.tsx +2 -1
- package/src/environment/EnvironmentPicker.tsx +2 -1
- package/src/error/SecretFlowErrorGuide.tsx +3 -2
- package/src/execution/ArtifactContentBody.tsx +2 -1
- package/src/execution/ArtifactsWidget.tsx +2 -1
- package/src/execution/FileChangeProgressBar.tsx +2 -1
- package/src/execution/TodoList.tsx +2 -1
- package/src/execution/WriteBacksWidget.tsx +2 -1
- package/src/execution/tool-categories.ts +17 -41
- package/src/iam-policy/PeopleWithAccess.tsx +2 -1
- package/src/iam-policy/PrincipalPicker.tsx +2 -0
- package/src/iam-policy/RoleSelector.tsx +2 -1
- package/src/identity-provider/CreateIdentityProviderForm.tsx +2 -1
- package/src/identity-provider/IdentityProviderDetailPanel.tsx +2 -1
- package/src/identity-provider/IdentityProviderWizard.tsx +2 -1
- package/src/index.ts +13 -0
- package/src/internal/element-resets.ts +33 -0
- package/src/internal/file-tree/FileTreeNode.tsx +2 -1
- package/src/internal/markdown-components.tsx +15 -5
- package/src/invitation/InvitationManager.tsx +3 -2
- package/src/manifest/ApplyManifestDialog.tsx +2 -1
- package/src/mcp-server/steps/IdentityTransportStep.tsx +3 -2
- package/src/platform-client/CreatePlatformClientForm.tsx +4 -3
- package/src/platform-client/PlatformClientDetailPanel.tsx +4 -3
- package/src/pricing-governance/BaselineEditor.tsx +3 -2
- package/src/pricing-governance/PricingGovernanceConsole.tsx +2 -1
- package/src/provider-standing/ProviderStandingAccessNotice.tsx +36 -0
- package/src/provider-standing/ProviderStandingConsole.tsx +216 -0
- package/src/provider-standing/__tests__/ProviderStandingConsole.test.tsx +143 -0
- package/src/provider-standing/index.ts +8 -0
- package/src/provider-standing/useProviderStanding.ts +50 -0
- package/src/provider.tsx +12 -0
- package/src/resource-creation/StepIndicator.tsx +2 -1
- package/src/schedule/ScheduleDetailView.tsx +3 -2
- package/src/schedule/ScheduleRunsTable.tsx +2 -1
- package/src/session/__tests__/UsageTab.test.tsx +136 -0
- package/src/session/__tests__/useSessionUsage.test.ts +19 -0
- package/src/session/facets/ArtifactsTab.tsx +2 -1
- package/src/session/facets/ChangesTab.tsx +2 -1
- package/src/session/facets/UsageTab.tsx +98 -6
- package/src/session/index.ts +1 -0
- package/src/session/useSessionUsage.ts +37 -0
- package/src/settings/__tests__/useSettingsNavGroups.test.tsx +11 -1
- package/src/settings/settings-nav.ts +7 -0
- package/src/settings/useSettingsNavGroups.ts +8 -1
- package/src/sharing/ShareAgentDialog.tsx +2 -1
- package/src/sidebar/WorkspaceSidebar.tsx +2 -1
- package/src/skill/SkillFileBrowser.tsx +2 -1
- package/src/skill/__tests__/SkillFileBrowser.test.tsx +10 -2
- package/src/skill/__tests__/fetchAndUnpackArtifact.test.ts +90 -0
- package/src/skill/__tests__/useSkillUpload.test.ts +69 -0
- package/src/skill/internal/fetchAndUnpackArtifact.ts +54 -4
- package/src/skill/useSkillDiff.ts +7 -1
- package/src/skill/useSkillUpload.ts +25 -6
- package/src/workflow/FailedRunsWidget.tsx +2 -1
- package/src/workflow/PendingApprovalsWidget.tsx +2 -1
- package/src/workflow/facets/WorkflowArtifactsTab.tsx +2 -1
- package/src/workflow/facets/WorkflowUsageTab.tsx +2 -1
- package/src/workflow/inspector/WorkflowSummaryPanel.tsx +2 -1
- package/src/workflow/inspector/tabs/BranchesTab.tsx +2 -1
- package/src/workflow/inspector/tabs/IterationTab.tsx +2 -1
- package/src/workflow/instance/WorkflowInstanceDetailPanel.tsx +2 -1
- package/src/workspace/ExplorerTree.tsx +2 -1
- package/src/workspace/WorkspaceContentSearch.tsx +2 -1
- package/src/workspace/WorkspaceFileSearch.tsx +2 -1
- package/src/workspace/WorkspaceSummary.tsx +2 -1
- package/styles.css +1 -1
- package/workflow/FailedRunsWidget.d.ts.map +1 -1
- package/workflow/FailedRunsWidget.js +2 -1
- package/workflow/FailedRunsWidget.js.map +1 -1
- package/workflow/PendingApprovalsWidget.d.ts.map +1 -1
- package/workflow/PendingApprovalsWidget.js +2 -1
- package/workflow/PendingApprovalsWidget.js.map +1 -1
- package/workflow/facets/WorkflowArtifactsTab.d.ts.map +1 -1
- package/workflow/facets/WorkflowArtifactsTab.js +2 -1
- package/workflow/facets/WorkflowArtifactsTab.js.map +1 -1
- package/workflow/facets/WorkflowUsageTab.d.ts.map +1 -1
- package/workflow/facets/WorkflowUsageTab.js +2 -1
- package/workflow/facets/WorkflowUsageTab.js.map +1 -1
- package/workflow/inspector/WorkflowSummaryPanel.d.ts.map +1 -1
- package/workflow/inspector/WorkflowSummaryPanel.js +2 -1
- package/workflow/inspector/WorkflowSummaryPanel.js.map +1 -1
- package/workflow/inspector/tabs/BranchesTab.d.ts.map +1 -1
- package/workflow/inspector/tabs/BranchesTab.js +2 -1
- package/workflow/inspector/tabs/BranchesTab.js.map +1 -1
- package/workflow/inspector/tabs/IterationTab.d.ts.map +1 -1
- package/workflow/inspector/tabs/IterationTab.js +2 -1
- package/workflow/inspector/tabs/IterationTab.js.map +1 -1
- package/workflow/instance/WorkflowInstanceDetailPanel.d.ts.map +1 -1
- package/workflow/instance/WorkflowInstanceDetailPanel.js +2 -1
- package/workflow/instance/WorkflowInstanceDetailPanel.js.map +1 -1
- package/workspace/ExplorerTree.d.ts.map +1 -1
- package/workspace/ExplorerTree.js +2 -1
- package/workspace/ExplorerTree.js.map +1 -1
- package/workspace/WorkspaceContentSearch.d.ts.map +1 -1
- package/workspace/WorkspaceContentSearch.js +2 -1
- package/workspace/WorkspaceContentSearch.js.map +1 -1
- package/workspace/WorkspaceFileSearch.d.ts.map +1 -1
- package/workspace/WorkspaceFileSearch.js +2 -1
- package/workspace/WorkspaceFileSearch.js.map +1 -1
- package/workspace/WorkspaceSummary.d.ts.map +1 -1
- package/workspace/WorkspaceSummary.js +2 -1
- package/workspace/WorkspaceSummary.js.map +1 -1
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { cn } from "@stigmer/theme";
|
|
4
|
+
import { getUserMessage, isPermissionDenied } from "@stigmer/sdk";
|
|
5
|
+
import type { Timestamp } from "@bufbuild/protobuf/wkt";
|
|
6
|
+
import type { ProviderStandingEntry } from "@stigmer/protos/ai/stigmer/platform/providerstanding/v1/io_pb";
|
|
7
|
+
import { Button } from "../button/index.js";
|
|
8
|
+
import { ProviderStandingAccessNotice } from "./ProviderStandingAccessNotice.js";
|
|
9
|
+
import { useProviderStanding } from "./useProviderStanding.js";
|
|
10
|
+
|
|
11
|
+
/** Props for {@link ProviderStandingConsole}. */
|
|
12
|
+
export interface ProviderStandingConsoleProps {
|
|
13
|
+
/** Additional CSS class names. */
|
|
14
|
+
readonly className?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// BigInt literals (0n) require an ES2020 target, which not every consuming
|
|
18
|
+
// app's tsconfig guarantees — the constructor form is target-agnostic (the
|
|
19
|
+
// cursor-account-format precedent).
|
|
20
|
+
const ZERO = BigInt(0);
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A probe verdict older than this is flagged stale — the probe-silence
|
|
24
|
+
* alert's UI twin: the probe runs hourly and the alert fires at 3h (the
|
|
25
|
+
* 60m interval plus generous grace), so the console goes amber on the
|
|
26
|
+
* same clock operators are paged on.
|
|
27
|
+
*/
|
|
28
|
+
const STALE_AFTER_MS = 3 * 60 * 60 * 1000;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Display metadata for the probe's closed status vocabulary (the
|
|
32
|
+
* lowercase labels StandingStatus persists). An unknown future label
|
|
33
|
+
* degrades to a muted badge showing the raw label — the wire contract is
|
|
34
|
+
* a string precisely so consoles never fail to decode a new status.
|
|
35
|
+
*/
|
|
36
|
+
const STATUS_DISPLAY: Record<string, { label: string; tone: "ok" | "warn" | "muted" }> = {
|
|
37
|
+
healthy: { label: "Healthy", tone: "ok" },
|
|
38
|
+
platform_billing: { label: "Billing rejected", tone: "warn" },
|
|
39
|
+
platform_auth: { label: "Auth rejected", tone: "warn" },
|
|
40
|
+
unclassified_error: { label: "Erroring", tone: "warn" },
|
|
41
|
+
unreachable: { label: "Unreachable", tone: "warn" },
|
|
42
|
+
not_configured: { label: "Not configured", tone: "muted" },
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The platform-operator console page for provider standing: one card per
|
|
47
|
+
* platform LLM provider account with the latest canary-probe verdict —
|
|
48
|
+
* status, round-trip latency, upstream HTTP status, a bounded error
|
|
49
|
+
* summary, and when the probe last ran (flagged when stale).
|
|
50
|
+
*
|
|
51
|
+
* Read-only by design: the detection core (hourly probe, standing store,
|
|
52
|
+
* degraded/silence alerts) lives server-side; this surface only makes it
|
|
53
|
+
* visible where operators already look. Requires
|
|
54
|
+
* `can_view_provider_standing` on `platform:stigmer` — non-operators see
|
|
55
|
+
* the designed access notice.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```tsx
|
|
59
|
+
* <ProviderStandingConsole />
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export function ProviderStandingConsole({ className }: ProviderStandingConsoleProps) {
|
|
63
|
+
const { standing, isLoading, isRefetching, error, refetch } = useProviderStanding();
|
|
64
|
+
|
|
65
|
+
if (isLoading) {
|
|
66
|
+
return (
|
|
67
|
+
<div className={cn("stg:space-y-2", className)} aria-busy="true">
|
|
68
|
+
<div className="stg:h-4 stg:w-40 stg:animate-pulse stg:rounded stg:bg-muted-subtle" />
|
|
69
|
+
{Array.from({ length: 2 }, (_, i) => (
|
|
70
|
+
<div key={i} className="stg:h-16 stg:animate-pulse stg:rounded-lg stg:bg-muted-subtle" />
|
|
71
|
+
))}
|
|
72
|
+
</div>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (error) {
|
|
77
|
+
// A non-operator landing here is expected (the route is reachable by
|
|
78
|
+
// URL) — show the designed access notice, not a raw RPC error.
|
|
79
|
+
if (isPermissionDenied(error)) {
|
|
80
|
+
return <ProviderStandingAccessNotice className={className} />;
|
|
81
|
+
}
|
|
82
|
+
return (
|
|
83
|
+
<p className={cn("stg:text-destructive stg:text-xs", className)} role="alert">
|
|
84
|
+
{getUserMessage(error)}
|
|
85
|
+
</p>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const providers = standing?.providers ?? [];
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<div className={cn("stg:space-y-3", className)}>
|
|
93
|
+
<div className="stg:flex stg:items-center stg:justify-between">
|
|
94
|
+
<p className="stg:text-xs stg:text-muted-foreground">
|
|
95
|
+
Latest canary verdict per platform provider account, probed hourly.
|
|
96
|
+
</p>
|
|
97
|
+
<Button
|
|
98
|
+
variant="outline"
|
|
99
|
+
size="sm"
|
|
100
|
+
onClick={refetch}
|
|
101
|
+
disabled={isRefetching}
|
|
102
|
+
>
|
|
103
|
+
{isRefetching ? "Refreshing…" : "Refresh"}
|
|
104
|
+
</Button>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
{providers.length === 0 ? (
|
|
108
|
+
<p className="stg:rounded-lg stg:border stg:border-border stg:bg-card stg:px-4 stg:py-6 stg:text-center stg:text-xs stg:text-muted-foreground">
|
|
109
|
+
No probe verdicts recorded yet — the standing probe has not
|
|
110
|
+
completed a pass since this deployment came up.
|
|
111
|
+
</p>
|
|
112
|
+
) : (
|
|
113
|
+
<ul className="stg:m-0 stg:grid stg:list-none stg:gap-2 stg:p-0 sm:stg:grid-cols-2">
|
|
114
|
+
{providers.map((entry) => (
|
|
115
|
+
<StandingCard key={entry.provider} entry={entry} />
|
|
116
|
+
))}
|
|
117
|
+
</ul>
|
|
118
|
+
)}
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function StandingCard({ entry }: { readonly entry: ProviderStandingEntry }) {
|
|
124
|
+
const display = STATUS_DISPLAY[entry.status] ?? {
|
|
125
|
+
label: entry.status || "unknown",
|
|
126
|
+
tone: "muted" as const,
|
|
127
|
+
};
|
|
128
|
+
const stale = isStale(entry.checkedAt);
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<li className="stg:rounded-lg stg:border stg:border-border stg:bg-card stg:p-3">
|
|
132
|
+
<div className="stg:flex stg:items-center stg:justify-between stg:gap-2">
|
|
133
|
+
<span className="stg:text-sm stg:font-semibold stg:capitalize stg:text-foreground">
|
|
134
|
+
{entry.provider}
|
|
135
|
+
</span>
|
|
136
|
+
<StatusBadge tone={display.tone} label={display.label} />
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
<dl className="stg:mt-2 stg:flex stg:flex-wrap stg:gap-x-4 stg:gap-y-1 stg:text-xs stg:text-muted-foreground">
|
|
140
|
+
{entry.latencyMs > ZERO && (
|
|
141
|
+
<div className="stg:flex stg:gap-1">
|
|
142
|
+
<dt>Latency</dt>
|
|
143
|
+
<dd className="stg:m-0 stg:text-foreground">{`${entry.latencyMs}ms`}</dd>
|
|
144
|
+
</div>
|
|
145
|
+
)}
|
|
146
|
+
{entry.httpStatus > 0 && (
|
|
147
|
+
<div className="stg:flex stg:gap-1">
|
|
148
|
+
<dt>HTTP</dt>
|
|
149
|
+
<dd className="stg:m-0 stg:text-foreground">{entry.httpStatus}</dd>
|
|
150
|
+
</div>
|
|
151
|
+
)}
|
|
152
|
+
<div className="stg:flex stg:gap-1">
|
|
153
|
+
<dt>Probed</dt>
|
|
154
|
+
<dd
|
|
155
|
+
className={cn(
|
|
156
|
+
"stg:m-0",
|
|
157
|
+
stale ? "stg:font-medium stg:text-destructive" : "stg:text-foreground",
|
|
158
|
+
)}
|
|
159
|
+
>
|
|
160
|
+
{formatProbeTime(entry.checkedAt)}
|
|
161
|
+
{stale ? " — stale" : ""}
|
|
162
|
+
</dd>
|
|
163
|
+
</div>
|
|
164
|
+
</dl>
|
|
165
|
+
|
|
166
|
+
{entry.errorSummary && (
|
|
167
|
+
<p className="stg:mt-2 stg:break-words stg:rounded stg:bg-muted-subtle stg:px-2 stg:py-1 stg:text-[11px] stg:text-muted-foreground">
|
|
168
|
+
{entry.errorSummary}
|
|
169
|
+
</p>
|
|
170
|
+
)}
|
|
171
|
+
</li>
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Small tonal status badge — the cursor-accounts StateBadge shape, local
|
|
177
|
+
* to this module (the two operator surfaces evolve independently).
|
|
178
|
+
*/
|
|
179
|
+
function StatusBadge({
|
|
180
|
+
tone,
|
|
181
|
+
label,
|
|
182
|
+
}: {
|
|
183
|
+
readonly tone: "ok" | "warn" | "muted";
|
|
184
|
+
readonly label: string;
|
|
185
|
+
}) {
|
|
186
|
+
return (
|
|
187
|
+
<span
|
|
188
|
+
className={cn(
|
|
189
|
+
"stg:inline-block stg:rounded stg:px-1.5 stg:py-0.5 stg:text-[10px] stg:font-medium",
|
|
190
|
+
tone === "ok" && "stg:bg-accent stg:text-primary",
|
|
191
|
+
tone === "warn" && "stg:bg-muted-subtle stg:text-destructive",
|
|
192
|
+
tone === "muted" && "stg:bg-muted-subtle stg:text-muted-foreground",
|
|
193
|
+
)}
|
|
194
|
+
>
|
|
195
|
+
{label}
|
|
196
|
+
</span>
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function isStale(ts: Timestamp | undefined): boolean {
|
|
201
|
+
if (!ts || ts.seconds === ZERO) return true;
|
|
202
|
+
return Date.now() - Number(ts.seconds) * 1000 > STALE_AFTER_MS;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Proto Timestamp to a compact local date-time (e.g. "Jul 22, 14:05"). */
|
|
206
|
+
function formatProbeTime(ts: Timestamp | undefined): string {
|
|
207
|
+
if (!ts || ts.seconds === ZERO) {
|
|
208
|
+
return "never";
|
|
209
|
+
}
|
|
210
|
+
return new Date(Number(ts.seconds) * 1000).toLocaleString(undefined, {
|
|
211
|
+
month: "short",
|
|
212
|
+
day: "numeric",
|
|
213
|
+
hour: "2-digit",
|
|
214
|
+
minute: "2-digit",
|
|
215
|
+
});
|
|
216
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { describe, it, expect, vi, afterEach } from "vitest";
|
|
2
|
+
import { cleanup, render, screen, waitFor } from "@testing-library/react";
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
import { create } from "@bufbuild/protobuf";
|
|
5
|
+
import { timestampFromDate } from "@bufbuild/protobuf/wkt";
|
|
6
|
+
import { StigmerError } from "@stigmer/sdk";
|
|
7
|
+
import {
|
|
8
|
+
ProviderStandingEntrySchema,
|
|
9
|
+
ProviderStandingViewSchema,
|
|
10
|
+
} from "@stigmer/protos/ai/stigmer/platform/providerstanding/v1/io_pb";
|
|
11
|
+
import { StigmerContext } from "../../context";
|
|
12
|
+
import { FetchCacheContext } from "../../internal/FetchCacheProvider";
|
|
13
|
+
import { ProviderStandingConsole } from "../ProviderStandingConsole";
|
|
14
|
+
|
|
15
|
+
function createMockStigmer(getStandingView: ReturnType<typeof vi.fn>) {
|
|
16
|
+
return {
|
|
17
|
+
providerStanding: { getStandingView },
|
|
18
|
+
} as never;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function wrapper(client: unknown) {
|
|
22
|
+
return function Wrapper({ children }: { children: ReactNode }) {
|
|
23
|
+
return (
|
|
24
|
+
<FetchCacheContext.Provider value={null}>
|
|
25
|
+
<StigmerContext.Provider value={client as never}>
|
|
26
|
+
{children}
|
|
27
|
+
</StigmerContext.Provider>
|
|
28
|
+
</FetchCacheContext.Provider>
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function entry(overrides: Record<string, unknown> = {}) {
|
|
34
|
+
return create(ProviderStandingEntrySchema, {
|
|
35
|
+
provider: "anthropic",
|
|
36
|
+
status: "healthy",
|
|
37
|
+
httpStatus: 200,
|
|
38
|
+
latencyMs: BigInt(842),
|
|
39
|
+
errorSummary: "",
|
|
40
|
+
checkedAt: timestampFromDate(new Date()),
|
|
41
|
+
...overrides,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function view(entries: ReturnType<typeof entry>[]) {
|
|
46
|
+
return create(ProviderStandingViewSchema, { providers: entries });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
afterEach(() => {
|
|
50
|
+
cleanup();
|
|
51
|
+
vi.restoreAllMocks();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe("ProviderStandingConsole", () => {
|
|
55
|
+
it("renders one card per provider with status, latency, and probe time", async () => {
|
|
56
|
+
const client = createMockStigmer(
|
|
57
|
+
vi.fn().mockResolvedValue(
|
|
58
|
+
view([
|
|
59
|
+
entry(),
|
|
60
|
+
entry({
|
|
61
|
+
provider: "openai",
|
|
62
|
+
status: "platform_billing",
|
|
63
|
+
httpStatus: 429,
|
|
64
|
+
latencyMs: BigInt(0),
|
|
65
|
+
errorSummary: "insufficient_quota: billing hard limit reached",
|
|
66
|
+
}),
|
|
67
|
+
]),
|
|
68
|
+
),
|
|
69
|
+
);
|
|
70
|
+
render(<ProviderStandingConsole />, { wrapper: wrapper(client) });
|
|
71
|
+
|
|
72
|
+
await waitFor(() => {
|
|
73
|
+
expect(screen.getByText("anthropic")).toBeTruthy();
|
|
74
|
+
});
|
|
75
|
+
expect(screen.getByText("Healthy")).toBeTruthy();
|
|
76
|
+
expect(screen.getByText("842ms")).toBeTruthy();
|
|
77
|
+
|
|
78
|
+
expect(screen.getByText("openai")).toBeTruthy();
|
|
79
|
+
expect(screen.getByText("Billing rejected")).toBeTruthy();
|
|
80
|
+
expect(screen.getByText("429")).toBeTruthy();
|
|
81
|
+
expect(
|
|
82
|
+
screen.getByText("insufficient_quota: billing hard limit reached"),
|
|
83
|
+
).toBeTruthy();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("flags a verdict older than the probe-silence window as stale", async () => {
|
|
87
|
+
const fourHoursAgo = new Date(Date.now() - 4 * 60 * 60 * 1000);
|
|
88
|
+
const client = createMockStigmer(
|
|
89
|
+
vi.fn().mockResolvedValue(
|
|
90
|
+
view([entry({ checkedAt: timestampFromDate(fourHoursAgo) })]),
|
|
91
|
+
),
|
|
92
|
+
);
|
|
93
|
+
render(<ProviderStandingConsole />, { wrapper: wrapper(client) });
|
|
94
|
+
|
|
95
|
+
await waitFor(() => {
|
|
96
|
+
expect(screen.getByText(/— stale/)).toBeTruthy();
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("degrades an unknown future status label to a muted badge with the raw label", async () => {
|
|
101
|
+
const client = createMockStigmer(
|
|
102
|
+
vi.fn().mockResolvedValue(view([entry({ status: "rate_limited" })])),
|
|
103
|
+
);
|
|
104
|
+
render(<ProviderStandingConsole />, { wrapper: wrapper(client) });
|
|
105
|
+
|
|
106
|
+
await waitFor(() => {
|
|
107
|
+
expect(screen.getByText("rate_limited")).toBeTruthy();
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("shows the designed access notice on permission denied, not a raw RPC error", async () => {
|
|
112
|
+
const client = createMockStigmer(
|
|
113
|
+
vi.fn().mockRejectedValue(
|
|
114
|
+
new StigmerError("permission-denied", "only platform operators can view provider standing", 7),
|
|
115
|
+
),
|
|
116
|
+
);
|
|
117
|
+
render(<ProviderStandingConsole />, { wrapper: wrapper(client) });
|
|
118
|
+
|
|
119
|
+
await waitFor(() => {
|
|
120
|
+
expect(screen.getByText("Platform operator access required")).toBeTruthy();
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("shows the error message for non-permission failures", async () => {
|
|
125
|
+
const client = createMockStigmer(
|
|
126
|
+
vi.fn().mockRejectedValue(new StigmerError("internal", "standing lookup failed", 13)),
|
|
127
|
+
);
|
|
128
|
+
render(<ProviderStandingConsole />, { wrapper: wrapper(client) });
|
|
129
|
+
|
|
130
|
+
await waitFor(() => {
|
|
131
|
+
expect(screen.getByRole("alert")).toBeTruthy();
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("explains the empty state before the probe's first pass", async () => {
|
|
136
|
+
const client = createMockStigmer(vi.fn().mockResolvedValue(view([])));
|
|
137
|
+
render(<ProviderStandingConsole />, { wrapper: wrapper(client) });
|
|
138
|
+
|
|
139
|
+
await waitFor(() => {
|
|
140
|
+
expect(screen.getByText(/No probe verdicts recorded yet/)).toBeTruthy();
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Data hooks
|
|
2
|
+
export { useProviderStanding } from "./useProviderStanding.js";
|
|
3
|
+
export type { UseProviderStandingReturn } from "./useProviderStanding.js";
|
|
4
|
+
|
|
5
|
+
// Styled components
|
|
6
|
+
export { ProviderStandingConsole } from "./ProviderStandingConsole.js";
|
|
7
|
+
export type { ProviderStandingConsoleProps } from "./ProviderStandingConsole.js";
|
|
8
|
+
export { ProviderStandingAccessNotice } from "./ProviderStandingAccessNotice.js";
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ProviderStandingView } from "@stigmer/protos/ai/stigmer/platform/providerstanding/v1/io_pb";
|
|
4
|
+
import { useStigmer } from "../hooks.js";
|
|
5
|
+
import { useFetch } from "../internal/useFetch.js";
|
|
6
|
+
|
|
7
|
+
/** Return value of {@link useProviderStanding}. */
|
|
8
|
+
export interface UseProviderStandingReturn {
|
|
9
|
+
/** The standing view, or `null` before the first successful fetch. */
|
|
10
|
+
readonly standing: ProviderStandingView | null;
|
|
11
|
+
/** `true` while the initial fetch is in flight. */
|
|
12
|
+
readonly isLoading: boolean;
|
|
13
|
+
/** `true` while a background refetch is in flight and stale data is shown. */
|
|
14
|
+
readonly isRefetching: boolean;
|
|
15
|
+
/** Error from the last failed request, or `null` when healthy. */
|
|
16
|
+
readonly error: Error | null;
|
|
17
|
+
/** Discard cached data and re-fetch from the server. */
|
|
18
|
+
readonly refetch: () => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Data hook for platform provider standing: the latest canary-probe
|
|
23
|
+
* verdict per platform LLM provider account (status, latency, bounded
|
|
24
|
+
* error summary, probe time), recorded hourly by the standing probe.
|
|
25
|
+
*
|
|
26
|
+
* Platform-operator surface: the caller needs
|
|
27
|
+
* `can_view_provider_standing` on `platform:stigmer`. Cloud-only — the
|
|
28
|
+
* OSS Go server does not implement the controller.
|
|
29
|
+
*
|
|
30
|
+
* Pass `enabled: false` to skip fetching (stable no-op).
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```tsx
|
|
34
|
+
* const { standing, isLoading, refetch } = useProviderStanding();
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export function useProviderStanding(
|
|
38
|
+
options?: { readonly enabled?: boolean },
|
|
39
|
+
): UseProviderStandingReturn {
|
|
40
|
+
const stigmer = useStigmer();
|
|
41
|
+
const enabled = options?.enabled ?? true;
|
|
42
|
+
|
|
43
|
+
const { data: standing, isLoading, isRefetching, error, refetch } = useFetch(
|
|
44
|
+
enabled ? () => stigmer.providerStanding.getStandingView() : null,
|
|
45
|
+
[enabled, stigmer],
|
|
46
|
+
null as ProviderStandingView | null,
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
return { standing, isLoading, isRefetching, error, refetch };
|
|
50
|
+
}
|
package/src/provider.tsx
CHANGED
|
@@ -240,6 +240,18 @@ const EMPTY_REVIEW_RENDERERS: ReviewRenderers = {};
|
|
|
240
240
|
* column makes it balloon even in a content-sized page), so only
|
|
241
241
|
* the host — which knows its layout — can apply it safely.
|
|
242
242
|
*
|
|
243
|
+
* **Hosts without a global CSS reset.** SDK components carry their
|
|
244
|
+
* own per-element resets (lists, fieldsets, form controls) and the
|
|
245
|
+
* markdown surfaces declare their margins explicitly, so they render
|
|
246
|
+
* correctly with no Tailwind-preflight-style reset in the host page
|
|
247
|
+
* (#695). Two deliberate boundaries remain the host's: font-family
|
|
248
|
+
* inherits from the host (an embedded widget adopting host typography
|
|
249
|
+
* is correct — set `--stgm-font-sans` to override), and the host's
|
|
250
|
+
* OWN content inside the provider keeps its user-agent styling — the
|
|
251
|
+
* SDK never resets elements it did not render. If your reset-less
|
|
252
|
+
* page composes its own `<p>`/`<h*>` content between SDK components,
|
|
253
|
+
* their user-agent margins are yours to manage.
|
|
254
|
+
*
|
|
243
255
|
* Two stable selector hooks are part of the public contract:
|
|
244
256
|
* `data-stgm-root` marks the in-tree container (and only it);
|
|
245
257
|
* `data-stgm-portal` marks the portal container appended to
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { cn } from "@stigmer/theme";
|
|
4
|
+
import { UNSTYLED_LIST } from "../internal/element-resets.js";
|
|
4
5
|
|
|
5
6
|
/** Props for {@link StepIndicator}. */
|
|
6
7
|
export interface StepIndicatorProps {
|
|
@@ -37,7 +38,7 @@ export function StepIndicator({
|
|
|
37
38
|
aria-label="Wizard progress"
|
|
38
39
|
className={cn("stg:flex stg:flex-col stg:gap-1", className)}
|
|
39
40
|
>
|
|
40
|
-
<ol className="stg:flex stg:flex-col stg:gap-1" role="list">
|
|
41
|
+
<ol className={cn(UNSTYLED_LIST, "stg:flex stg:flex-col stg:gap-1")} role="list">
|
|
41
42
|
{steps.map((step, index) => {
|
|
42
43
|
const state = getStepState(index, currentStepIndex);
|
|
43
44
|
const isClickable = state === "completed" && onStepClick != null;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
4
4
|
import { cn } from "@stigmer/theme";
|
|
5
|
+
import { UNSTYLED_LIST } from "../internal/element-resets.js";
|
|
5
6
|
import { create } from "@bufbuild/protobuf";
|
|
6
7
|
import { timestampDate } from "@bufbuild/protobuf/wkt";
|
|
7
8
|
import type { Schedule } from "@stigmer/protos/ai/stigmer/agentic/schedule/v1/api_pb";
|
|
@@ -1356,7 +1357,7 @@ function EnvironmentRefList({
|
|
|
1356
1357
|
return <span className="stg:text-sm stg:text-muted-foreground">—</span>;
|
|
1357
1358
|
}
|
|
1358
1359
|
return (
|
|
1359
|
-
<ul className="stg:flex stg:flex-col stg:gap-0.5">
|
|
1360
|
+
<ul className={cn(UNSTYLED_LIST, "stg:flex stg:flex-col stg:gap-0.5")}>
|
|
1360
1361
|
{refs.map((ref, i) => (
|
|
1361
1362
|
<li
|
|
1362
1363
|
key={`${ref.org}/${ref.slug}-${i}`}
|
|
@@ -1470,7 +1471,7 @@ function WorkspaceSummary({
|
|
|
1470
1471
|
return <span className="stg:text-sm stg:text-muted-foreground">—</span>;
|
|
1471
1472
|
}
|
|
1472
1473
|
return (
|
|
1473
|
-
<ul className="stg:flex stg:flex-col stg:gap-0.5">
|
|
1474
|
+
<ul className={cn(UNSTYLED_LIST, "stg:flex stg:flex-col stg:gap-0.5")}>
|
|
1474
1475
|
{entries.map((entry, i) => {
|
|
1475
1476
|
const git =
|
|
1476
1477
|
entry.source?.source?.case === "gitRepo"
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState } from "react";
|
|
4
4
|
import { cn } from "@stigmer/theme";
|
|
5
|
+
import { UNSTYLED_LIST } from "../internal/element-resets.js";
|
|
5
6
|
import { timestampDate } from "@bufbuild/protobuf/wkt";
|
|
6
7
|
import {
|
|
7
8
|
ScheduleRunOrigin,
|
|
@@ -277,7 +278,7 @@ export function ScheduleRunsCompactList({
|
|
|
277
278
|
return <EmptyRuns />;
|
|
278
279
|
}
|
|
279
280
|
return (
|
|
280
|
-
<ul className="stg:divide-y stg:divide-border">
|
|
281
|
+
<ul className={cn(UNSTYLED_LIST, "stg:divide-y stg:divide-border")}>
|
|
281
282
|
{runs.map((run, i) => (
|
|
282
283
|
<CompactRunRow
|
|
283
284
|
key={runKey(run, i)}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
// The #362 console surface: the Usage tab pairs billing's RESOLVED
|
|
2
|
+
// per-execution model (the after-the-fact truth for Cursor Auto runs)
|
|
3
|
+
// with the tier the runner REQUESTED (the streaming summary's audit
|
|
4
|
+
// record that the account default was never left in control).
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect, afterEach, vi } from "vitest";
|
|
7
|
+
import { render, screen, cleanup } from "@testing-library/react";
|
|
8
|
+
import { create } from "@bufbuild/protobuf";
|
|
9
|
+
import { AgentExecutionSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
10
|
+
import { ServiceTier } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
11
|
+
import type { UseSessionUsageReturn } from "../useSessionUsage.js";
|
|
12
|
+
|
|
13
|
+
// UsageTab's render contract is what this suite pins; the aggregation
|
|
14
|
+
// itself (billing-report-wins, streaming fallback, breakdown mapping) is
|
|
15
|
+
// useSessionUsage's own suite. Mocking the hook also spares the RPC
|
|
16
|
+
// provider plumbing.
|
|
17
|
+
const usageMock = vi.hoisted(() => vi.fn<() => UseSessionUsageReturn>());
|
|
18
|
+
vi.mock("../useSessionUsage.js", async (importOriginal) => ({
|
|
19
|
+
...(await importOriginal<object>()),
|
|
20
|
+
useSessionUsage: () => usageMock(),
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
import { UsageTab } from "../facets/UsageTab.js";
|
|
24
|
+
|
|
25
|
+
// UsageWidget re-aggregates via the real hook internally — with the module
|
|
26
|
+
// mocked above it sees the same mocked value, which is exactly the intent.
|
|
27
|
+
|
|
28
|
+
const EMPTY_USAGE: UseSessionUsageReturn = {
|
|
29
|
+
totalCostUsd: 0,
|
|
30
|
+
totalTokens: 0,
|
|
31
|
+
inputTokens: 0,
|
|
32
|
+
outputTokens: 0,
|
|
33
|
+
cacheReadTokens: 0,
|
|
34
|
+
cacheCreationTokens: 0,
|
|
35
|
+
llmCallCount: 0,
|
|
36
|
+
modelBreakdown: [],
|
|
37
|
+
executionBreakdown: [],
|
|
38
|
+
primaryModel: "",
|
|
39
|
+
primaryProvider: "",
|
|
40
|
+
hasUsage: false,
|
|
41
|
+
isEstimated: false,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
function usageWith(overrides: Partial<UseSessionUsageReturn>): UseSessionUsageReturn {
|
|
45
|
+
return {
|
|
46
|
+
...EMPTY_USAGE,
|
|
47
|
+
hasUsage: true,
|
|
48
|
+
totalCostUsd: 0.0123,
|
|
49
|
+
totalTokens: 100,
|
|
50
|
+
llmCallCount: 1,
|
|
51
|
+
primaryModel: "composer-2.5",
|
|
52
|
+
primaryProvider: "cursor",
|
|
53
|
+
...overrides,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function executionWithTier(id: string, tier?: ServiceTier) {
|
|
58
|
+
return create(AgentExecutionSchema, {
|
|
59
|
+
metadata: { id },
|
|
60
|
+
spec: { sessionId: "ses_1" },
|
|
61
|
+
status: {
|
|
62
|
+
streamingUsage: {
|
|
63
|
+
totalTokens: 100n,
|
|
64
|
+
turnCount: 1,
|
|
65
|
+
estimatedCostUsd: 0.01,
|
|
66
|
+
model: "default",
|
|
67
|
+
...(tier !== undefined ? { requestedServiceTier: tier } : {}),
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
afterEach(cleanup);
|
|
74
|
+
|
|
75
|
+
describe("UsageTab (#362 model provenance)", () => {
|
|
76
|
+
it("shows the empty state when no usage exists", () => {
|
|
77
|
+
usageMock.mockReturnValue(EMPTY_USAGE);
|
|
78
|
+
render(<UsageTab executions={[]} />);
|
|
79
|
+
expect(screen.getByText(/No usage data yet/)).toBeTruthy();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("renders the billing-resolved model per run with its cost — the Auto forensics view", () => {
|
|
83
|
+
usageMock.mockReturnValue(usageWith({
|
|
84
|
+
executionBreakdown: [{
|
|
85
|
+
executionId: "exe_1",
|
|
86
|
+
resolvedModel: "cursor-grok-4.5-high-fast",
|
|
87
|
+
billableCostUsd: 0.0123,
|
|
88
|
+
isEstimated: false,
|
|
89
|
+
}],
|
|
90
|
+
}));
|
|
91
|
+
|
|
92
|
+
render(<UsageTab executions={[executionWithTier("exe_1", ServiceTier.STANDARD)]} />);
|
|
93
|
+
|
|
94
|
+
expect(screen.getByText(/cursor-grok-4\.5-high-fast/)).toBeTruthy();
|
|
95
|
+
expect(screen.getByRole("list", { name: "Per-execution model and tier" })).toBeTruthy();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("pairs the run with the tier the runner requested", () => {
|
|
99
|
+
usageMock.mockReturnValue(usageWith({
|
|
100
|
+
executionBreakdown: [{
|
|
101
|
+
executionId: "exe_1",
|
|
102
|
+
resolvedModel: "composer-2.5",
|
|
103
|
+
billableCostUsd: 0.0123,
|
|
104
|
+
isEstimated: false,
|
|
105
|
+
}],
|
|
106
|
+
}));
|
|
107
|
+
|
|
108
|
+
render(<UsageTab executions={[executionWithTier("exe_1", ServiceTier.FAST)]} />);
|
|
109
|
+
|
|
110
|
+
expect(screen.getByText("fast requested")).toBeTruthy();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("omits the tier chip for executions that predate the tier attribute", () => {
|
|
114
|
+
usageMock.mockReturnValue(usageWith({
|
|
115
|
+
executionBreakdown: [{
|
|
116
|
+
executionId: "exe_0",
|
|
117
|
+
resolvedModel: "claude-haiku-4.5",
|
|
118
|
+
billableCostUsd: 0.0042,
|
|
119
|
+
isEstimated: false,
|
|
120
|
+
}],
|
|
121
|
+
}));
|
|
122
|
+
|
|
123
|
+
render(<UsageTab executions={[executionWithTier("exe_0")]} />);
|
|
124
|
+
|
|
125
|
+
expect(screen.getByText(/claude-haiku-4\.5/)).toBeTruthy();
|
|
126
|
+
expect(screen.queryByText(/requested/)).toBeNull();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("renders no provenance list before any billing record lands", () => {
|
|
130
|
+
usageMock.mockReturnValue(usageWith({ executionBreakdown: [] }));
|
|
131
|
+
|
|
132
|
+
render(<UsageTab executions={[executionWithTier("exe_1", ServiceTier.STANDARD)]} />);
|
|
133
|
+
|
|
134
|
+
expect(screen.queryByRole("list", { name: "Per-execution model and tier" })).toBeNull();
|
|
135
|
+
});
|
|
136
|
+
});
|
|
@@ -53,6 +53,14 @@ function authoritativeReport(billableMicros: bigint): GetSessionUsageReportOutpu
|
|
|
53
53
|
primaryProvider: "cursor",
|
|
54
54
|
},
|
|
55
55
|
modelBreakdown: [],
|
|
56
|
+
executions: [
|
|
57
|
+
{
|
|
58
|
+
executionId: "exe_1",
|
|
59
|
+
primaryModel: "claude-sonnet-4-6",
|
|
60
|
+
billableCostMicros: billableMicros,
|
|
61
|
+
isEstimated: false,
|
|
62
|
+
},
|
|
63
|
+
],
|
|
56
64
|
isEstimated: false,
|
|
57
65
|
} as unknown as GetSessionUsageReportOutput;
|
|
58
66
|
}
|
|
@@ -71,6 +79,7 @@ function emptyReport(): GetSessionUsageReportOutput {
|
|
|
71
79
|
primaryProvider: "",
|
|
72
80
|
},
|
|
73
81
|
modelBreakdown: [],
|
|
82
|
+
executions: [],
|
|
74
83
|
isEstimated: true,
|
|
75
84
|
} as unknown as GetSessionUsageReportOutput;
|
|
76
85
|
}
|
|
@@ -122,6 +131,16 @@ describe("useSessionUsage", () => {
|
|
|
122
131
|
// 60_000 micros = $0.06 billable, replacing the $0.05 estimate.
|
|
123
132
|
expect(result.current.totalCostUsd).toBeCloseTo(0.06);
|
|
124
133
|
expect(result.current.llmCallCount).toBe(2);
|
|
134
|
+
// The #362 provenance rows ride the same report: billing-RESOLVED
|
|
135
|
+
// model per execution, never the runner's requested echo.
|
|
136
|
+
expect(result.current.executionBreakdown).toEqual([
|
|
137
|
+
{
|
|
138
|
+
executionId: "exe_1",
|
|
139
|
+
resolvedModel: "claude-sonnet-4-6",
|
|
140
|
+
billableCostUsd: 0.06,
|
|
141
|
+
isEstimated: false,
|
|
142
|
+
},
|
|
143
|
+
]);
|
|
125
144
|
});
|
|
126
145
|
|
|
127
146
|
it("polls the usage report while an execution is in progress", async () => {
|