diff --git a/.github/workflows/functional-tests.yml b/.github/workflows/functional-tests.yml index b17501960..fc7f2addf 100644 --- a/.github/workflows/functional-tests.yml +++ b/.github/workflows/functional-tests.yml @@ -99,7 +99,7 @@ jobs: echo "✓ OPENROUTER_API_KEY is configured" echo "✓ OPENROUTER_MODEL: $OPENROUTER_MODEL" fi - + - name: Run functional tests (${{ matrix.storage_mode }}) run: | cd tests/functional diff --git a/.gitignore b/.gitignore index 7ffb304c2..c2202690f 100644 --- a/.gitignore +++ b/.gitignore @@ -44,8 +44,11 @@ node_modules/ # IDE / Tooling .idea/ .vscode/ +.cursor/ *.code-workspace .opencode/ +.agit/ +.claude/worktrees/ # Generated assets coverage/ diff --git a/Makefile b/Makefile index c46de3593..68bf67804 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,21 @@ SHELL := /usr/bin/env bash .PHONY: all install build test lint fmt tidy clean control-plane sdk-go sdk-python -.PHONY: test-functional test-functional-local test-functional-postgres test-functional-cleanup test-functional-ci +.PHONY: test-functional test-functional-local test-functional-postgres test-functional-cleanup test-functional-ci log-demo-up log-demo-down log-demo-native-up log-demo-native-down + +# Local UI stack: control plane + Python/Go/TS agents emitting NDJSON process logs (see tests/functional/docker/docker-compose.log-demo.yml). +log-demo-up: + docker compose -f tests/functional/docker/docker-compose.log-demo.yml up --build -d + +log-demo-down: + docker compose -f tests/functional/docker/docker-compose.log-demo.yml down + +# Same stack on the host when Docker Desktop is not running (writable paths under /tmp/agentfield-log-demo). +log-demo-native-up: + ./scripts/run-log-demo-native.sh + +log-demo-native-down: + ./scripts/stop-log-demo-native.sh all: build diff --git a/control-plane/.env.example b/control-plane/.env.example index 48f037687..64bd5137b 100644 --- a/control-plane/.env.example +++ b/control-plane/.env.example @@ -55,6 +55,17 @@ AGENTFIELD_STORAGE_MODE=local # Agents with RequireOriginAuth=true validate this to ensure only the control plane can invoke them. # AGENTFIELD_AUTHORIZATION_INTERNAL_TOKEN=internal-secret-token +# UI → agent log proxy (GET /api/ui/v1/nodes/:nodeId/logs). Optional env overrides; otherwise YAML / DB overlay. +# AGENTFIELD_NODE_LOG_PROXY_CONNECT_TIMEOUT=10s +# AGENTFIELD_NODE_LOG_PROXY_STREAM_IDLE_TIMEOUT=2m +# AGENTFIELD_NODE_LOG_PROXY_MAX_DURATION=15m +# AGENTFIELD_NODE_LOG_MAX_TAIL_LINES=2000 + +# Python agent: process NDJSON logs (see docs/api/AGENT_NODE_LOGS.md) +# AGENTFIELD_LOGS_ENABLED=true +# AGENTFIELD_LOG_BUFFER_BYTES=4194304 +# AGENTFIELD_LOG_MAX_LINE_BYTES=16384 + # Development/Debug # GIN_MODE=debug # LOG_LEVEL=info diff --git a/control-plane/internal/cli/vc.go b/control-plane/internal/cli/vc.go index d6386eef6..8c0e4916a 100644 --- a/control-plane/internal/cli/vc.go +++ b/control-plane/internal/cli/vc.go @@ -5,7 +5,7 @@ import ( "encoding/base64" "encoding/json" "fmt" - "net/http" + "net/url" "os" "strings" "time" @@ -100,18 +100,19 @@ type VerificationMetadata struct { // VCVerificationResult represents the comprehensive verification result type VCVerificationResult struct { - Valid bool `json:"valid"` - Type string `json:"type"` - WorkflowID string `json:"workflow_id,omitempty"` - SignatureValid bool `json:"signature_valid"` - FormatValid bool `json:"format_valid"` - Message string `json:"message"` - Error string `json:"error,omitempty"` - VerifiedAt string `json:"verified_at"` - ComponentResults []ComponentVerification `json:"component_results,omitempty"` - DIDResolutions []DIDResolutionResult `json:"did_resolutions,omitempty"` - VerificationSteps []VerificationStep `json:"verification_steps,omitempty"` - Summary VerificationSummary `json:"summary"` + Valid bool `json:"valid"` + Type string `json:"type"` + WorkflowID string `json:"workflow_id,omitempty"` + SignatureValid bool `json:"signature_valid"` + FormatValid bool `json:"format_valid"` + Message string `json:"message"` + Error string `json:"error,omitempty"` + VerifiedAt string `json:"verified_at"` + ComponentResults []ComponentVerification `json:"component_results,omitempty"` + DIDResolutions []DIDResolutionResult `json:"did_resolutions,omitempty"` + VerificationSteps []VerificationStep `json:"verification_steps,omitempty"` + Summary VerificationSummary `json:"summary"` + Comprehensive *ComprehensiveVerificationResult `json:"comprehensive,omitempty"` } // ComponentVerification represents verification result for a single component @@ -158,17 +159,15 @@ type VerificationSummary struct { } func verifyVC(vcFilePath string, options VerifyOptions) error { - result := VCVerificationResult{ - VerifiedAt: time.Now().UTC().Format(time.RFC3339), - VerificationSteps: []VerificationStep{}, - DIDResolutions: []DIDResolutionResult{}, - ComponentResults: []ComponentVerification{}, - } - - // Step 1: Read and parse VC file step1 := VerificationStep{Step: 1, Description: "Reading VC file"} vcData, err := os.ReadFile(vcFilePath) if err != nil { + result := VCVerificationResult{ + VerifiedAt: time.Now().UTC().Format(time.RFC3339), + VerificationSteps: []VerificationStep{}, + DIDResolutions: []DIDResolutionResult{}, + ComponentResults: []ComponentVerification{}, + } step1.Success = false step1.Error = fmt.Sprintf("Failed to read VC file: %v", err) result.VerificationSteps = append(result.VerificationSteps, step1) @@ -178,158 +177,9 @@ func verifyVC(vcFilePath string, options VerifyOptions) error { } step1.Success = true step1.Details = fmt.Sprintf("Read %d bytes from %s", len(vcData), vcFilePath) - result.VerificationSteps = append(result.VerificationSteps, step1) - - // Step 2: Parse VC structure - step2 := VerificationStep{Step: 2, Description: "Parsing VC structure"} - var enhancedChain EnhancedVCChain - if err := json.Unmarshal(vcData, &enhancedChain); err == nil && enhancedChain.WorkflowID != "" { - normalizeEnhancedChain(&enhancedChain) - // Enhanced VC chain with DID resolution bundle - step2.Success = true - step2.Details = fmt.Sprintf("Parsed enhanced VC chain with %d execution VCs", len(enhancedChain.ExecutionVCs)) - result.Type = "workflow" - result.WorkflowID = enhancedChain.WorkflowID - result.FormatValid = true - } else { - // Try legacy format - var workflowChain types.WorkflowVCChainResponse - if err := json.Unmarshal(vcData, &workflowChain); err == nil && workflowChain.WorkflowID != "" { - step2.Success = true - step2.Details = fmt.Sprintf("Parsed legacy VC chain with %d execution VCs", len(workflowChain.ComponentVCs)) - result.Type = "workflow" - result.WorkflowID = workflowChain.WorkflowID - result.FormatValid = true - // Convert to enhanced format - enhancedChain = convertLegacyChain(workflowChain) - normalizeEnhancedChain(&enhancedChain) - } else { - step2.Success = false - step2.Error = "Invalid VC format: not a recognized AgentField VC structure" - result.VerificationSteps = append(result.VerificationSteps, step2) - result.Valid = false - result.FormatValid = false - result.Error = step2.Error - return outputResult(result, options) - } - } - result.VerificationSteps = append(result.VerificationSteps, step2) - - // Step 3: Collect unique DIDs - step3 := VerificationStep{Step: 3, Description: "Collecting unique DIDs"} - uniqueDIDs := collectUniqueDIDs(enhancedChain) - step3.Success = true - step3.Details = fmt.Sprintf("Found %d unique DIDs", len(uniqueDIDs)) - result.VerificationSteps = append(result.VerificationSteps, step3) - - // Step 4: Resolve DIDs - step4 := VerificationStep{Step: 4, Description: "Resolving DIDs"} - didResolutions := make(map[string]DIDResolutionInfo) - resolvedCount := 0 - - for _, did := range uniqueDIDs { - resolution, err := resolveDID(did, enhancedChain.DIDResolutionBundle, options) - didResult := DIDResolutionResult{ - DID: did, - Method: getDIDMethod(did), - } - - if err != nil { - didResult.Success = false - didResult.Error = err.Error() - } else { - didResult.Success = true - didResult.ResolvedFrom = resolution.ResolvedFrom - if resolution.WebURL != "" { - didResult.WebURL = resolution.WebURL - } - didResolutions[did] = resolution - resolvedCount++ - } - result.DIDResolutions = append(result.DIDResolutions, didResult) - } - - step4.Success = resolvedCount > 0 - step4.Details = fmt.Sprintf("Resolved %d/%d DIDs", resolvedCount, len(uniqueDIDs)) - if resolvedCount == 0 { - step4.Error = "Failed to resolve any DIDs" - } - result.VerificationSteps = append(result.VerificationSteps, step4) - - // Step 5: Enhanced comprehensive verification - step5 := VerificationStep{Step: 5, Description: "Performing comprehensive verification"} - - // Use the enhanced verifier for comprehensive checks - enhancedVerifier := NewEnhancedVCVerifier(didResolutions, options.Verbose) - comprehensiveResult := enhancedVerifier.VerifyEnhancedVCChain(enhancedChain) - - // Convert comprehensive result to legacy format for compatibility - validSignatures := 0 - totalSignatures := len(enhancedChain.ExecutionVCs) - if enhancedChain.WorkflowVC.VCDocument != nil { - totalSignatures++ - } - - for _, compResult := range comprehensiveResult.ComponentResults { - if compResult.SignatureValid { - validSignatures++ - } - // Convert to legacy ComponentVerification format - legacyResult := ComponentVerification{ - VCID: compResult.VCID, - ExecutionID: compResult.ExecutionID, - IssuerDID: compResult.IssuerDID, - Valid: compResult.Valid, - SignatureValid: compResult.SignatureValid, - FormatValid: compResult.FormatValid, - Status: compResult.Status, - Error: compResult.Error, - } - result.ComponentResults = append(result.ComponentResults, legacyResult) - } - - step5.Success = comprehensiveResult.Valid - step5.Details = fmt.Sprintf("Comprehensive verification completed - Score: %.1f/100", comprehensiveResult.OverallScore) - if !comprehensiveResult.Valid { - step5.Error = fmt.Sprintf("Found %d critical issues", len(comprehensiveResult.CriticalIssues)) - } - result.VerificationSteps = append(result.VerificationSteps, step5) - - // Add detailed verification information if verbose - if options.Verbose && len(comprehensiveResult.CriticalIssues) > 0 { - step6 := VerificationStep{Step: 6, Description: "Critical Issues Detected"} - step6.Success = false - details := "Critical issues found:\n" - for _, issue := range comprehensiveResult.CriticalIssues { - details += fmt.Sprintf(" - %s: %s\n", issue.Type, issue.Description) - } - step6.Details = details - result.VerificationSteps = append(result.VerificationSteps, step6) - } - - // Final result based on comprehensive verification - result.SignatureValid = comprehensiveResult.SecurityAnalysis.SecurityScore > 80.0 - result.Valid = comprehensiveResult.Valid - - if result.Valid { - result.Message = fmt.Sprintf("Workflow VC chain verified successfully (Score: %.1f/100)", comprehensiveResult.OverallScore) - } else { - result.Message = fmt.Sprintf("Workflow VC chain verification failed (Score: %.1f/100)", comprehensiveResult.OverallScore) - if len(comprehensiveResult.CriticalIssues) > 0 { - result.Error = fmt.Sprintf("%d critical issues detected", len(comprehensiveResult.CriticalIssues)) - } - } - - // Summary - result.Summary = VerificationSummary{ - TotalComponents: len(enhancedChain.ExecutionVCs), - ValidComponents: len(result.ComponentResults), - TotalDIDs: len(uniqueDIDs), - ResolvedDIDs: resolvedCount, - TotalSignatures: totalSignatures, - ValidSignatures: validSignatures, - } + result := VerifyProvenanceJSON(vcData, options) + result.VerificationSteps = append([]VerificationStep{step1}, result.VerificationSteps...) return outputResult(result, options) } @@ -363,22 +213,19 @@ func collectUniqueDIDs(chain EnhancedVCChain) []string { } func resolveDID(did string, bundle map[string]DIDResolutionInfo, options VerifyOptions) (DIDResolutionInfo, error) { - // 1. did:web always resolves from web - if strings.HasPrefix(did, "did:web:") { - return resolveWebDID(did) - } - - // 2. --resolve-web flag: fetch from web if options.ResolveWeb { - return resolveFromWeb(did, options.Resolver) + return DIDResolutionInfo{}, fmt.Errorf("web DID resolution is disabled in this CLI verifier; use bundled DID resolution instead") } - - // 3. --did-resolver: use custom endpoint if options.Resolver != "" { - return resolveFromCustom(did, options.Resolver) + return DIDResolutionInfo{}, fmt.Errorf("custom DID resolvers are disabled in this CLI verifier; use bundled DID resolution instead") + } + + // 1. did:web always resolves from web + if strings.HasPrefix(did, "did:web:") { + return resolveWebDID(did) } - // 4. Fallback: use bundled resolution + // 2. Fallback: use bundled resolution if bundle != nil { if resolution, exists := bundle[did]; exists { resolution.ResolvedFrom = "bundled" @@ -396,75 +243,18 @@ func resolveWebDID(did string) (DIDResolutionInfo, error) { return DIDResolutionInfo{}, fmt.Errorf("invalid did:web format") } - domain := parts[2] + domain, err := url.PathUnescape(parts[2]) + if err != nil { + return DIDResolutionInfo{}, fmt.Errorf("invalid did:web domain: %w", err) + } path := "/.well-known/did.json" if len(parts) > 3 { path = "/" + strings.Join(parts[3:], "/") + "/did.json" } - url := fmt.Sprintf("https://%s%s", domain, path) - - resp, err := http.Get(url) - if err != nil { - return DIDResolutionInfo{}, fmt.Errorf("failed to fetch DID document: %v", err) - } - defer resp.Body.Close() - - if resp.StatusCode != 200 { - return DIDResolutionInfo{}, fmt.Errorf("DID document not found: HTTP %d", resp.StatusCode) - } - - var didDoc map[string]interface{} - if err := json.NewDecoder(resp.Body).Decode(&didDoc); err != nil { - return DIDResolutionInfo{}, fmt.Errorf("failed to parse DID document: %v", err) - } - - // Extract public key from DID document - publicKeyJWK, err := extractPublicKeyFromDIDDoc(didDoc) - if err != nil { - return DIDResolutionInfo{}, err - } - - return DIDResolutionInfo{ - DID: did, - Method: "web", - PublicKeyJWK: publicKeyJWK, - WebURL: url, - ResolvedFrom: "web", - }, nil -} - -func resolveFromWeb(did, resolver string) (DIDResolutionInfo, error) { - // For did:key, we can resolve locally - if strings.HasPrefix(did, "did:key:") { - return resolveKeyDID(did) - } - - // For other methods, would need a universal resolver - return DIDResolutionInfo{}, fmt.Errorf("web resolution not supported for DID method: %s", getDIDMethod(did)) -} - -func resolveFromCustom(did, resolver string) (DIDResolutionInfo, error) { - url := fmt.Sprintf("%s/%s", strings.TrimSuffix(resolver, "/"), did) - - resp, err := http.Get(url) - if err != nil { - return DIDResolutionInfo{}, fmt.Errorf("failed to resolve DID: %v", err) - } - defer resp.Body.Close() - - if resp.StatusCode != 200 { - return DIDResolutionInfo{}, fmt.Errorf("DID resolution failed: HTTP %d", resp.StatusCode) - } - - var resolution DIDResolutionInfo - if err := json.NewDecoder(resp.Body).Decode(&resolution); err != nil { - return DIDResolutionInfo{}, fmt.Errorf("failed to parse resolution response: %v", err) - } - - resolution.ResolvedFrom = "custom" - return resolution, nil + didURL := fmt.Sprintf("https://%s%s", domain, path) + return DIDResolutionInfo{}, fmt.Errorf("did:web online resolution is disabled in this CLI verifier; use bundled DID resolution for %s (%s)", did, didURL) } func resolveKeyDID(did string) (DIDResolutionInfo, error) { @@ -483,25 +273,6 @@ func resolveKeyDID(did string) (DIDResolutionInfo, error) { }, fmt.Errorf("did:key resolution not fully implemented") } -func extractPublicKeyFromDIDDoc(didDoc map[string]interface{}) (map[string]interface{}, error) { - verificationMethod, ok := didDoc["verificationMethod"].([]interface{}) - if !ok || len(verificationMethod) == 0 { - return nil, fmt.Errorf("no verification methods found in DID document") - } - - firstMethod, ok := verificationMethod[0].(map[string]interface{}) - if !ok { - return nil, fmt.Errorf("invalid verification method format") - } - - publicKeyJwk, ok := firstMethod["publicKeyJwk"].(map[string]interface{}) - if !ok { - return nil, fmt.Errorf("no publicKeyJwk found in verification method") - } - - return publicKeyJwk, nil -} - //nolint:unused // Reserved for future signature verification func verifyVCSignature(vcDoc types.VCDocument, resolution DIDResolutionInfo) (bool, error) { // Create canonical representation for verification @@ -579,7 +350,7 @@ func getDIDMethod(did string) string { } func convertLegacyChain(legacy types.WorkflowVCChainResponse) EnhancedVCChain { - return EnhancedVCChain{ + chain := EnhancedVCChain{ WorkflowID: legacy.WorkflowID, GeneratedAt: time.Now().UTC().Format(time.RFC3339), TotalExecutions: len(legacy.ComponentVCs), @@ -589,6 +360,10 @@ func convertLegacyChain(legacy types.WorkflowVCChainResponse) EnhancedVCChain { ComponentVCs: legacy.ComponentVCs, WorkflowVC: legacy.WorkflowVC, } + if len(legacy.DIDResolutionBundle) > 0 { + chain.DIDResolutionBundle = mergeDIDBundle(chain.DIDResolutionBundle, legacy.DIDResolutionBundle) + } + return chain } func normalizeEnhancedChain(chain *EnhancedVCChain) { diff --git a/control-plane/internal/cli/vc_test.go b/control-plane/internal/cli/vc_test.go new file mode 100644 index 000000000..18b416c7c --- /dev/null +++ b/control-plane/internal/cli/vc_test.go @@ -0,0 +1,28 @@ +package cli + +import ( + "strings" + "testing" +) + +func TestResolveWebDIDAcceptsEncodedPort(t *testing.T) { + _, err := resolveWebDID("did:web:example.com%3A8443:agents:test-agent") + if err == nil { + t.Fatalf("expected online resolution to be disabled") + } + if strings.Contains(err.Error(), "invalid did:web domain") || strings.Contains(err.Error(), "invalid URL") { + t.Fatalf("expected encoded port to be accepted before offline rejection, got %v", err) + } +} + +func TestResolveDIDRejectsOnlineOptions(t *testing.T) { + _, err := resolveDID("did:key:test", nil, VerifyOptions{ResolveWeb: true}) + if err == nil || !strings.Contains(err.Error(), "disabled") { + t.Fatalf("expected resolve-web option to be rejected, got %v", err) + } + + _, err = resolveDID("did:key:test", nil, VerifyOptions{Resolver: "https://resolver.example"}) + if err == nil || !strings.Contains(err.Error(), "disabled") { + t.Fatalf("expected custom resolver option to be rejected, got %v", err) + } +} diff --git a/control-plane/internal/cli/verify_provenance.go b/control-plane/internal/cli/verify_provenance.go new file mode 100644 index 000000000..06b34e021 --- /dev/null +++ b/control-plane/internal/cli/verify_provenance.go @@ -0,0 +1,322 @@ +package cli + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/Agent-Field/agentfield/control-plane/pkg/types" +) + +// MaxVerifyAuditBodyBytes is the maximum JSON body size for audit-bundle verification (HTTP handlers). +const MaxVerifyAuditBodyBytes = 10 << 20 // 10 MiB + +// VerifyProvenanceJSON verifies AgentField provenance JSON: enhanced or legacy workflow chain, +// or a bare W3C VerifiableCredential document (same shape as UI "download VC document"). +// It returns a structured result suitable for APIs and CLI output; it does not write to stdout. +func VerifyProvenanceJSON(vcData []byte, options VerifyOptions) VCVerificationResult { + result := VCVerificationResult{ + VerifiedAt: time.Now().UTC().Format(time.RFC3339), + VerificationSteps: []VerificationStep{}, + DIDResolutions: []DIDResolutionResult{}, + ComponentResults: []ComponentVerification{}, + } + + step2 := VerificationStep{Step: 2, Description: "Parsing VC structure"} + var enhancedChain EnhancedVCChain + var parseKind string + + if chain, ok := tryParseEnhancedChain(vcData); ok { + enhancedChain = chain + normalizeEnhancedChain(&enhancedChain) + step2.Success = true + step2.Details = fmt.Sprintf("Parsed enhanced VC chain with %d execution VCs", len(enhancedChain.ExecutionVCs)) + parseKind = "workflow" + } else if chain, ok := tryParseLegacyWorkflowChain(vcData); ok { + enhancedChain = chain + normalizeEnhancedChain(&enhancedChain) + step2.Success = true + step2.Details = fmt.Sprintf("Parsed legacy VC chain with %d execution VCs", len(enhancedChain.ExecutionVCs)) + parseKind = "workflow" + } else if chain, ok := tryParseBareExecutionVC(vcData); ok { + enhancedChain = chain + normalizeEnhancedChain(&enhancedChain) + step2.Success = true + step2.Details = "Parsed bare W3C VerifiableCredential (execution VC document)" + parseKind = "credential" + } else { + step2.Success = false + step2.Error = "Invalid VC format: not a recognized AgentField VC structure or W3C VerifiableCredential" + result.VerificationSteps = append(result.VerificationSteps, step2) + result.Valid = false + result.FormatValid = false + result.Error = step2.Error + return result + } + result.VerificationSteps = append(result.VerificationSteps, step2) + + if parseKind == "workflow" { + result.Type = "workflow" + result.WorkflowID = enhancedChain.WorkflowID + } else { + result.Type = "credential" + if enhancedChain.WorkflowID != "" { + result.WorkflowID = enhancedChain.WorkflowID + } + } + result.FormatValid = true + + step3 := VerificationStep{Step: 3, Description: "Collecting unique DIDs"} + uniqueDIDs := collectUniqueDIDs(enhancedChain) + step3.Success = true + step3.Details = fmt.Sprintf("Found %d unique DIDs", len(uniqueDIDs)) + result.VerificationSteps = append(result.VerificationSteps, step3) + + step4 := VerificationStep{Step: 4, Description: "Resolving DIDs"} + didResolutions := make(map[string]DIDResolutionInfo) + resolvedCount := 0 + + if len(uniqueDIDs) == 0 { + step4.Success = true + step4.Details = "No DIDs to resolve" + result.VerificationSteps = append(result.VerificationSteps, step4) + } else { + for _, did := range uniqueDIDs { + resolution, err := resolveDID(did, enhancedChain.DIDResolutionBundle, options) + didResult := DIDResolutionResult{ + DID: did, + Method: getDIDMethod(did), + } + + if err != nil { + didResult.Success = false + didResult.Error = err.Error() + } else { + didResult.Success = true + didResult.ResolvedFrom = resolution.ResolvedFrom + if resolution.WebURL != "" { + didResult.WebURL = resolution.WebURL + } + didResolutions[did] = resolution + resolvedCount++ + } + result.DIDResolutions = append(result.DIDResolutions, didResult) + } + + step4.Success = resolvedCount > 0 + step4.Details = fmt.Sprintf("Resolved %d/%d DIDs", resolvedCount, len(uniqueDIDs)) + if resolvedCount == 0 { + step4.Error = "Failed to resolve any DIDs" + } + result.VerificationSteps = append(result.VerificationSteps, step4) + } + + step5 := VerificationStep{Step: 5, Description: "Performing comprehensive verification"} + enhancedVerifier := NewEnhancedVCVerifier(didResolutions, options.Verbose) + comprehensiveResult := enhancedVerifier.VerifyEnhancedVCChain(enhancedChain) + result.Comprehensive = comprehensiveResult + + validSignatures := 0 + totalSignatures := len(enhancedChain.ExecutionVCs) + if enhancedChain.WorkflowVC.VCDocument != nil { + totalSignatures++ + } + + validCount := 0 + for _, compResult := range comprehensiveResult.ComponentResults { + if compResult.SignatureValid { + validSignatures++ + } + legacyResult := ComponentVerification{ + VCID: compResult.VCID, + ExecutionID: compResult.ExecutionID, + IssuerDID: compResult.IssuerDID, + Valid: compResult.Valid, + SignatureValid: compResult.SignatureValid, + FormatValid: compResult.FormatValid, + Status: compResult.Status, + DurationMS: compResult.DurationMS, + Timestamp: compResult.Timestamp, + Error: compResult.Error, + } + result.ComponentResults = append(result.ComponentResults, legacyResult) + if compResult.Valid { + validCount++ + } + } + + step5.Success = comprehensiveResult.Valid + step5.Details = fmt.Sprintf("Comprehensive verification completed - Score: %.1f/100", comprehensiveResult.OverallScore) + if !comprehensiveResult.Valid { + step5.Error = fmt.Sprintf("Found %d critical issues", len(comprehensiveResult.CriticalIssues)) + } + result.VerificationSteps = append(result.VerificationSteps, step5) + + if options.Verbose && len(comprehensiveResult.CriticalIssues) > 0 { + step6 := VerificationStep{Step: 6, Description: "Critical Issues Detected"} + step6.Success = false + details := "Critical issues found:\n" + for _, issue := range comprehensiveResult.CriticalIssues { + details += fmt.Sprintf(" - %s: %s\n", issue.Type, issue.Description) + } + step6.Details = details + result.VerificationSteps = append(result.VerificationSteps, step6) + } + + result.SignatureValid = comprehensiveResult.SecurityAnalysis.SecurityScore > 80.0 + result.Valid = comprehensiveResult.Valid + + if result.Valid { + if parseKind == "credential" { + result.Message = fmt.Sprintf("Verifiable credential verified successfully (Score: %.1f/100)", comprehensiveResult.OverallScore) + } else { + result.Message = fmt.Sprintf("Workflow VC chain verified successfully (Score: %.1f/100)", comprehensiveResult.OverallScore) + } + } else { + if parseKind == "credential" { + result.Message = fmt.Sprintf("Verifiable credential verification failed (Score: %.1f/100)", comprehensiveResult.OverallScore) + } else { + result.Message = fmt.Sprintf("Workflow VC chain verification failed (Score: %.1f/100)", comprehensiveResult.OverallScore) + } + if len(comprehensiveResult.CriticalIssues) > 0 { + result.Error = fmt.Sprintf("%d critical issues detected", len(comprehensiveResult.CriticalIssues)) + } + } + + result.Summary = VerificationSummary{ + TotalComponents: len(enhancedChain.ExecutionVCs), + ValidComponents: validCount, + TotalDIDs: len(uniqueDIDs), + ResolvedDIDs: resolvedCount, + TotalSignatures: totalSignatures, + ValidSignatures: validSignatures, + } + + return result +} + +func tryParseEnhancedChain(vcData []byte) (EnhancedVCChain, bool) { + var enhancedChain EnhancedVCChain + if err := json.Unmarshal(vcData, &enhancedChain); err != nil { + return EnhancedVCChain{}, false + } + if enhancedChain.WorkflowID == "" { + return EnhancedVCChain{}, false + } + var raw map[string]json.RawMessage + if err := json.Unmarshal(vcData, &raw); err == nil { + if bundleRaw, ok := raw["did_resolution_bundle"]; ok && len(bundleRaw) > 0 && string(bundleRaw) != "null" { + var bundle map[string]types.DIDResolutionEntry + if err := json.Unmarshal(bundleRaw, &bundle); err == nil && len(bundle) > 0 { + enhancedChain.DIDResolutionBundle = mergeDIDBundle(enhancedChain.DIDResolutionBundle, bundle) + } + } + } + return enhancedChain, true +} + +func tryParseLegacyWorkflowChain(vcData []byte) (EnhancedVCChain, bool) { + var workflowChain types.WorkflowVCChainResponse + if err := json.Unmarshal(vcData, &workflowChain); err != nil { + return EnhancedVCChain{}, false + } + if workflowChain.WorkflowID == "" { + return EnhancedVCChain{}, false + } + return convertLegacyChain(workflowChain), true +} + +func tryParseBareExecutionVC(vcData []byte) (EnhancedVCChain, bool) { + execVC, err := executionVCFromBareDocument(json.RawMessage(vcData)) + if err != nil { + return EnhancedVCChain{}, false + } + wfID := execVC.WorkflowID + if wfID == "" { + wfID = "standalone-vc" + } + chain := EnhancedVCChain{ + WorkflowID: wfID, + GeneratedAt: time.Now().UTC().Format(time.RFC3339), + TotalExecutions: 1, + CompletedExecutions: 1, + WorkflowStatus: execVC.Status, + ExecutionVCs: []types.ExecutionVC{execVC}, + ComponentVCs: []types.ExecutionVC{execVC}, + WorkflowVC: types.WorkflowVC{}, + } + return chain, true +} + +func executionVCFromBareDocument(raw json.RawMessage) (types.ExecutionVC, error) { + var vcDoc types.VCDocument + if err := json.Unmarshal(raw, &vcDoc); err != nil { + return types.ExecutionVC{}, err + } + hasVC := false + for _, t := range vcDoc.Type { + if t == "VerifiableCredential" { + hasVC = true + break + } + } + if !hasVC { + return types.ExecutionVC{}, fmt.Errorf("missing VerifiableCredential type") + } + + sub := vcDoc.CredentialSubject + wfID := sub.WorkflowID + if wfID == "" { + wfID = "standalone-vc" + } + + createdAt := time.Now().UTC() + if vcDoc.IssuanceDate != "" { + if ts, err := time.Parse(time.RFC3339, vcDoc.IssuanceDate); err == nil { + createdAt = ts + } + } + + vcID := vcDoc.ID + if vcID == "" { + vcID = "bare-vc" + } + + return types.ExecutionVC{ + VCID: vcID, + ExecutionID: sub.ExecutionID, + WorkflowID: wfID, + SessionID: sub.SessionID, + IssuerDID: vcDoc.Issuer, + TargetDID: sub.Target.DID, + CallerDID: sub.Caller.DID, + VCDocument: raw, + Signature: vcDoc.Proof.ProofValue, + InputHash: sub.Execution.InputHash, + OutputHash: sub.Execution.OutputHash, + Status: sub.Execution.Status, + CreatedAt: createdAt, + }, nil +} + +func mergeDIDBundle( + existing map[string]DIDResolutionInfo, + entries map[string]types.DIDResolutionEntry, +) map[string]DIDResolutionInfo { + out := make(map[string]DIDResolutionInfo, len(existing)+len(entries)) + for k, v := range existing { + out[k] = v + } + for did, ent := range entries { + var jwk map[string]interface{} + _ = json.Unmarshal(ent.PublicKeyJWK, &jwk) + out[did] = DIDResolutionInfo{ + DID: did, + Method: ent.Method, + PublicKeyJWK: jwk, + ResolvedFrom: ent.ResolvedFrom, + CachedAt: ent.ResolvedAt, + } + } + return out +} diff --git a/control-plane/internal/cli/verify_provenance_test.go b/control-plane/internal/cli/verify_provenance_test.go new file mode 100644 index 000000000..0bdfd402f --- /dev/null +++ b/control-plane/internal/cli/verify_provenance_test.go @@ -0,0 +1,43 @@ +package cli + +import ( + "encoding/json" + "strings" + "testing" + + "github.com/Agent-Field/agentfield/control-plane/pkg/types" +) + +func TestVerifyProvenanceJSON_LegacyChainEmptyComponents(t *testing.T) { + legacy := types.WorkflowVCChainResponse{ + WorkflowID: "wf-empty", + ComponentVCs: []types.ExecutionVC{}, + WorkflowVC: types.WorkflowVC{WorkflowID: "wf-empty"}, + Status: "completed", + } + raw, err := json.Marshal(legacy) + if err != nil { + t.Fatal(err) + } + res := VerifyProvenanceJSON(raw, VerifyOptions{}) + if res.FormatValid != true || res.Type != "workflow" { + t.Fatalf("expected workflow parse, got type=%q formatValid=%v", res.Type, res.FormatValid) + } +} + +func TestVerifyProvenanceJSON_InvalidJSON(t *testing.T) { + res := VerifyProvenanceJSON([]byte(`not json`), VerifyOptions{}) + if res.FormatValid || res.Valid { + t.Fatalf("expected invalid") + } +} + +func TestResolveWebDID_DecodesEncodedPortInHost(t *testing.T) { + _, err := resolveWebDID("did:web:example.com%3A8443:agents:test-agent") + if err == nil { + t.Fatalf("expected network fetch to fail in test environment") + } + if strings.Contains(err.Error(), "invalid did:web domain") || strings.Contains(err.Error(), "invalid URL") { + t.Fatalf("expected encoded port to be accepted before fetch, got %v", err) + } +} diff --git a/control-plane/internal/config/config.go b/control-plane/internal/config/config.go index fd37cfbec..837f87b94 100644 --- a/control-plane/internal/config/config.go +++ b/control-plane/internal/config/config.go @@ -39,6 +39,64 @@ type AgentFieldConfig struct { ExecutionCleanup ExecutionCleanupConfig `yaml:"execution_cleanup" mapstructure:"execution_cleanup"` ExecutionQueue ExecutionQueueConfig `yaml:"execution_queue" mapstructure:"execution_queue"` Approval ApprovalConfig `yaml:"approval" mapstructure:"approval"` + NodeLogProxy NodeLogProxyConfig `yaml:"node_log_proxy" mapstructure:"node_log_proxy"` + ExecutionLogs ExecutionLogsConfig `yaml:"execution_logs" mapstructure:"execution_logs"` +} + +// NodeLogProxyConfig limits the control plane proxy to agent process logs (NDJSON). +type NodeLogProxyConfig struct { + ConnectTimeout time.Duration `yaml:"connect_timeout" mapstructure:"connect_timeout"` + StreamIdleTimeout time.Duration `yaml:"stream_idle_timeout" mapstructure:"stream_idle_timeout"` + MaxStreamDuration time.Duration `yaml:"max_stream_duration" mapstructure:"max_stream_duration"` + MaxTailLines int `yaml:"max_tail_lines" mapstructure:"max_tail_lines"` +} + +// EffectiveNodeLogProxy returns proxy settings with defaults for zero values. +func EffectiveNodeLogProxy(c NodeLogProxyConfig) NodeLogProxyConfig { + out := c + if out.ConnectTimeout <= 0 { + out.ConnectTimeout = 5 * time.Second + } + if out.StreamIdleTimeout <= 0 { + out.StreamIdleTimeout = 60 * time.Second + } + if out.MaxStreamDuration <= 0 { + out.MaxStreamDuration = 15 * time.Minute + } + if out.MaxTailLines <= 0 { + out.MaxTailLines = 10000 + } + return out +} + +// ExecutionLogsConfig governs structured execution-correlated logs stored by the control plane. +type ExecutionLogsConfig struct { + RetentionPeriod time.Duration `yaml:"retention_period" mapstructure:"retention_period"` + MaxEntriesPerExecution int `yaml:"max_entries_per_execution" mapstructure:"max_entries_per_execution"` + MaxTailEntries int `yaml:"max_tail_entries" mapstructure:"max_tail_entries"` + StreamIdleTimeout time.Duration `yaml:"stream_idle_timeout" mapstructure:"stream_idle_timeout"` + MaxStreamDuration time.Duration `yaml:"max_stream_duration" mapstructure:"max_stream_duration"` +} + +// EffectiveExecutionLogs returns execution-log settings with defaults for zero values. +func EffectiveExecutionLogs(c ExecutionLogsConfig) ExecutionLogsConfig { + out := c + if out.RetentionPeriod <= 0 { + out.RetentionPeriod = 24 * time.Hour + } + if out.MaxEntriesPerExecution <= 0 { + out.MaxEntriesPerExecution = 5000 + } + if out.MaxTailEntries <= 0 { + out.MaxTailEntries = 1000 + } + if out.StreamIdleTimeout <= 0 { + out.StreamIdleTimeout = 60 * time.Second + } + if out.MaxStreamDuration <= 0 { + out.MaxStreamDuration = 15 * time.Minute + } + return out } // ApprovalConfig holds configuration for the execution approval workflow. @@ -392,6 +450,55 @@ func ApplyEnvOverrides(cfg *Config) { cfg.Features.DID.Authorization.InternalToken = val } + // Node log proxy (UI → agent NDJSON) + if val := os.Getenv("AGENTFIELD_NODE_LOG_PROXY_CONNECT_TIMEOUT"); val != "" { + if d, err := time.ParseDuration(val); err == nil { + cfg.AgentField.NodeLogProxy.ConnectTimeout = d + } + } + if val := os.Getenv("AGENTFIELD_NODE_LOG_PROXY_STREAM_IDLE_TIMEOUT"); val != "" { + if d, err := time.ParseDuration(val); err == nil { + cfg.AgentField.NodeLogProxy.StreamIdleTimeout = d + } + } + if val := os.Getenv("AGENTFIELD_NODE_LOG_PROXY_MAX_DURATION"); val != "" { + if d, err := time.ParseDuration(val); err == nil { + cfg.AgentField.NodeLogProxy.MaxStreamDuration = d + } + } + if val := os.Getenv("AGENTFIELD_NODE_LOG_MAX_TAIL_LINES"); val != "" { + if i, err := strconv.Atoi(val); err == nil { + cfg.AgentField.NodeLogProxy.MaxTailLines = i + } + } + + // Structured execution log storage and streaming + if val := os.Getenv("AGENTFIELD_EXECUTION_LOG_RETENTION_PERIOD"); val != "" { + if d, err := time.ParseDuration(val); err == nil { + cfg.AgentField.ExecutionLogs.RetentionPeriod = d + } + } + if val := os.Getenv("AGENTFIELD_EXECUTION_LOG_MAX_ENTRIES_PER_EXECUTION"); val != "" { + if i, err := strconv.Atoi(val); err == nil { + cfg.AgentField.ExecutionLogs.MaxEntriesPerExecution = i + } + } + if val := os.Getenv("AGENTFIELD_EXECUTION_LOG_MAX_TAIL_ENTRIES"); val != "" { + if i, err := strconv.Atoi(val); err == nil { + cfg.AgentField.ExecutionLogs.MaxTailEntries = i + } + } + if val := os.Getenv("AGENTFIELD_EXECUTION_LOG_STREAM_IDLE_TIMEOUT"); val != "" { + if d, err := time.ParseDuration(val); err == nil { + cfg.AgentField.ExecutionLogs.StreamIdleTimeout = d + } + } + if val := os.Getenv("AGENTFIELD_EXECUTION_LOG_MAX_DURATION"); val != "" { + if d, err := time.ParseDuration(val); err == nil { + cfg.AgentField.ExecutionLogs.MaxStreamDuration = d + } + } + // Approval workflow overrides if val := os.Getenv("AGENTFIELD_APPROVAL_WEBHOOK_SECRET"); val != "" { cfg.AgentField.Approval.WebhookSecret = val diff --git a/control-plane/internal/handlers/did_handlers.go b/control-plane/internal/handlers/did_handlers.go index 778dfbf43..98118bee0 100644 --- a/control-plane/internal/handlers/did_handlers.go +++ b/control-plane/internal/handlers/did_handlers.go @@ -172,6 +172,13 @@ func (h *DIDHandlers) VerifyVC(c *gin.Context) { c.JSON(http.StatusOK, response) } +// VerifyAuditBundle verifies exported provenance JSON (same logic as `af vc verify`). +// POST /api/v1/did/verify-audit +// Query: resolve_web=true, did_resolver=, verbose=true +func (h *DIDHandlers) VerifyAuditBundle(c *gin.Context) { + HandleVerifyAuditBundle(c) +} + // GetWorkflowVCChain handles workflow VC chain requests. // GET /api/v1/did/workflow/:workflow_id/vc-chain func (h *DIDHandlers) GetWorkflowVCChain(c *gin.Context) { @@ -528,6 +535,7 @@ func (h *DIDHandlers) RegisterRoutes(router *gin.RouterGroup) { didGroup.POST("/register", h.RegisterAgent) didGroup.GET("/resolve/:did", h.ResolveDID) didGroup.POST("/verify", h.VerifyVC) + didGroup.POST("/verify-audit", h.VerifyAuditBundle) didGroup.GET("/workflow/:workflow_id/vc-chain", h.GetWorkflowVCChain) didGroup.POST("/workflow/:workflow_id/vc", h.CreateWorkflowVC) didGroup.GET("/status", h.GetDIDStatus) diff --git a/control-plane/internal/handlers/execute_test.go b/control-plane/internal/handlers/execute_test.go index 1b94ed380..84e408aa9 100644 --- a/control-plane/internal/handlers/execute_test.go +++ b/control-plane/internal/handlers/execute_test.go @@ -12,6 +12,7 @@ import ( "testing" "time" + "github.com/Agent-Field/agentfield/control-plane/internal/events" "github.com/Agent-Field/agentfield/control-plane/pkg/types" "github.com/gin-gonic/gin" @@ -86,6 +87,15 @@ func (m *MockStorageProvider) StoreWorkflowExecutionEvent(ctx context.Context, e func (m *MockStorageProvider) ListWorkflowExecutionEvents(ctx context.Context, executionID string, afterSeq *int64, limit int) ([]*types.WorkflowExecutionEvent, error) { return nil, nil } +func (m *MockStorageProvider) StoreExecutionLogEntry(ctx context.Context, entry *types.ExecutionLogEntry) error { + return nil +} +func (m *MockStorageProvider) ListExecutionLogEntries(ctx context.Context, executionID string, afterSeq *int64, limit int, levels []string, nodeIDs []string, sources []string, query string) ([]*types.ExecutionLogEntry, error) { + return nil, nil +} +func (m *MockStorageProvider) PruneExecutionLogEntries(ctx context.Context, executionID string, maxEntries int, olderThan time.Time) error { + return nil +} func (m *MockStorageProvider) StoreWorkflowRunEvent(ctx context.Context, event *types.WorkflowRunEvent) error { return nil } @@ -119,6 +129,18 @@ func (m *MockStorageProvider) QueryWorkflows(ctx context.Context, filters types. func (m *MockStorageProvider) CreateOrUpdateSession(ctx context.Context, session *types.Session) error { return nil } +func (m *MockStorageProvider) GetExecutionEventBus() *events.ExecutionEventBus { + return events.NewExecutionEventBus() +} +func (m *MockStorageProvider) GetWorkflowExecutionEventBus() *events.EventBus[*types.WorkflowExecutionEvent] { + return events.NewEventBus[*types.WorkflowExecutionEvent]() +} +func (m *MockStorageProvider) GetExecutionLogEventBus() *events.EventBus[*types.ExecutionLogEntry] { + return events.NewEventBus[*types.ExecutionLogEntry]() +} +func (m *MockStorageProvider) GetWorkflowRunEventBus() *events.EventBus[*types.WorkflowRunEvent] { + return events.NewEventBus[*types.WorkflowRunEvent]() +} func (m *MockStorageProvider) GetSession(ctx context.Context, sessionID string) (*types.Session, error) { return nil, nil } diff --git a/control-plane/internal/handlers/execution_logs.go b/control-plane/internal/handlers/execution_logs.go new file mode 100644 index 000000000..6ff18ac5c --- /dev/null +++ b/control-plane/internal/handlers/execution_logs.go @@ -0,0 +1,155 @@ +package handlers + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "strings" + "time" + + "github.com/Agent-Field/agentfield/control-plane/internal/config" + "github.com/Agent-Field/agentfield/control-plane/pkg/types" + "github.com/gin-gonic/gin" +) + +type executionLogEnvelope struct { + Entries []types.ExecutionLogEntry `json:"entries"` +} + +// ExecutionLogStore defines the storage needed for structured execution log ingestion. +type ExecutionLogStore interface { + StoreExecutionLogEntry(ctx context.Context, entry *types.ExecutionLogEntry) error + PruneExecutionLogEntries(ctx context.Context, executionID string, maxEntries int, olderThan time.Time) error +} + +type executionLogBatchStore interface { + StoreExecutionLogEntries(ctx context.Context, executionID string, entries []*types.ExecutionLogEntry) error +} + +// StructuredExecutionLogsHandler ingests structured execution logs emitted by SDK runtimes. +// POST /api/v1/executions/:execution_id/logs +func StructuredExecutionLogsHandler(store ExecutionLogStore, snapshot func() config.ExecutionLogsConfig) gin.HandlerFunc { + return func(c *gin.Context) { + executionID := strings.TrimSpace(c.Param("execution_id")) + if executionID == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "execution_id is required"}) + return + } + + const maxBodyBytes int64 = 10 << 20 // 10 MiB + c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, maxBodyBytes) + body, err := io.ReadAll(c.Request.Body) + if err != nil { + var maxBytesErr *http.MaxBytesError + if errors.As(err, &maxBytesErr) { + c.JSON(http.StatusRequestEntityTooLarge, gin.H{"error": "request body too large", "max_bytes": maxBodyBytes}) + } else { + c.JSON(http.StatusBadRequest, gin.H{"error": "failed to read body"}) + } + return + } + if len(body) == 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "request body is required"}) + return + } + + entries, err := decodeExecutionLogEntries(body) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("invalid payload: %v", err)}) + return + } + if len(entries) == 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "at least one execution log entry is required"}) + return + } + + cfg := config.EffectiveExecutionLogs(snapshot()) + ctx := c.Request.Context() + pruneBefore := time.Time{} + if cfg.RetentionPeriod > 0 { + pruneBefore = time.Now().UTC().Add(-cfg.RetentionPeriod) + } + + prepared := make([]types.ExecutionLogEntry, 0, len(entries)) + for i := range entries { + entry := entries[i] + entry.ExecutionID = strings.TrimSpace(entry.ExecutionID) + if entry.ExecutionID == "" { + entry.ExecutionID = executionID + } + if entry.ExecutionID != executionID { + c.JSON(http.StatusBadRequest, gin.H{ + "error": "execution_id_mismatch", + "path_execution": executionID, + "body_execution": entry.ExecutionID, + }) + return + } + if strings.TrimSpace(entry.Message) == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "message is required"}) + return + } + if strings.TrimSpace(entry.AgentNodeID) == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "agent_node_id is required"}) + return + } + if strings.TrimSpace(entry.WorkflowID) == "" { + entry.WorkflowID = executionID + } + if entry.RootWorkflowID == nil || strings.TrimSpace(*entry.RootWorkflowID) == "" { + rootWorkflowID := entry.WorkflowID + entry.RootWorkflowID = &rootWorkflowID + } + if len(entry.Attributes) == 0 { + entry.Attributes = json.RawMessage("{}") + } + + prepared = append(prepared, entry) + } + + if batchStore, ok := store.(executionLogBatchStore); ok { + ptrs := make([]*types.ExecutionLogEntry, 0, len(prepared)) + for i := range prepared { + ptrs = append(ptrs, &prepared[i]) + } + if err := batchStore.StoreExecutionLogEntries(ctx, executionID, ptrs); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to store execution log entry batch: %v", err)}) + return + } + } else { + for i := range prepared { + if err := store.StoreExecutionLogEntry(ctx, &prepared[i]); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to store execution log entry: %v", err)}) + return + } + } + } + + if err := store.PruneExecutionLogEntries(ctx, executionID, cfg.MaxEntriesPerExecution, pruneBefore); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to prune execution logs: %v", err)}) + return + } + + c.JSON(http.StatusAccepted, gin.H{ + "success": true, + "execution_id": executionID, + "accepted": len(prepared), + }) + } +} + +func decodeExecutionLogEntries(body []byte) ([]types.ExecutionLogEntry, error) { + var envelope executionLogEnvelope + if err := json.Unmarshal(body, &envelope); err == nil && len(envelope.Entries) > 0 { + return envelope.Entries, nil + } + + var single types.ExecutionLogEntry + if err := json.Unmarshal(body, &single); err != nil { + return nil, err + } + return []types.ExecutionLogEntry{single}, nil +} diff --git a/control-plane/internal/handlers/execution_logs_test.go b/control-plane/internal/handlers/execution_logs_test.go new file mode 100644 index 000000000..782894821 --- /dev/null +++ b/control-plane/internal/handlers/execution_logs_test.go @@ -0,0 +1,100 @@ +package handlers + +import ( + "bytes" + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/Agent-Field/agentfield/control-plane/internal/config" + "github.com/Agent-Field/agentfield/control-plane/pkg/types" + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestStructuredExecutionLogsHandlerAcceptsSingleAndBatch(t *testing.T) { + gin.SetMode(gin.TestMode) + store := newTestExecutionStorage(&types.AgentNode{ID: "node-1"}) + handler := StructuredExecutionLogsHandler(store, func() config.ExecutionLogsConfig { + return config.ExecutionLogsConfig{ + MaxEntriesPerExecution: 100, + RetentionPeriod: 24 * time.Hour, + } + }) + + router := gin.New() + router.POST("/api/v1/executions/:execution_id/logs", handler) + + single := types.ExecutionLogEntry{ + ExecutionID: "exec-1", + WorkflowID: "wf-1", + AgentNodeID: "node-1", + Level: "info", + Source: "sdk.typescript", + Message: "single message", + Attributes: json.RawMessage(`{"mode":"single"}`), + EmittedAt: time.Now().UTC(), + } + body, err := json.Marshal(single) + require.NoError(t, err) + + req := httptest.NewRequest(http.MethodPost, "/api/v1/executions/exec-1/logs", bytes.NewReader(body)) + req.Header.Set("Content-Type", "application/json") + resp := httptest.NewRecorder() + router.ServeHTTP(resp, req) + require.Equal(t, http.StatusAccepted, resp.Code) + + batch := map[string]any{ + "entries": []map[string]any{ + { + "execution_id": "exec-1", + "workflow_id": "wf-1", + "agent_node_id": "node-1", + "level": "warn", + "source": "sdk.runtime", + "message": "batch message", + "ts": time.Now().UTC().Format(time.RFC3339), + }, + }, + } + body, err = json.Marshal(batch) + require.NoError(t, err) + + req = httptest.NewRequest(http.MethodPost, "/api/v1/executions/exec-1/logs", bytes.NewReader(body)) + req.Header.Set("Content-Type", "application/json") + resp = httptest.NewRecorder() + router.ServeHTTP(resp, req) + require.Equal(t, http.StatusAccepted, resp.Code) + + entries, err := store.ListExecutionLogEntries(context.Background(), "exec-1", nil, 10, nil, nil, nil, "") + require.NoError(t, err) + require.Len(t, entries, 2) + assert.Equal(t, "single message", entries[0].Message) + assert.Equal(t, "batch message", entries[1].Message) + assert.Equal(t, int64(1), entries[0].Sequence) + assert.Equal(t, int64(2), entries[1].Sequence) +} + +func TestStructuredExecutionLogsHandlerRejectsPathMismatch(t *testing.T) { + gin.SetMode(gin.TestMode) + store := newTestExecutionStorage(&types.AgentNode{ID: "node-1"}) + handler := StructuredExecutionLogsHandler(store, func() config.ExecutionLogsConfig { + return config.ExecutionLogsConfig{MaxEntriesPerExecution: 100} + }) + + router := gin.New() + router.POST("/api/v1/executions/:execution_id/logs", handler) + + body := `{"execution_id":"exec-2","workflow_id":"wf-1","agent_node_id":"node-1","level":"info","source":"sdk.typescript","message":"oops"}` + req := httptest.NewRequest(http.MethodPost, "/api/v1/executions/exec-1/logs", bytes.NewBufferString(body)) + req.Header.Set("Content-Type", "application/json") + resp := httptest.NewRecorder() + router.ServeHTTP(resp, req) + + require.Equal(t, http.StatusBadRequest, resp.Code) + assert.Contains(t, resp.Body.String(), "execution_id_mismatch") +} diff --git a/control-plane/internal/handlers/test_helpers_test.go b/control-plane/internal/handlers/test_helpers_test.go index c9b2320c2..a0698b039 100644 --- a/control-plane/internal/handlers/test_helpers_test.go +++ b/control-plane/internal/handlers/test_helpers_test.go @@ -3,7 +3,9 @@ package handlers import ( "context" "fmt" + "strings" "sync" + "time" "github.com/Agent-Field/agentfield/control-plane/internal/events" "github.com/Agent-Field/agentfield/control-plane/pkg/types" @@ -14,11 +16,13 @@ type testExecutionStorage struct { agent *types.AgentNode workflowExecutions map[string]*types.WorkflowExecution executionRecords map[string]*types.Execution + executionLogs map[string][]*types.ExecutionLogEntry runs map[string]*types.WorkflowRun steps map[string]*types.WorkflowStep webhooks map[string]*types.ExecutionWebhook eventBus *events.ExecutionEventBus workflowExecutionEventBus *events.EventBus[*types.WorkflowExecutionEvent] + executionLogEventBus *events.EventBus[*types.ExecutionLogEntry] workflowRunEventBus *events.EventBus[*types.WorkflowRunEvent] updateCh chan string } @@ -28,11 +32,13 @@ func newTestExecutionStorage(agent *types.AgentNode) *testExecutionStorage { agent: agent, workflowExecutions: make(map[string]*types.WorkflowExecution), executionRecords: make(map[string]*types.Execution), + executionLogs: make(map[string][]*types.ExecutionLogEntry), runs: make(map[string]*types.WorkflowRun), steps: make(map[string]*types.WorkflowStep), webhooks: make(map[string]*types.ExecutionWebhook), eventBus: events.NewExecutionEventBus(), workflowExecutionEventBus: events.NewEventBus[*types.WorkflowExecutionEvent](), + executionLogEventBus: events.NewEventBus[*types.ExecutionLogEntry](), workflowRunEventBus: events.NewEventBus[*types.WorkflowRunEvent](), updateCh: make(chan string, 10), } @@ -198,6 +204,10 @@ func (s *testExecutionStorage) GetWorkflowExecutionEventBus() *events.EventBus[* return s.workflowExecutionEventBus } +func (s *testExecutionStorage) GetExecutionLogEventBus() *events.EventBus[*types.ExecutionLogEntry] { + return s.executionLogEventBus +} + func (s *testExecutionStorage) GetWorkflowRunEventBus() *events.EventBus[*types.WorkflowRunEvent] { return s.workflowRunEventBus } @@ -282,6 +292,84 @@ func (s *testExecutionStorage) StoreWorkflowExecutionEvent(ctx context.Context, return nil } +func (s *testExecutionStorage) StoreExecutionLogEntry(ctx context.Context, entry *types.ExecutionLogEntry) error { + s.mu.Lock() + defer s.mu.Unlock() + + if entry == nil { + return fmt.Errorf("execution log entry cannot be nil") + } + + copy := *entry + copy.Sequence = int64(len(s.executionLogs[copy.ExecutionID]) + 1) + s.executionLogs[copy.ExecutionID] = append(s.executionLogs[copy.ExecutionID], ©) + s.executionLogEventBus.Publish(©) + return nil +} + +func (s *testExecutionStorage) ListExecutionLogEntries(ctx context.Context, executionID string, afterSeq *int64, limit int, levels []string, nodeIDs []string, sources []string, query string) ([]*types.ExecutionLogEntry, error) { + s.mu.Lock() + defer s.mu.Unlock() + + matchesString := func(needle string, haystack []string) bool { + if len(haystack) == 0 { + return true + } + for _, value := range haystack { + if value == needle { + return true + } + } + return false + } + + out := make([]*types.ExecutionLogEntry, 0) + for _, entry := range s.executionLogs[executionID] { + if afterSeq != nil && entry.Sequence <= *afterSeq { + continue + } + if !matchesString(entry.Level, levels) { + continue + } + if !matchesString(entry.AgentNodeID, nodeIDs) { + continue + } + if !matchesString(entry.Source, sources) { + continue + } + if trimmed := strings.TrimSpace(query); trimmed != "" && + !strings.Contains(entry.Message, trimmed) && + !strings.Contains(string(entry.Attributes), trimmed) { + continue + } + copy := *entry + out = append(out, ©) + } + if limit > 0 && len(out) > limit { + out = out[:limit] + } + return out, nil +} + +func (s *testExecutionStorage) PruneExecutionLogEntries(ctx context.Context, executionID string, maxEntries int, olderThan time.Time) error { + s.mu.Lock() + defer s.mu.Unlock() + + current := s.executionLogs[executionID] + filtered := make([]*types.ExecutionLogEntry, 0, len(current)) + for _, entry := range current { + if !olderThan.IsZero() && entry.EmittedAt.Before(olderThan) { + continue + } + filtered = append(filtered, entry) + } + if maxEntries > 0 && len(filtered) > maxEntries { + filtered = filtered[len(filtered)-maxEntries:] + } + s.executionLogs[executionID] = filtered + return nil +} + func (s *testExecutionStorage) QueryExecutionRecords(ctx context.Context, filter types.ExecutionFilter) ([]*types.Execution, error) { s.mu.Lock() defer s.mu.Unlock() diff --git a/control-plane/internal/handlers/ui/config_test.go b/control-plane/internal/handlers/ui/config_test.go index 0ef0de28d..c1f69173a 100644 --- a/control-plane/internal/handlers/ui/config_test.go +++ b/control-plane/internal/handlers/ui/config_test.go @@ -448,6 +448,32 @@ func (m *MockStorageProvider) GetExecutionEventBus() *events.ExecutionEventBus { return args.Get(0).(*events.ExecutionEventBus) } +func (m *MockStorageProvider) StoreExecutionLogEntry(ctx context.Context, entry *types.ExecutionLogEntry) error { + args := m.Called(ctx, entry) + return args.Error(0) +} + +func (m *MockStorageProvider) ListExecutionLogEntries(ctx context.Context, executionID string, afterSeq *int64, limit int, levels []string, nodeIDs []string, sources []string, query string) ([]*types.ExecutionLogEntry, error) { + args := m.Called(ctx, executionID, afterSeq, limit, levels, nodeIDs, sources, query) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).([]*types.ExecutionLogEntry), args.Error(1) +} + +func (m *MockStorageProvider) PruneExecutionLogEntries(ctx context.Context, executionID string, maxEntries int, olderThan time.Time) error { + args := m.Called(ctx, executionID, maxEntries, olderThan) + return args.Error(0) +} + +func (m *MockStorageProvider) GetExecutionLogEventBus() *events.EventBus[*types.ExecutionLogEntry] { + args := m.Called() + if args.Get(0) == nil { + return nil + } + return args.Get(0).(*events.EventBus[*types.ExecutionLogEntry]) +} + // DID Registry operations func (m *MockStorageProvider) StoreDID(ctx context.Context, did string, didDocument, publicKey, privateKeyRef, derivationPath string) error { args := m.Called(ctx, did, didDocument, publicKey, privateKeyRef, derivationPath) diff --git a/control-plane/internal/handlers/ui/did.go b/control-plane/internal/handlers/ui/did.go index 3ae8df932..02da3e5e8 100644 --- a/control-plane/internal/handlers/ui/did.go +++ b/control-plane/internal/handlers/ui/did.go @@ -8,6 +8,7 @@ import ( "strings" "time" + "github.com/Agent-Field/agentfield/control-plane/internal/handlers" "github.com/Agent-Field/agentfield/control-plane/internal/services" "github.com/Agent-Field/agentfield/control-plane/internal/storage" "github.com/Agent-Field/agentfield/control-plane/pkg/types" @@ -218,12 +219,8 @@ func (h *DIDHandler) GetExecutionVCStatusHandler(c *gin.Context) { return } - // DEBUG: Log the execution ID being requested - fmt.Printf("DEBUG: GetExecutionVCStatusHandler called for execution_id: %s\n", executionID) - // If VC service is not available, return empty response if h.vcService == nil { - fmt.Printf("DEBUG: VC service is nil for execution_id: %s\n", executionID) c.JSON(http.StatusOK, gin.H{ "has_vc": false, "status": "none", @@ -234,7 +231,6 @@ func (h *DIDHandler) GetExecutionVCStatusHandler(c *gin.Context) { executionVC, err := h.vcService.GetExecutionVCByExecutionID(executionID) if err != nil { - fmt.Printf("DEBUG: Execution VC lookup failed for %s: %v\n", executionID, err) c.JSON(http.StatusOK, gin.H{ "has_vc": false, "status": "none", @@ -243,16 +239,12 @@ func (h *DIDHandler) GetExecutionVCStatusHandler(c *gin.Context) { return } - fmt.Printf("DEBUG: Found VC for execution_id %s: vc_id=%s, status=%s, vc_document_type=%T\n", - executionID, executionVC.VCID, executionVC.Status, executionVC.VCDocument) - var vcDocumentForResponse interface{} documentStatus := executionVC.Status if len(executionVC.VCDocument) > 0 { var parsed interface{} if err := json.Unmarshal(executionVC.VCDocument, &parsed); err != nil { - fmt.Printf("DEBUG: VC document parsing failed for %s: %v\n", executionID, err) vcDocumentForResponse = map[string]interface{}{ "parse_error": true, "error": err.Error(), @@ -262,7 +254,6 @@ func (h *DIDHandler) GetExecutionVCStatusHandler(c *gin.Context) { documentStatus = "malformed" } else { vcDocumentForResponse = parsed - fmt.Printf("DEBUG: VC document is valid JSON (%d bytes)\n", len(executionVC.VCDocument)) } } else if executionVC.StorageURI != "" { vcDocumentForResponse = map[string]interface{}{ @@ -300,26 +291,20 @@ func (h *DIDHandler) GetExecutionVCHandler(c *gin.Context) { return } - // DEBUG: Log the execution ID being requested - fmt.Printf("DEBUG: GetExecutionVCHandler called for execution_id: %s\n", executionID) - // If VC service is not available, return error if h.vcService == nil { - fmt.Printf("DEBUG: VC service is nil for execution_id: %s\n", executionID) c.JSON(http.StatusServiceUnavailable, gin.H{"error": "VC service not available"}) return } executionVC, err := h.vcService.GetExecutionVCByExecutionID(executionID) if err != nil { - fmt.Printf("DEBUG: No VC found for execution_id: %s (err=%v)\n", executionID, err) c.JSON(http.StatusNotFound, gin.H{"error": "VC not found for this execution"}) return } if len(executionVC.VCDocument) == 0 { if executionVC.StorageURI == "" { - fmt.Printf("DEBUG: VC document is empty for execution_id: %s\n", executionID) c.JSON(http.StatusNotFound, gin.H{"error": "VC document not found or empty"}) return } @@ -480,6 +465,13 @@ func (h *DIDHandler) VerifyVCHandler(c *gin.Context) { c.JSON(http.StatusOK, response) } +// VerifyAuditBundleHandler verifies exported provenance JSON (workflow chain, enhanced export, or bare W3C VC). +// POST /api/ui/v1/did/verify-audit +// Query: resolve_web=true, did_resolver=, verbose=true +func (h *DIDHandler) VerifyAuditBundleHandler(c *gin.Context) { + handlers.HandleVerifyAuditBundle(c) +} + // VerifyExecutionVCComprehensiveHandler handles requests for comprehensive VC verification. // POST /api/ui/v1/executions/:executionId/verify-vc func (h *DIDHandler) VerifyExecutionVCComprehensiveHandler(c *gin.Context) { diff --git a/control-plane/internal/handlers/ui/execution_logs.go b/control-plane/internal/handlers/ui/execution_logs.go index f5b8b304a..a2b7d5a39 100644 --- a/control-plane/internal/handlers/ui/execution_logs.go +++ b/control-plane/internal/handlers/ui/execution_logs.go @@ -1,94 +1,192 @@ package ui import ( + "context" "encoding/json" + "errors" "fmt" "net/http" + "strconv" + "strings" "time" - "github.com/Agent-Field/agentfield/control-plane/internal/events" + "github.com/Agent-Field/agentfield/control-plane/internal/config" "github.com/Agent-Field/agentfield/control-plane/internal/handlers" "github.com/Agent-Field/agentfield/control-plane/internal/logger" "github.com/Agent-Field/agentfield/control-plane/internal/services" + "github.com/Agent-Field/agentfield/control-plane/internal/storage" + "github.com/Agent-Field/agentfield/control-plane/pkg/types" "github.com/gin-gonic/gin" ) -// ExecutionLogsHandler handles real-time execution log streaming. +// ExecutionLogsHandler handles live and recent structured execution logs. type ExecutionLogsHandler struct { + storage storage.StorageProvider llmHealthMonitor *services.LLMHealthMonitor + Snapshot func() config.ExecutionLogsConfig } // NewExecutionLogsHandler creates a new ExecutionLogsHandler. -func NewExecutionLogsHandler(llmHealthMonitor *services.LLMHealthMonitor) *ExecutionLogsHandler { +func NewExecutionLogsHandler(store storage.StorageProvider, llmHealthMonitor *services.LLMHealthMonitor, snapshot func() config.ExecutionLogsConfig) *ExecutionLogsHandler { return &ExecutionLogsHandler{ + storage: store, llmHealthMonitor: llmHealthMonitor, + Snapshot: snapshot, } } -// GetExecutionQueueStatusHandler returns concurrency slot usage per agent and overall queue health. -// GET /api/ui/v1/executions/queue -func (h *ExecutionLogsHandler) GetExecutionQueueStatusHandler(c *gin.Context) { - limiter := handlers.GetConcurrencyLimiter() - maxPerAgent := 0 - counts := map[string]int64{} - if limiter != nil { - maxPerAgent = limiter.MaxPerAgent() - counts = limiter.GetAllCounts() - } +type executionLogsResponse struct { + Entries []types.ExecutionLogEntry `json:"entries"` +} - type agentSlot struct { - AgentNodeID string `json:"agent_node_id"` - Running int64 `json:"running"` - Max int `json:"max"` - Available int `json:"available"` +func parseCSVQuery(value string) []string { + if strings.TrimSpace(value) == "" { + return nil } - - agents := make([]agentSlot, 0, len(counts)) - totalRunning := int64(0) - for agentID, running := range counts { - avail := maxPerAgent - int(running) - if avail < 0 { - avail = 0 + raw := strings.Split(value, ",") + out := make([]string, 0, len(raw)) + for _, item := range raw { + if trimmed := strings.TrimSpace(item); trimmed != "" { + out = append(out, trimmed) } - agents = append(agents, agentSlot{ - AgentNodeID: agentID, - Running: running, - Max: maxPerAgent, - Available: avail, - }) - totalRunning += running } + return out +} - c.JSON(http.StatusOK, gin.H{ - "enabled": maxPerAgent > 0, - "max_per_agent": maxPerAgent, - "total_running": totalRunning, - "agents": agents, - "checked_at": time.Now().Format(time.RFC3339), - }) +// GetExecutionLogsHandler returns a recent or filtered tail of structured execution logs. +// GET /api/ui/v1/executions/:execution_id/logs +func (h *ExecutionLogsHandler) GetExecutionLogsHandler(c *gin.Context) { + if h.storage == nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "not_configured"}) + return + } + executionID := strings.TrimSpace(c.Param("execution_id")) + if executionID == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "execution_id is required"}) + return + } + maxTail := config.EffectiveExecutionLogs(h.Snapshot()).MaxTailEntries + limit := maxTail + if raw := strings.TrimSpace(c.Query("tail")); raw != "" { + n, err := strconv.Atoi(raw) + if err != nil || n < 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid_tail"}) + return + } + if n > maxTail { + c.JSON(http.StatusBadRequest, gin.H{"error": "tail_too_large", "max_allowed": maxTail}) + return + } + limit = n + } + var afterSeq *int64 + if raw := strings.TrimSpace(c.Query("after_seq")); raw != "" { + n, err := strconv.ParseInt(raw, 10, 64) + if err != nil || n < 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid_after_seq"}) + return + } + afterSeq = &n + } + entries, err := h.storage.ListExecutionLogEntries( + c.Request.Context(), + executionID, + afterSeq, + limit, + parseCSVQuery(c.Query("levels")), + parseCSVQuery(c.Query("node_ids")), + parseCSVQuery(c.Query("sources")), + c.Query("q"), + ) + if err != nil { + logger.Logger.Error().Err(err).Str("execution_id", executionID).Msg("failed to list execution logs") + c.JSON(http.StatusInternalServerError, gin.H{"error": "failed_to_list_execution_logs"}) + return + } + response := executionLogsResponse{Entries: make([]types.ExecutionLogEntry, 0, len(entries))} + for _, entry := range entries { + if entry != nil { + response.Entries = append(response.Entries, *entry) + } + } + c.JSON(http.StatusOK, response) } -// StreamExecutionLogsHandler streams real-time log events for a specific execution via SSE. +// StreamExecutionLogsHandler streams structured execution logs for a specific execution via SSE. // GET /api/ui/v1/executions/:executionId/logs/stream func (h *ExecutionLogsHandler) StreamExecutionLogsHandler(c *gin.Context) { - executionID := c.Param("execution_id") + if h.storage == nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "not_configured"}) + return + } + executionID := strings.TrimSpace(c.Param("execution_id")) if executionID == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "executionId is required"}) return } - // Set headers for SSE + cfg := config.EffectiveExecutionLogs(h.Snapshot()) + maxTail := cfg.MaxTailEntries + tail := 0 + if raw := strings.TrimSpace(c.Query("tail")); raw != "" { + n, err := strconv.Atoi(raw) + if err != nil || n < 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid_tail"}) + return + } + if n > maxTail { + c.JSON(http.StatusBadRequest, gin.H{"error": "tail_too_large", "max_allowed": maxTail}) + return + } + tail = n + } + var sinceSeq *int64 + if raw := strings.TrimSpace(c.Query("since_seq")); raw != "" { + n, err := strconv.ParseInt(raw, 10, 64) + if err != nil || n < 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid_since_seq"}) + return + } + sinceSeq = &n + } + c.Header("Content-Type", "text/event-stream") c.Header("Cache-Control", "no-cache") c.Header("Connection", "keep-alive") c.Header("X-Accel-Buffering", "no") subscriberID := fmt.Sprintf("exec_logs_%s_%d_%s", executionID, time.Now().UnixNano(), c.ClientIP()) + eventChan := h.storage.GetExecutionLogEventBus().Subscribe(subscriberID) + defer h.storage.GetExecutionLogEventBus().Unsubscribe(subscriberID) - eventChan := events.GlobalExecutionEventBus.Subscribe(subscriberID) - defer events.GlobalExecutionEventBus.Unsubscribe(subscriberID) + if initial, err := h.storage.ListExecutionLogEntries( + c.Request.Context(), + executionID, + sinceSeq, + tail, + parseCSVQuery(c.Query("levels")), + parseCSVQuery(c.Query("node_ids")), + parseCSVQuery(c.Query("sources")), + c.Query("q"), + ); err == nil { + for _, entry := range initial { + if entry == nil { + continue + } + payload, marshalErr := json.Marshal(entry) + if marshalErr != nil { + continue + } + if !writeSSE(c, payload) { + return + } + if sinceSeq == nil || entry.Sequence > *sinceSeq { + next := entry.Sequence + sinceSeq = &next + } + } + } - // Send initial connection confirmation initialEvent := map[string]interface{}{ "type": "connected", "execution_id": executionID, @@ -101,33 +199,63 @@ func (h *ExecutionLogsHandler) StreamExecutionLogsHandler(c *gin.Context) { } } - ctx := c.Request.Context() + streamCtx := c.Request.Context() + if cfg.MaxStreamDuration > 0 { + var cancel context.CancelFunc + streamCtx, cancel = context.WithTimeout(c.Request.Context(), cfg.MaxStreamDuration) + defer cancel() + } + heartbeatTicker := time.NewTicker(30 * time.Second) defer heartbeatTicker.Stop() + idleTimer := time.NewTimer(cfg.StreamIdleTimeout) + defer idleTimer.Stop() - logger.Logger.Debug(). - Str("execution_id", executionID). - Str("subscriber", subscriberID). - Msg("Execution log SSE client connected") + // Check if execution is already in a terminal state before streaming. + if exec, err := h.storage.GetWorkflowExecution(c.Request.Context(), executionID); err == nil && exec != nil { + if types.IsTerminalExecutionStatus(exec.Status) { + closeEvt, _ := json.Marshal(map[string]interface{}{ + "type": "stream_end", "reason": "terminal_status", "status": exec.Status, + }) + writeSSE(c, closeEvt) + return + } + } for { select { - case event := <-eventChan: - // Filter to only events for this execution - if event.ExecutionID != executionID { + case entry, ok := <-eventChan: + if !ok { + return + } + if entry == nil || entry.ExecutionID != executionID { continue } - - eventData, err := json.Marshal(event) + if sinceSeq != nil && entry.Sequence <= *sinceSeq { + continue + } + payload, err := json.Marshal(entry) if err != nil { - logger.Logger.Error().Err(err).Msg("Error marshalling execution event") + logger.Logger.Error().Err(err).Msg("error marshalling execution log entry") continue } - if !writeSSE(c, eventData) { + if !writeSSE(c, payload) { return } - + next := entry.Sequence + sinceSeq = &next + resetTimer(idleTimer, cfg.StreamIdleTimeout) case <-heartbeatTicker.C: + // Check if execution reached terminal state; close stream if so. + if exec, err := h.storage.GetWorkflowExecution(streamCtx, executionID); err == nil && exec != nil { + if types.IsTerminalExecutionStatus(exec.Status) { + closeEvt, _ := json.Marshal(map[string]interface{}{ + "type": "stream_end", "reason": "terminal_status", "status": exec.Status, + }) + writeSSE(c, closeEvt) + return + } + } heartbeat := map[string]interface{}{ "type": "heartbeat", "timestamp": time.Now().Format(time.RFC3339), @@ -137,16 +265,74 @@ func (h *ExecutionLogsHandler) StreamExecutionLogsHandler(c *gin.Context) { return } } - - case <-ctx.Done(): - logger.Logger.Debug(). - Str("execution_id", executionID). - Msg("Execution log SSE client disconnected") + // Don't reset idle timer on heartbeats — only real log entries should. + case <-idleTimer.C: + return + case <-streamCtx.Done(): + if !errors.Is(streamCtx.Err(), http.ErrAbortHandler) { + logger.Logger.Debug().Str("execution_id", executionID).Msg("execution log SSE client disconnected") + } return } } } +func resetTimer(timer *time.Timer, d time.Duration) { + if d <= 0 { + return + } + if !timer.Stop() { + select { + case <-timer.C: + default: + } + } + timer.Reset(d) +} + +// GetExecutionQueueStatusHandler returns concurrency slot usage per agent and overall queue health. +// GET /api/ui/v1/executions/queue +func (h *ExecutionLogsHandler) GetExecutionQueueStatusHandler(c *gin.Context) { + limiter := handlers.GetConcurrencyLimiter() + maxPerAgent := 0 + counts := map[string]int64{} + if limiter != nil { + maxPerAgent = limiter.MaxPerAgent() + counts = limiter.GetAllCounts() + } + + type agentSlot struct { + AgentNodeID string `json:"agent_node_id"` + Running int64 `json:"running"` + Max int `json:"max"` + Available int `json:"available"` + } + + agents := make([]agentSlot, 0, len(counts)) + totalRunning := int64(0) + for agentID, running := range counts { + avail := maxPerAgent - int(running) + if avail < 0 { + avail = 0 + } + agents = append(agents, agentSlot{ + AgentNodeID: agentID, + Running: running, + Max: maxPerAgent, + Available: avail, + }) + totalRunning += running + } + + c.JSON(http.StatusOK, gin.H{ + "enabled": maxPerAgent > 0, + "max_per_agent": maxPerAgent, + "total_running": totalRunning, + "agents": agents, + "checked_at": time.Now().Format(time.RFC3339), + }) +} + // GetLLMHealthHandler returns the health status of all configured LLM endpoints. // GET /api/ui/v1/llm/health func (h *ExecutionLogsHandler) GetLLMHealthHandler(c *gin.Context) { @@ -162,9 +348,9 @@ func (h *ExecutionLogsHandler) GetLLMHealthHandler(c *gin.Context) { anyHealthy := h.llmHealthMonitor.IsAnyEndpointHealthy() c.JSON(http.StatusOK, gin.H{ - "enabled": true, - "healthy": anyHealthy, - "endpoints": statuses, - "checked_at": time.Now().Format(time.RFC3339), + "enabled": true, + "healthy": anyHealthy, + "endpoints": statuses, + "checked_at": time.Now().Format(time.RFC3339), }) } diff --git a/control-plane/internal/handlers/ui/executions.go b/control-plane/internal/handlers/ui/executions.go index 5bdf3b24e..2a37efc73 100644 --- a/control-plane/internal/handlers/ui/executions.go +++ b/control-plane/internal/handlers/ui/executions.go @@ -190,6 +190,11 @@ type ExecutionDetailsResponse struct { LatestNote *types.ExecutionNote `json:"latest_note,omitempty"` WebhookRegistered bool `json:"webhook_registered"` WebhookEvents []*types.ExecutionWebhookEvent `json:"webhook_events,omitempty"` + // Provenance (from execution_vcs when DID/VC is enabled for this execution) + CallerDID *string `json:"caller_did,omitempty"` + TargetDID *string `json:"target_did,omitempty"` + InputHash *string `json:"input_hash,omitempty"` + OutputHash *string `json:"output_hash,omitempty"` } type EnhancedExecution struct { @@ -626,6 +631,18 @@ func (h *ExecutionHandler) StreamExecutionEventsHandler(c *gin.Context) { eventChan := eventBus.Subscribe(subscriberID) defer eventBus.Unsubscribe(subscriberID) + // Send an initial connected event so the browser EventSource detects the open. + connectedEvt := map[string]interface{}{ + "type": "connected", + "message": "Execution events stream connected", + "timestamp": time.Now().Format(time.RFC3339), + } + if payload, err := json.Marshal(connectedEvt); err == nil { + if !writeSSE(c, payload) { + return + } + } + ctx := c.Request.Context() ticker := time.NewTicker(30 * time.Second) defer ticker.Stop() @@ -711,6 +728,15 @@ func (h *ExecutionHandler) toExecutionDetails(ctx context.Context, exec *types.E webhookRegistered := exec.WebhookRegistered webhookEvents := exec.WebhookEvents + notes := exec.Notes + if notes == nil { + notes = []types.ExecutionNote{} + } + var latestNote *types.ExecutionNote + if n := len(notes); n > 0 { + latestNote = ¬es[n-1] + } + resp := ExecutionDetailsResponse{ ID: 0, ExecutionID: exec.ExecutionID, @@ -738,9 +764,9 @@ func (h *ExecutionHandler) toExecutionDetails(ctx context.Context, exec *types.E RetryCount: 0, CreatedAt: exec.StartedAt.Format(time.RFC3339), UpdatedAt: &updated, - Notes: nil, - NotesCount: 0, - LatestNote: nil, + Notes: notes, + NotesCount: len(notes), + LatestNote: latestNote, WebhookRegistered: webhookRegistered, WebhookEvents: webhookEvents, } @@ -761,6 +787,27 @@ func (h *ExecutionHandler) toExecutionDetails(ctx context.Context, exec *types.E resp.ApprovalRespondedAt = &formatted } } + + execID := exec.ExecutionID + if vcs, err := h.storage.ListExecutionVCs(ctx, types.VCFilters{ExecutionID: &execID, Limit: 1}); err == nil && len(vcs) > 0 && vcs[0] != nil { + vc := vcs[0] + if t := strings.TrimSpace(vc.CallerDID); t != "" { + v := t + resp.CallerDID = &v + } + if t := strings.TrimSpace(vc.TargetDID); t != "" { + v := t + resp.TargetDID = &v + } + if t := strings.TrimSpace(vc.InputHash); t != "" { + v := t + resp.InputHash = &v + } + if t := strings.TrimSpace(vc.OutputHash); t != "" { + v := t + resp.OutputHash = &v + } + } } return resp diff --git a/control-plane/internal/handlers/ui/node_log_settings.go b/control-plane/internal/handlers/ui/node_log_settings.go new file mode 100644 index 000000000..0e363ac5f --- /dev/null +++ b/control-plane/internal/handlers/ui/node_log_settings.go @@ -0,0 +1,194 @@ +package ui + +import ( + "context" + "net/http" + "os" + "time" + + "github.com/Agent-Field/agentfield/control-plane/internal/config" + "github.com/Agent-Field/agentfield/control-plane/internal/storage" + "github.com/gin-gonic/gin" + "gopkg.in/yaml.v3" +) + +const agentfieldYAMLConfigKey = "agentfield.yaml" + +// NodeLogSettingsHandler reads/updates node log proxy limits (DB + runtime config). +type NodeLogSettingsHandler struct { + Storage storage.StorageProvider + ReadConfig func(func(*config.Config)) + WriteConfig func(func(*config.Config)) +} + +type nodeLogProxyJSON struct { + ConnectTimeout string `json:"connect_timeout"` + StreamIdleTimeout string `json:"stream_idle_timeout"` + MaxStreamDuration string `json:"max_stream_duration"` + MaxTailLines int `json:"max_tail_lines"` +} + +func envLocksNodeLogProxy() map[string]bool { + return map[string]bool{ + "connect_timeout": os.Getenv("AGENTFIELD_NODE_LOG_PROXY_CONNECT_TIMEOUT") != "", + "stream_idle_timeout": os.Getenv("AGENTFIELD_NODE_LOG_PROXY_STREAM_IDLE_TIMEOUT") != "", + "max_stream_duration": os.Getenv("AGENTFIELD_NODE_LOG_PROXY_MAX_DURATION") != "", + "max_tail_lines": os.Getenv("AGENTFIELD_NODE_LOG_MAX_TAIL_LINES") != "", + } +} + +// GetNodeLogProxySettingsHandler GET /api/ui/v1/settings/node-log-proxy +func (h *NodeLogSettingsHandler) GetNodeLogProxySettingsHandler(c *gin.Context) { + if h.ReadConfig == nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "not_configured"}) + return + } + var eff config.NodeLogProxyConfig + h.ReadConfig(func(cfg *config.Config) { + eff = config.EffectiveNodeLogProxy(cfg.AgentField.NodeLogProxy) + }) + c.JSON(http.StatusOK, gin.H{ + "effective": gin.H{ + "connect_timeout": eff.ConnectTimeout.String(), + "stream_idle_timeout": eff.StreamIdleTimeout.String(), + "max_stream_duration": eff.MaxStreamDuration.String(), + "max_tail_lines": eff.MaxTailLines, + }, + "env_locks": envLocksNodeLogProxy(), + }) +} + +// PutNodeLogProxySettingsHandler PUT /api/ui/v1/settings/node-log-proxy +func (h *NodeLogSettingsHandler) PutNodeLogProxySettingsHandler(c *gin.Context) { + if h.Storage == nil || h.WriteConfig == nil || h.ReadConfig == nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "not_configured"}) + return + } + locks := envLocksNodeLogProxy() + for k, v := range locks { + if v { + c.JSON(http.StatusConflict, gin.H{ + "error": "env_locked", + "message": "Clear environment override for " + k + " before editing from UI", + "locks": locks, + }) + return + } + } + + var body nodeLogProxyJSON + if err := c.ShouldBindJSON(&body); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid_json", "message": err.Error()}) + return + } + + next := config.NodeLogProxyConfig{} + if body.ConnectTimeout != "" { + d, err := time.ParseDuration(body.ConnectTimeout) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid_connect_timeout"}) + return + } + next.ConnectTimeout = d + } + if body.StreamIdleTimeout != "" { + d, err := time.ParseDuration(body.StreamIdleTimeout) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid_stream_idle_timeout"}) + return + } + next.StreamIdleTimeout = d + } + if body.MaxStreamDuration != "" { + d, err := time.ParseDuration(body.MaxStreamDuration) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid_max_stream_duration"}) + return + } + next.MaxStreamDuration = d + } + if body.MaxTailLines > 0 { + next.MaxTailLines = body.MaxTailLines + } + if next.ConnectTimeout == 0 && next.StreamIdleTimeout == 0 && next.MaxStreamDuration == 0 && next.MaxTailLines == 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "no_fields", "message": "Provide at least one field to update"}) + return + } + + ctx := c.Request.Context() + if err := persistNodeLogProxyOverlay(ctx, h.Storage, next); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "persist_failed", "message": err.Error()}) + return + } + + h.WriteConfig(func(cfg *config.Config) { + if next.ConnectTimeout > 0 { + cfg.AgentField.NodeLogProxy.ConnectTimeout = next.ConnectTimeout + } + if next.StreamIdleTimeout > 0 { + cfg.AgentField.NodeLogProxy.StreamIdleTimeout = next.StreamIdleTimeout + } + if next.MaxStreamDuration > 0 { + cfg.AgentField.NodeLogProxy.MaxStreamDuration = next.MaxStreamDuration + } + if next.MaxTailLines > 0 { + cfg.AgentField.NodeLogProxy.MaxTailLines = next.MaxTailLines + } + }) + + var eff config.NodeLogProxyConfig + h.ReadConfig(func(cfg *config.Config) { + eff = config.EffectiveNodeLogProxy(cfg.AgentField.NodeLogProxy) + }) + c.JSON(http.StatusOK, gin.H{ + "effective": gin.H{ + "connect_timeout": eff.ConnectTimeout.String(), + "stream_idle_timeout": eff.StreamIdleTimeout.String(), + "max_stream_duration": eff.MaxStreamDuration.String(), + "max_tail_lines": eff.MaxTailLines, + }, + }) +} + +func persistNodeLogProxyOverlay(ctx context.Context, st storage.StorageProvider, patch config.NodeLogProxyConfig) error { + var root map[string]interface{} + entry, err := st.GetConfig(ctx, agentfieldYAMLConfigKey) + if err != nil { + return err + } + if entry != nil && entry.Value != "" { + if err := yaml.Unmarshal([]byte(entry.Value), &root); err != nil { + return err + } + } + if root == nil { + root = make(map[string]interface{}) + } + af, _ := root["agentfield"].(map[string]interface{}) + if af == nil { + af = make(map[string]interface{}) + root["agentfield"] = af + } + nlp, _ := af["node_log_proxy"].(map[string]interface{}) + if nlp == nil { + nlp = make(map[string]interface{}) + af["node_log_proxy"] = nlp + } + if patch.ConnectTimeout > 0 { + nlp["connect_timeout"] = patch.ConnectTimeout.String() + } + if patch.StreamIdleTimeout > 0 { + nlp["stream_idle_timeout"] = patch.StreamIdleTimeout.String() + } + if patch.MaxStreamDuration > 0 { + nlp["max_stream_duration"] = patch.MaxStreamDuration.String() + } + if patch.MaxTailLines > 0 { + nlp["max_tail_lines"] = patch.MaxTailLines + } + out, err := yaml.Marshal(root) + if err != nil { + return err + } + return st.SetConfig(ctx, agentfieldYAMLConfigKey, string(out), "ui") +} diff --git a/control-plane/internal/handlers/ui/node_logs.go b/control-plane/internal/handlers/ui/node_logs.go new file mode 100644 index 000000000..3e0979716 --- /dev/null +++ b/control-plane/internal/handlers/ui/node_logs.go @@ -0,0 +1,222 @@ +package ui + +import ( + "bufio" + "context" + "errors" + "io" + "net" + "net/http" + "net/url" + "strconv" + "strings" + "time" + + "github.com/Agent-Field/agentfield/control-plane/internal/config" + "github.com/Agent-Field/agentfield/control-plane/internal/logger" + "github.com/Agent-Field/agentfield/control-plane/internal/storage" + "github.com/gin-gonic/gin" +) + +const agentProcessLogsPath = "/agentfield/v1/logs" + +// NodeLogsProxyHandler proxies UI requests to an agent node's NDJSON log API. +type NodeLogsProxyHandler struct { + Storage storage.StorageProvider + // Snapshot returns effective proxy limits and the internal bearer token for upstream calls. + Snapshot func() (proxy config.NodeLogProxyConfig, internalToken string) +} + +// ProxyNodeLogsHandler is GET /api/ui/v1/nodes/:nodeId/logs +func (h *NodeLogsProxyHandler) ProxyNodeLogsHandler(c *gin.Context) { + if h.Snapshot == nil || h.Storage == nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "proxy_not_configured"}) + return + } + nodeID := c.Param("nodeId") + if nodeID == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "nodeId is required"}) + return + } + ctx := c.Request.Context() + agent, err := h.Storage.GetAgent(ctx, nodeID) + if err != nil || agent == nil { + c.JSON(http.StatusNotFound, gin.H{"error": "node not found"}) + return + } + base := strings.TrimSpace(agent.BaseURL) + if base == "" { + c.JSON(http.StatusBadGateway, gin.H{"error": "agent_unreachable", "message": "node has no base_url"}) + return + } + u, err := url.Parse(base) + if err != nil || (u.Scheme != "http" && u.Scheme != "https") { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid_base_url"}) + return + } + + proxyCfg, token := h.Snapshot() + q := c.Request.URL.Query() + if tl := q.Get("tail_lines"); tl != "" { + n, err := strconv.Atoi(tl) + if err != nil || n < 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid_tail_lines"}) + return + } + if n > proxyCfg.MaxTailLines { + c.JSON(http.StatusBadRequest, gin.H{ + "error": "tail_too_large", + "message": "tail_lines exceeds server maximum", + "max_allowed": proxyCfg.MaxTailLines, + }) + return + } + } + + upstream := strings.TrimSuffix(base, "/") + agentProcessLogsPath + if raw := c.Request.URL.RawQuery; raw != "" { + upstream += "?" + raw + } + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, upstream, nil) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "upstream_build_failed"}) + return + } + if token != "" { + req.Header.Set("Authorization", "Bearer "+token) + } + req.Header.Set("Accept", "application/x-ndjson, application/json") + + dialer := &net.Dialer{Timeout: proxyCfg.ConnectTimeout} + transport := &http.Transport{ + DialContext: func(dctx context.Context, network, addr string) (net.Conn, error) { + dctx2, cancel := context.WithTimeout(dctx, proxyCfg.ConnectTimeout) + defer cancel() + return dialer.DialContext(dctx2, network, addr) + }, + } + client := &http.Client{Transport: transport, Timeout: 0} + + streamCtx := ctx + var cancel context.CancelFunc + if strings.EqualFold(q.Get("follow"), "1") || strings.EqualFold(q.Get("follow"), "true") || q.Get("follow") == "yes" { + streamCtx, cancel = context.WithTimeout(ctx, proxyCfg.MaxStreamDuration) + defer cancel() + req = req.WithContext(streamCtx) + } + + resp, err := client.Do(req) + if err != nil { + var netErr net.Error + if errors.As(err, &netErr) && netErr.Timeout() { + c.JSON(http.StatusGatewayTimeout, gin.H{"error": "agent_timeout"}) + return + } + if errors.Is(err, context.DeadlineExceeded) { + c.JSON(http.StatusGatewayTimeout, gin.H{"error": "agent_timeout"}) + return + } + logger.Logger.Debug().Err(err).Str("node_id", nodeID).Msg("node logs proxy upstream error") + c.JSON(http.StatusBadGateway, gin.H{"error": "agent_unreachable", "message": err.Error()}) + return + } + defer resp.Body.Close() + + if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusNotFound || resp.StatusCode == http.StatusRequestEntityTooLarge { + c.Status(resp.StatusCode) + _, _ = io.Copy(c.Writer, resp.Body) + return + } + if resp.StatusCode != http.StatusOK { + c.JSON(http.StatusBadGateway, gin.H{"error": "agent_bad_response", "status": resp.StatusCode}) + return + } + + c.Header("Content-Type", "application/x-ndjson") + c.Header("Cache-Control", "no-store") + c.Header("X-Content-Type-Options", "nosniff") + c.Status(http.StatusOK) + + flusher, _ := c.Writer.(http.Flusher) + // Idle timeout between chunks while streaming + idle := proxyCfg.StreamIdleTimeout + if idle <= 0 { + idle = 60 * time.Second + } + + if strings.EqualFold(q.Get("follow"), "1") || strings.EqualFold(q.Get("follow"), "true") || q.Get("follow") == "yes" { + err = streamNDJSONWithIdle(c.Writer, resp.Body, idle, flusher, streamCtx) + } else { + _, err = io.Copy(c.Writer, resp.Body) + if flusher != nil { + flusher.Flush() + } + } + if err != nil && !errors.Is(err, context.Canceled) { + logger.Logger.Debug().Err(err).Str("node_id", nodeID).Msg("node logs proxy copy ended") + } +} + +func streamNDJSONWithIdle(w io.Writer, r io.Reader, idle time.Duration, flusher http.Flusher, ctx context.Context) error { + type result struct { + line []byte + err error + } + ch := make(chan result, 8) + go func() { + defer close(ch) + sc := bufio.NewScanner(r) + const maxLine = 2 << 20 // 2 MiB per line safety cap + sc.Buffer(make([]byte, 0, 64*1024), maxLine) + for sc.Scan() { + b := append([]byte(nil), sc.Bytes()...) + b = append(b, '\n') + select { + case ch <- result{line: b}: + case <-ctx.Done(): + return + } + } + if err := sc.Err(); err != nil { + select { + case ch <- result{err: err}: + case <-ctx.Done(): + } + } + }() + + timer := time.NewTimer(idle) + defer timer.Stop() + for { + select { + case <-ctx.Done(): + return ctx.Err() + case <-timer.C: + return context.DeadlineExceeded + case res, ok := <-ch: + if !ok { + return nil + } + if res.err != nil { + return res.err + } + if len(res.line) == 0 { + continue + } + if _, err := w.Write(res.line); err != nil { + return err + } + if flusher != nil { + flusher.Flush() + } + if !timer.Stop() { + select { + case <-timer.C: + default: + } + } + timer.Reset(idle) + } + } +} diff --git a/control-plane/internal/handlers/verify_audit.go b/control-plane/internal/handlers/verify_audit.go new file mode 100644 index 000000000..2a4fec831 --- /dev/null +++ b/control-plane/internal/handlers/verify_audit.go @@ -0,0 +1,47 @@ +package handlers + +import ( + "errors" + "io" + "net/http" + + "github.com/gin-gonic/gin" + + afcli "github.com/Agent-Field/agentfield/control-plane/internal/cli" +) + +// HandleVerifyAuditBundle reads, validates, and verifies an exported provenance JSON body. +// Shared by both the public API and UI handlers. +func HandleVerifyAuditBundle(c *gin.Context) { + c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, afcli.MaxVerifyAuditBodyBytes) + body, err := io.ReadAll(c.Request.Body) + if err != nil { + var maxBytesErr *http.MaxBytesError + if errors.As(err, &maxBytesErr) { + c.JSON(http.StatusRequestEntityTooLarge, gin.H{"error": "request body too large", "max_bytes": afcli.MaxVerifyAuditBodyBytes}) + } else { + c.JSON(http.StatusBadRequest, gin.H{"error": "failed to read request body"}) + } + return + } + if len(body) == 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "empty body"}) + return + } + if c.Query("resolve_web") == "true" || c.Query("did_resolver") != "" { + c.JSON(http.StatusBadRequest, gin.H{ + "error": "remote DID resolution is only supported in the CLI; upload a bundle with embedded DID data for HTTP verification", + }) + return + } + opts := afcli.VerifyOptions{ + OutputFormat: "json", + Verbose: c.Query("verbose") == "true", + } + result := afcli.VerifyProvenanceJSON(body, opts) + if !result.FormatValid { + c.JSON(http.StatusUnprocessableEntity, result) + return + } + c.JSON(http.StatusOK, result) +} diff --git a/control-plane/internal/handlers/verify_audit_test.go b/control-plane/internal/handlers/verify_audit_test.go new file mode 100644 index 000000000..a525f3c4e --- /dev/null +++ b/control-plane/internal/handlers/verify_audit_test.go @@ -0,0 +1,34 @@ +package handlers + +import ( + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/gin-gonic/gin" +) + +func TestHandleVerifyAuditBundle_RejectsRemoteResolutionParams(t *testing.T) { + gin.SetMode(gin.TestMode) + + router := gin.New() + router.POST("/verify", HandleVerifyAuditBundle) + + req := httptest.NewRequest( + http.MethodPost, + "/verify?resolve_web=true&did_resolver=https://resolver.example", + strings.NewReader(`{"workflow_id":"wf-1","workflow_vc":{"workflow_id":"wf-1"}}`), + ) + req.Header.Set("Content-Type", "application/json") + rec := httptest.NewRecorder() + + router.ServeHTTP(rec, req) + + if rec.Code != http.StatusBadRequest { + t.Fatalf("expected 400, got %d body=%s", rec.Code, rec.Body.String()) + } + if !strings.Contains(rec.Body.String(), "remote DID resolution is only supported in the CLI") { + t.Fatalf("unexpected body: %s", rec.Body.String()) + } +} diff --git a/control-plane/internal/handlers/webhook_approval.go b/control-plane/internal/handlers/webhook_approval.go index 978c72fc6..d3b82c7c1 100644 --- a/control-plane/internal/handlers/webhook_approval.go +++ b/control-plane/internal/handlers/webhook_approval.go @@ -197,8 +197,14 @@ func (c *webhookApprovalController) handleApprovalWebhook(ctx *gin.Context) { // Idempotency: if execution is no longer in waiting state, it was already // processed by a previous webhook delivery. Return 200 so the sender // (hax-sdk retry queue) considers it delivered and stops retrying. + // Exception: if the stale reaper marked it as "timeout" but the approval + // is still pending, allow the approval to proceed (the user is explicitly + // resolving the approval via the UI or webhook). normalized := types.NormalizeExecutionStatus(wfExec.Status) - if normalized != types.ExecutionStatusWaiting { + approvalStillPending := wfExec.ApprovalStatus != nil && *wfExec.ApprovalStatus == "pending" + reaperTimeout := normalized == string(types.ExecutionStatusTimeout) && approvalStillPending + + if normalized != types.ExecutionStatusWaiting && !reaperTimeout { logger.Logger.Info(). Str("execution_id", executionID). Str("current_status", normalized). diff --git a/control-plane/internal/handlers/workflow_dag.go b/control-plane/internal/handlers/workflow_dag.go index af9b2f4a8..2b41ec3a6 100644 --- a/control-plane/internal/handlers/workflow_dag.go +++ b/control-plane/internal/handlers/workflow_dag.go @@ -14,13 +14,8 @@ import ( "github.com/gin-gonic/gin" ) -type executionRecordProvider interface { - QueryExecutionRecords(ctx context.Context, filter types.ExecutionFilter) ([]*types.Execution, error) - GetExecutionRecord(ctx context.Context, executionID string) (*types.Execution, error) -} - type executionGraphService struct { - store executionRecordProvider + store storage.StorageProvider } func newExecutionGraphService(storageProvider storage.StorageProvider) *executionGraphService { @@ -77,6 +72,23 @@ type WorkflowDAGLightweightNode struct { WorkflowDepth int `json:"workflow_depth"` } +// WebhookRunSummary aggregates callback delivery attempts for a workflow run (UI strip). +type WebhookRunSummary struct { + StepsWithWebhook int `json:"steps_with_webhook"` + TotalDeliveries int `json:"total_deliveries"` + FailedDeliveries int `json:"failed_deliveries"` +} + +// WebhookFailurePreview is one execution whose latest failed webhook attempt is shown for run-level retry UX. +type WebhookFailurePreview struct { + ExecutionID string `json:"execution_id"` + AgentNodeID string `json:"agent_node_id,omitempty"` + ReasonerID string `json:"reasoner_id,omitempty"` + EventType string `json:"event_type,omitempty"` + HTTPStatus *int `json:"http_status,omitempty"` + CreatedAt string `json:"created_at,omitempty"` +} + type WorkflowDAGLightweightResponse struct { RootWorkflowID string `json:"root_workflow_id"` WorkflowStatus string `json:"workflow_status"` @@ -87,6 +99,13 @@ type WorkflowDAGLightweightResponse struct { MaxDepth int `json:"max_depth"` Timeline []WorkflowDAGLightweightNode `json:"timeline"` Mode string `json:"mode"` + // UniqueAgentNodeIDs lists distinct agent node IDs participating in this run (nodes strip). + UniqueAgentNodeIDs []string `json:"unique_agent_node_ids,omitempty"` + // WorkflowIssuerDID is the issuer DID from the newest execution VC for this workflow, when VC data exists. + WorkflowIssuerDID *string `json:"workflow_issuer_did,omitempty"` + WebhookSummary *WebhookRunSummary `json:"webhook_summary,omitempty"` + // WebhookFailures lists executions with a failed delivery (latest failure per execution), capped for the run strip. + WebhookFailures []WebhookFailurePreview `json:"webhook_failures,omitempty"` } func GetWorkflowDAGHandler(storageProvider storage.StorageProvider) gin.HandlerFunc { @@ -118,16 +137,21 @@ func (s *executionGraphService) handleGetWorkflowDAG(c *gin.Context) { if isLightweightRequest(c) { timeline, workflowStatus, workflowName, sessionID, actorID, maxDepth := buildLightweightExecutionDAG(executions) + wh := aggregateWebhookRunData(ctx, s.store, executions) response := WorkflowDAGLightweightResponse{ - RootWorkflowID: runID, - WorkflowStatus: workflowStatus, - WorkflowName: workflowName, - SessionID: sessionID, - ActorID: actorID, - TotalNodes: len(executions), - MaxDepth: maxDepth, - Timeline: timeline, - Mode: "lightweight", + RootWorkflowID: runID, + WorkflowStatus: workflowStatus, + WorkflowName: workflowName, + SessionID: sessionID, + ActorID: actorID, + TotalNodes: len(executions), + MaxDepth: maxDepth, + Timeline: timeline, + Mode: "lightweight", + UniqueAgentNodeIDs: collectUniqueAgentNodeIDs(executions), + WorkflowIssuerDID: lookupWorkflowIssuerDID(ctx, s.store, runID), + WebhookSummary: wh.summary, + WebhookFailures: wh.failures, } c.JSON(http.StatusOK, response) @@ -266,6 +290,148 @@ func (s *executionGraphService) loadRunExecutions(ctx context.Context, runID str return s.store.QueryExecutionRecords(ctx, filter) } +func collectUniqueAgentNodeIDs(executions []*types.Execution) []string { + seen := make(map[string]struct{}) + out := make([]string, 0) + for _, e := range executions { + if e == nil { + continue + } + id := strings.TrimSpace(e.AgentNodeID) + if id == "" { + continue + } + if _, ok := seen[id]; ok { + continue + } + seen[id] = struct{}{} + out = append(out, id) + } + return out +} + +const maxWebhookFailurePreviews = 20 + +type webhookRunAggregates struct { + summary *WebhookRunSummary + failures []WebhookFailurePreview +} + +// aggregateWebhookRunData always returns a non-nil summary so UIs can show an explicit +// “no webhooks” state. Failures are optional (latest failed attempt per execution). +func aggregateWebhookRunData(ctx context.Context, store storage.StorageProvider, executions []*types.Execution) webhookRunAggregates { + empty := &WebhookRunSummary{} + if len(executions) == 0 { + return webhookRunAggregates{summary: empty, failures: nil} + } + ids := make([]string, 0, len(executions)) + execByID := make(map[string]*types.Execution, len(executions)) + for _, e := range executions { + if e == nil { + continue + } + ids = append(ids, e.ExecutionID) + execByID[e.ExecutionID] = e + } + reg, err := store.ListExecutionWebhooksRegistered(ctx, ids) + if err != nil { + return webhookRunAggregates{summary: empty, failures: nil} + } + evMap, err := store.ListExecutionWebhookEventsBatch(ctx, ids) + if err != nil { + evMap = nil + } + steps := 0 + for _, ok := range reg { + if ok { + steps++ + } + } + total := 0 + failed := 0 + for _, evs := range evMap { + total += len(evs) + for _, ev := range evs { + if ev == nil { + continue + } + st := strings.ToLower(strings.TrimSpace(ev.Status)) + if st == "failed" { + failed++ + } + } + } + failures := buildWebhookFailurePreviews(execByID, evMap) + return webhookRunAggregates{ + summary: &WebhookRunSummary{ + StepsWithWebhook: steps, + TotalDeliveries: total, + FailedDeliveries: failed, + }, + failures: failures, + } +} + +func buildWebhookFailurePreviews( + execByID map[string]*types.Execution, + evMap map[string][]*types.ExecutionWebhookEvent, +) []WebhookFailurePreview { + if len(evMap) == 0 { + return nil + } + previews := make([]WebhookFailurePreview, 0) + for execID, evs := range evMap { + var latestFail *types.ExecutionWebhookEvent + for _, ev := range evs { + if ev == nil { + continue + } + if strings.ToLower(strings.TrimSpace(ev.Status)) != "failed" { + continue + } + if latestFail == nil || ev.CreatedAt.After(latestFail.CreatedAt) { + latestFail = ev + } + } + if latestFail == nil { + continue + } + var agent, reasoner string + if ex := execByID[execID]; ex != nil { + agent = strings.TrimSpace(ex.AgentNodeID) + reasoner = strings.TrimSpace(ex.ReasonerID) + } + previews = append(previews, WebhookFailurePreview{ + ExecutionID: execID, + AgentNodeID: agent, + ReasonerID: reasoner, + EventType: latestFail.EventType, + HTTPStatus: latestFail.HTTPStatus, + CreatedAt: latestFail.CreatedAt.UTC().Format(time.RFC3339), + }) + } + sort.Slice(previews, func(i, j int) bool { + return previews[i].CreatedAt > previews[j].CreatedAt + }) + if len(previews) > maxWebhookFailurePreviews { + previews = previews[:maxWebhookFailurePreviews] + } + return previews +} + +func lookupWorkflowIssuerDID(ctx context.Context, store storage.StorageProvider, workflowID string) *string { + wf := workflowID + vcs, err := store.ListExecutionVCs(ctx, types.VCFilters{WorkflowID: &wf, Limit: 1}) + if err != nil || len(vcs) == 0 || vcs[0] == nil { + return nil + } + iss := strings.TrimSpace(vcs[0].IssuerDID) + if iss == "" { + return nil + } + return &iss +} + func buildExecutionDAG(executions []*types.Execution) (WorkflowDAGNode, []WorkflowDAGNode, string, string, *string, *string, int) { execMap := make(map[string]*types.Execution, len(executions)) childrenMap := make(map[string][]*types.Execution) diff --git a/control-plane/internal/server/apicatalog/catalog_entries.go b/control-plane/internal/server/apicatalog/catalog_entries.go index 226375e30..59155fdb0 100644 --- a/control-plane/internal/server/apicatalog/catalog_entries.go +++ b/control-plane/internal/server/apicatalog/catalog_entries.go @@ -46,6 +46,18 @@ func DefaultEntries() []EndpointEntry { {Method: "POST", Path: "/api/v1/nodes/:node_id/shutdown", Group: "nodes", Summary: "Graceful node shutdown", AuthLevel: "api_key", Tags: []string{"nodes", "lifecycle", "shutdown"}}, {Method: "POST", Path: "/api/v1/actions/claim", Group: "nodes", Summary: "Claim pending actions", AuthLevel: "api_key", Tags: []string{"nodes", "actions", "claim"}}, + // --- UI: node logs (proxy to agent NDJSON) --- + {Method: "GET", Path: "/api/ui/v1/nodes/:nodeId/logs", Group: "ui-nodes", Summary: "Proxy agent process logs (NDJSON tail or follow)", AuthLevel: "api_key", Tags: []string{"ui", "nodes", "logs", "observability"}, + Parameters: []ParamEntry{ + {Name: "nodeId", In: "path", Required: true, Type: "string", Desc: "Agent node ID"}, + {Name: "tail_lines", In: "query", Type: "int", Desc: "Last N log lines"}, + {Name: "since_seq", In: "query", Type: "int", Desc: "Return lines with seq greater than this"}, + {Name: "follow", In: "query", Type: "string", Desc: "1 or true for chunked stream"}, + }, + }, + {Method: "GET", Path: "/api/ui/v1/settings/node-log-proxy", Group: "ui-settings", Summary: "Effective node log proxy limits and env lock flags", AuthLevel: "api_key", Tags: []string{"ui", "settings", "logs"}}, + {Method: "PUT", Path: "/api/ui/v1/settings/node-log-proxy", Group: "ui-settings", Summary: "Update node log proxy limits (persisted to DB config blob)", AuthLevel: "api_key", Tags: []string{"ui", "settings", "logs"}}, + // --- Execute --- {Method: "POST", Path: "/api/v1/execute/:target", Group: "execute", Summary: "Execute a reasoner or skill synchronously", AuthLevel: "api_key", Tags: []string{"execute", "reasoner", "skill", "sync"}, Parameters: []ParamEntry{{Name: "target", In: "path", Required: true, Type: "string", Desc: "Target in format agent_id.reasoner_id or agent_id.skill_id"}}, @@ -98,6 +110,7 @@ func DefaultEntries() []EndpointEntry { {Method: "GET", Path: "/api/v1/did/resolve/:did", Group: "did", Summary: "Resolve a DID to its document", AuthLevel: "public", Tags: []string{"did", "identity", "resolve"}}, {Method: "GET", Path: "/api/v1/did/issuer-public-keys", Group: "did", Summary: "Get issuer public keys", AuthLevel: "public", Tags: []string{"did", "identity", "keys"}}, {Method: "GET", Path: "/api/v1/did/workflow/:workflow_id/vc-chain", Group: "did", Summary: "Get VC chain for workflow", AuthLevel: "api_key", Tags: []string{"did", "vc", "workflow", "audit"}}, + {Method: "POST", Path: "/api/v1/did/verify-audit", Group: "did", Summary: "Verify exported provenance JSON (VC chain or bare VC)", AuthLevel: "api_key", Tags: []string{"did", "vc", "verify", "audit"}}, // --- Agentic API --- {Method: "GET", Path: "/api/v1/agentic/discover", Group: "agentic", Summary: "Search API endpoints by keyword, group, or method", AuthLevel: "api_key", Tags: []string{"agentic", "discover", "api", "search"}}, diff --git a/control-plane/internal/server/config_db.go b/control-plane/internal/server/config_db.go index 527e53715..82c71e593 100644 --- a/control-plane/internal/server/config_db.go +++ b/control-plane/internal/server/config_db.go @@ -82,6 +82,33 @@ func mergeDBConfig(target, dbCfg *config.Config) { if dbCfg.AgentField.Approval.WebhookSecret != "" || dbCfg.AgentField.Approval.DefaultExpiryHours != 0 { target.AgentField.Approval = dbCfg.AgentField.Approval } + if dbCfg.AgentField.NodeLogProxy.ConnectTimeout != 0 { + target.AgentField.NodeLogProxy.ConnectTimeout = dbCfg.AgentField.NodeLogProxy.ConnectTimeout + } + if dbCfg.AgentField.NodeLogProxy.StreamIdleTimeout != 0 { + target.AgentField.NodeLogProxy.StreamIdleTimeout = dbCfg.AgentField.NodeLogProxy.StreamIdleTimeout + } + if dbCfg.AgentField.NodeLogProxy.MaxStreamDuration != 0 { + target.AgentField.NodeLogProxy.MaxStreamDuration = dbCfg.AgentField.NodeLogProxy.MaxStreamDuration + } + if dbCfg.AgentField.NodeLogProxy.MaxTailLines != 0 { + target.AgentField.NodeLogProxy.MaxTailLines = dbCfg.AgentField.NodeLogProxy.MaxTailLines + } + if dbCfg.AgentField.ExecutionLogs.RetentionPeriod != 0 { + target.AgentField.ExecutionLogs.RetentionPeriod = dbCfg.AgentField.ExecutionLogs.RetentionPeriod + } + if dbCfg.AgentField.ExecutionLogs.MaxEntriesPerExecution != 0 { + target.AgentField.ExecutionLogs.MaxEntriesPerExecution = dbCfg.AgentField.ExecutionLogs.MaxEntriesPerExecution + } + if dbCfg.AgentField.ExecutionLogs.MaxTailEntries != 0 { + target.AgentField.ExecutionLogs.MaxTailEntries = dbCfg.AgentField.ExecutionLogs.MaxTailEntries + } + if dbCfg.AgentField.ExecutionLogs.StreamIdleTimeout != 0 { + target.AgentField.ExecutionLogs.StreamIdleTimeout = dbCfg.AgentField.ExecutionLogs.StreamIdleTimeout + } + if dbCfg.AgentField.ExecutionLogs.MaxStreamDuration != 0 { + target.AgentField.ExecutionLogs.MaxStreamDuration = dbCfg.AgentField.ExecutionLogs.MaxStreamDuration + } // Features if dbCfg.Features.DID.Method != "" { diff --git a/control-plane/internal/server/server.go b/control-plane/internal/server/server.go index f6bac25dd..5d05d5ac4 100644 --- a/control-plane/internal/server/server.go +++ b/control-plane/internal/server/server.go @@ -25,13 +25,13 @@ import ( "github.com/Agent-Field/agentfield/control-plane/internal/handlers/admin" // Admin handlers "github.com/Agent-Field/agentfield/control-plane/internal/handlers/agentic" // Agentic API handlers connectorpkg "github.com/Agent-Field/agentfield/control-plane/internal/handlers/connector" // Connector handlers - "github.com/Agent-Field/agentfield/control-plane/internal/server/apicatalog" // API catalog - "github.com/Agent-Field/agentfield/control-plane/internal/server/knowledgebase" // Knowledge base "github.com/Agent-Field/agentfield/control-plane/internal/handlers/ui" // UI handlers "github.com/Agent-Field/agentfield/control-plane/internal/infrastructure/communication" "github.com/Agent-Field/agentfield/control-plane/internal/infrastructure/process" infrastorage "github.com/Agent-Field/agentfield/control-plane/internal/infrastructure/storage" "github.com/Agent-Field/agentfield/control-plane/internal/logger" + "github.com/Agent-Field/agentfield/control-plane/internal/server/apicatalog" // API catalog + "github.com/Agent-Field/agentfield/control-plane/internal/server/knowledgebase" // Knowledge base "github.com/Agent-Field/agentfield/control-plane/internal/server/middleware" "github.com/Agent-Field/agentfield/control-plane/internal/services" // Services "github.com/Agent-Field/agentfield/control-plane/internal/storage" @@ -75,7 +75,7 @@ type AgentFieldServer struct { tagVCVerifier *services.TagVCVerifier agentfieldHome string // LLM health monitoring - llmHealthMonitor *services.LLMHealthMonitor + llmHealthMonitor *services.LLMHealthMonitor // Cleanup service cleanupService *handlers.ExecutionCleanupService payloadStore services.PayloadStore @@ -1078,6 +1078,36 @@ func (s *AgentFieldServer) setupRoutes() { // Individual node operations nodes.GET("/:nodeId/details", uiNodesHandler.GetNodeDetailsHandler) + nodeLogsHandler := &ui.NodeLogsProxyHandler{ + Storage: s.storage, + Snapshot: func() (config.NodeLogProxyConfig, string) { + s.configMu.RLock() + defer s.configMu.RUnlock() + return config.EffectiveNodeLogProxy(s.config.AgentField.NodeLogProxy), + s.config.Features.DID.Authorization.InternalToken + }, + } + nodes.GET("/:nodeId/logs", nodeLogsHandler.ProxyNodeLogsHandler) + + nodeLogSettingsHandler := &ui.NodeLogSettingsHandler{ + Storage: s.storage, + ReadConfig: func(fn func(*config.Config)) { + s.configMu.RLock() + defer s.configMu.RUnlock() + fn(s.config) + }, + WriteConfig: func(fn func(*config.Config)) { + s.configMu.Lock() + defer s.configMu.Unlock() + fn(s.config) + }, + } + settings := uiAPI.Group("/settings") + { + settings.GET("/node-log-proxy", nodeLogSettingsHandler.GetNodeLogProxySettingsHandler) + settings.PUT("/node-log-proxy", nodeLogSettingsHandler.PutNodeLogProxySettingsHandler) + } + // DID and VC management endpoints for nodes didHandler := ui.NewDIDHandler(s.storage, s.didService, s.vcService, s.didWebService) nodes.GET("/:nodeId/did", didHandler.GetNodeDIDHandler) @@ -1121,8 +1151,11 @@ func (s *AgentFieldServer) setupRoutes() { executions.POST("/note", handlers.AddExecutionNoteHandler(s.storage)) executions.GET("/:execution_id/notes", handlers.GetExecutionNotesHandler(s.storage)) - // Execution log streaming (SSE) - execLogsHandler := ui.NewExecutionLogsHandler(s.llmHealthMonitor) + // Structured execution logs for the execution detail page + execLogsHandler := ui.NewExecutionLogsHandler(s.storage, s.llmHealthMonitor, func() config.ExecutionLogsConfig { + return s.config.AgentField.ExecutionLogs + }) + executions.GET("/:execution_id/logs", execLogsHandler.GetExecutionLogsHandler) executions.GET("/:execution_id/logs/stream", execLogsHandler.StreamExecutionLogsHandler) // DID and VC management endpoints for executions @@ -1133,7 +1166,9 @@ func (s *AgentFieldServer) setupRoutes() { } // LLM health status endpoint and execution queue status - llmHandler := ui.NewExecutionLogsHandler(s.llmHealthMonitor) + llmHandler := ui.NewExecutionLogsHandler(s.storage, s.llmHealthMonitor, func() config.ExecutionLogsConfig { + return s.config.AgentField.ExecutionLogs + }) uiAPI.GET("/llm/health", llmHandler.GetLLMHealthHandler) uiAPI.GET("/queue/status", llmHandler.GetExecutionQueueStatusHandler) @@ -1186,6 +1221,8 @@ func (s *AgentFieldServer) setupRoutes() { didHandler := ui.NewDIDHandler(s.storage, s.didService, s.vcService, s.didWebService) did.GET("/status", didHandler.GetDIDSystemStatusHandler) did.GET("/export/vcs", didHandler.ExportVCsHandler) + did.POST("/verify", didHandler.VerifyVCHandler) + did.POST("/verify-audit", didHandler.VerifyAuditBundleHandler) did.GET("/:did/resolution-bundle", didHandler.GetDIDResolutionBundleHandler) did.GET("/:did/resolution-bundle/download", didHandler.DownloadDIDResolutionBundleHandler) } @@ -1307,6 +1344,9 @@ func (s *AgentFieldServer) setupRoutes() { agentAPI.GET("/executions/:execution_id", handlers.GetExecutionStatusHandler(s.storage)) agentAPI.POST("/executions/batch-status", handlers.BatchExecutionStatusHandler(s.storage)) agentAPI.POST("/executions/:execution_id/status", handlers.UpdateExecutionStatusHandler(s.storage, s.payloadStore, s.webhookDispatcher, s.config.AgentField.ExecutionQueue.AgentCallTimeout)) + agentAPI.POST("/executions/:execution_id/logs", handlers.StructuredExecutionLogsHandler(s.storage, func() config.ExecutionLogsConfig { + return s.config.AgentField.ExecutionLogs + })) agentAPI.POST("/executions/:execution_id/cancel", handlers.CancelExecutionHandler(s.storage)) agentAPI.POST("/executions/:execution_id/pause", handlers.PauseExecutionHandler(s.storage)) agentAPI.POST("/executions/:execution_id/resume", handlers.ResumeExecutionHandler(s.storage)) diff --git a/control-plane/internal/server/server_routes_test.go b/control-plane/internal/server/server_routes_test.go index 031201037..6a1196687 100644 --- a/control-plane/internal/server/server_routes_test.go +++ b/control-plane/internal/server/server_routes_test.go @@ -61,6 +61,18 @@ func (s *stubStorage) GetExecutionEventBus() *events.ExecutionEventBus { return s.eventBus } +func (s *stubStorage) StoreExecutionLogEntry(ctx context.Context, entry *types.ExecutionLogEntry) error { + return nil +} + +func (s *stubStorage) ListExecutionLogEntries(ctx context.Context, executionID string, afterSeq *int64, limit int, levels []string, nodeIDs []string, sources []string, query string) ([]*types.ExecutionLogEntry, error) { + return nil, nil +} + +func (s *stubStorage) PruneExecutionLogEntries(ctx context.Context, executionID string, maxEntries int, olderThan time.Time) error { + return nil +} + // Stub implementations for remaining StorageProvider methods func (s *stubStorage) Initialize(ctx context.Context, config storage.StorageConfig) error { return nil } func (s *stubStorage) Close(ctx context.Context) error { return nil } @@ -164,7 +176,7 @@ func (s *stubStorage) DeleteMemory(ctx context.Context, scope, scopeID, key stri func (s *stubStorage) ListMemory(ctx context.Context, scope, scopeID string) ([]*types.Memory, error) { return nil, nil } -func (s *stubStorage) SetVector(ctx context.Context, record *types.VectorRecord) error { return nil } +func (s *stubStorage) SetVector(ctx context.Context, record *types.VectorRecord) error { return nil } func (s *stubStorage) GetVector(ctx context.Context, scope, scopeID, key string) (*types.VectorRecord, error) { return nil, nil } @@ -301,6 +313,10 @@ func (s *stubStorage) GetWorkflowExecutionEventBus() *events.EventBus[*types.Wor return nil } +func (s *stubStorage) GetExecutionLogEventBus() *events.EventBus[*types.ExecutionLogEntry] { + return events.NewEventBus[*types.ExecutionLogEntry]() +} + // DID Registry operations func (s *stubStorage) StoreDID(ctx context.Context, did string, didDocument, publicKey, privateKeyRef, derivationPath string) error { return nil diff --git a/control-plane/internal/storage/execution_records.go b/control-plane/internal/storage/execution_records.go index 0341dfbac..3d46c1d54 100644 --- a/control-plane/internal/storage/execution_records.go +++ b/control-plane/internal/storage/execution_records.go @@ -1065,6 +1065,7 @@ func (ls *LocalStorage) MarkStaleWorkflowExecutions(ctx context.Context, staleAf FROM workflow_executions WHERE status IN ('running', 'pending', 'queued', 'waiting') AND COALESCE(updated_at, created_at, started_at) <= ? + AND COALESCE(approval_status, '') != 'pending' ORDER BY COALESCE(updated_at, created_at, started_at) ASC LIMIT ?`, cutoff, limit) if err != nil { @@ -1108,6 +1109,16 @@ func (ls *LocalStorage) MarkStaleWorkflowExecutions(ctx context.Context, staleAf } defer updateStmt.Close() + // Also sync the executions table so both tables stay consistent. + syncExecStmt, err := tx.PrepareContext(ctx, ` + UPDATE executions + SET status = ?, error_message = ?, completed_at = ?, duration_ms = ?, updated_at = ? + WHERE execution_id = ? AND status IN ('running', 'pending', 'queued', 'waiting')`) + if err != nil { + return 0, fmt.Errorf("prepare stale execution sync update: %w", err) + } + defer syncExecStmt.Close() + now := time.Now().UTC() timeoutMessage := "execution timed out (no activity)" @@ -1140,6 +1151,16 @@ func (ls *LocalStorage) MarkStaleWorkflowExecutions(ctx context.Context, staleAf return 0, fmt.Errorf("rows affected for workflow execution %s: %w", rec.id, err) } if rowsAffected > 0 { + // Keep executions table in sync. + _, _ = syncExecStmt.ExecContext( + ctx, + types.ExecutionStatusTimeout, + timeoutMessage, + now, + durationMS, + now, + rec.id, + ) updated++ } } diff --git a/control-plane/internal/storage/execution_state_validation.go b/control-plane/internal/storage/execution_state_validation.go index de06edc05..73399c584 100644 --- a/control-plane/internal/storage/execution_state_validation.go +++ b/control-plane/internal/storage/execution_state_validation.go @@ -32,7 +32,7 @@ func validateExecutionStateTransition(currentStatus, newStatus string) error { string(types.ExecutionStatusSucceeded): {}, string(types.ExecutionStatusFailed): {}, string(types.ExecutionStatusCancelled): {}, - string(types.ExecutionStatusTimeout): {}, + string(types.ExecutionStatusTimeout): {string(types.ExecutionStatusRunning), string(types.ExecutionStatusCancelled)}, } allowedStates, exists := validTransitions[currentStatus] diff --git a/control-plane/internal/storage/local.go b/control-plane/internal/storage/local.go index 93f61f2f0..9095118eb 100644 --- a/control-plane/internal/storage/local.go +++ b/control-plane/internal/storage/local.go @@ -481,6 +481,8 @@ type LocalStorage struct { vectorStore vectorStore eventBus *events.ExecutionEventBus // Event bus for real-time updates workflowExecutionEventBus *events.EventBus[*types.WorkflowExecutionEvent] + executionLogEventBus *events.EventBus[*types.ExecutionLogEntry] + ftsEnabled bool } // NewLocalStorage creates a new instance of LocalStorage. @@ -493,6 +495,7 @@ func NewLocalStorage(config LocalStorageConfig) *LocalStorage { subscribers: make(map[string][]chan types.MemoryChangeEvent), eventBus: events.NewExecutionEventBus(), workflowExecutionEventBus: events.NewEventBus[*types.WorkflowExecutionEvent](), + executionLogEventBus: events.NewEventBus[*types.ExecutionLogEntry](), } } @@ -506,6 +509,7 @@ func NewPostgresStorage(config PostgresStorageConfig) *LocalStorage { subscribers: make(map[string][]chan types.MemoryChangeEvent), eventBus: events.NewExecutionEventBus(), workflowExecutionEventBus: events.NewEventBus[*types.WorkflowExecutionEvent](), + executionLogEventBus: events.NewEventBus[*types.ExecutionLogEntry](), } } @@ -911,10 +915,13 @@ func (ls *LocalStorage) createSchema(ctx context.Context) error { if err := ls.setupWorkflowExecutionFTS(); err != nil { if strings.Contains(err.Error(), "no such module: fts5") { + ls.ftsEnabled = false log.Printf("FTS5 module not available, full-text search will be degraded") } else { return err } + } else { + ls.ftsEnabled = true } if err := ls.ensureSQLiteIndexes(); err != nil { @@ -2522,10 +2529,20 @@ func (ls *LocalStorage) QueryWorkflowExecutions(ctx context.Context, filters typ // Sanitize search input to prevent FTS5 syntax errors sanitizedSearch := sanitizeFTS5Query(*filters.Search) if sanitizedSearch != "" { - // Use FTS5 MATCH for efficient full-text search - ftsJoin = " INNER JOIN workflow_executions_fts ON workflow_executions.id = workflow_executions_fts.rowid" - conditions = append(conditions, "workflow_executions_fts MATCH ?") - args = append(args, sanitizedSearch) + if ls.ftsEnabled { + // Use FTS5 MATCH for efficient full-text search when available. + ftsJoin = " INNER JOIN workflow_executions_fts ON workflow_executions.id = workflow_executions_fts.rowid" + conditions = append(conditions, "workflow_executions_fts MATCH ?") + args = append(args, sanitizedSearch) + } else { + searchTerm := strings.Trim(strings.TrimSpace(sanitizedSearch), "\"") + if searchTerm == "" { + searchTerm = strings.TrimSpace(*filters.Search) + } + like := "%" + searchTerm + "%" + conditions = append(conditions, `(workflow_executions.execution_id LIKE ? OR workflow_executions.workflow_id LIKE ? OR workflow_executions.agent_node_id LIKE ? OR workflow_executions.session_id LIKE ? OR workflow_executions.workflow_name LIKE ?)`) + args = append(args, like, like, like, like, like) + } } } @@ -6195,6 +6212,11 @@ func (ls *LocalStorage) GetWorkflowExecutionEventBus() *events.EventBus[*types.W return ls.workflowExecutionEventBus } +// GetExecutionLogEventBus returns the bus for structured execution logs. +func (ls *LocalStorage) GetExecutionLogEventBus() *events.EventBus[*types.ExecutionLogEntry] { + return ls.executionLogEventBus +} + // AgentField Server DID operations func (ls *LocalStorage) StoreAgentFieldServerDID(ctx context.Context, agentfieldServerID, rootDID string, masterSeed []byte, createdAt, lastKeyRotation time.Time) error { // Check context cancellation early @@ -7625,6 +7647,327 @@ func (ls *LocalStorage) ListWorkflowExecutionEvents(ctx context.Context, executi return events, nil } +// StoreExecutionLogEntry inserts a structured execution log entry and publishes it to subscribers. +func (ls *LocalStorage) StoreExecutionLogEntry(ctx context.Context, entry *types.ExecutionLogEntry) error { + if entry == nil { + return fmt.Errorf("execution log entry is nil") + } + if strings.TrimSpace(entry.ExecutionID) == "" { + return fmt.Errorf("execution_id is required") + } + if strings.TrimSpace(entry.WorkflowID) == "" { + entry.WorkflowID = entry.ExecutionID + } + if strings.TrimSpace(entry.Level) == "" { + entry.Level = "info" + } + if strings.TrimSpace(entry.Source) == "" { + entry.Source = "sdk.logger" + } + if entry.EmittedAt.IsZero() { + entry.EmittedAt = time.Now().UTC() + } + ls.mu.Lock() + defer ls.mu.Unlock() + + return ls.retryDatabaseOperation(ctx, entry.ExecutionID, func() error { + tx, err := ls.db.BeginTx(ctx, nil) + if err != nil { + return fmt.Errorf("failed to begin execution log transaction: %w", err) + } + defer rollbackTx(tx, "StoreExecutionLogEntry:"+entry.ExecutionID) + + if err := ls.storeExecutionLogEntryTx(ctx, tx, entry); err != nil { + return err + } + + if err := tx.Commit(); err != nil { + return fmt.Errorf("failed to commit execution log transaction: %w", err) + } + + entryCopy := *entry + ls.executionLogEventBus.Publish(&entryCopy) + return nil + }) +} + +// StoreExecutionLogEntries atomically stores a batch of structured execution logs for one execution. +func (ls *LocalStorage) StoreExecutionLogEntries(ctx context.Context, executionID string, entries []*types.ExecutionLogEntry) error { + if len(entries) == 0 { + return nil + } + + ls.mu.Lock() + defer ls.mu.Unlock() + + return ls.retryDatabaseOperation(ctx, executionID, func() error { + tx, err := ls.db.BeginTx(ctx, nil) + if err != nil { + return fmt.Errorf("failed to begin execution log batch transaction: %w", err) + } + defer rollbackTx(tx, "StoreExecutionLogEntries:"+executionID) + + for _, entry := range entries { + if entry == nil { + continue + } + if err := ls.storeExecutionLogEntryTx(ctx, tx, entry); err != nil { + return err + } + } + + if err := tx.Commit(); err != nil { + return fmt.Errorf("failed to commit execution log batch transaction: %w", err) + } + + for _, entry := range entries { + if entry == nil { + continue + } + entryCopy := *entry + ls.executionLogEventBus.Publish(&entryCopy) + } + return nil + }) +} + +func (ls *LocalStorage) storeExecutionLogEntryTx(ctx context.Context, tx DBTX, entry *types.ExecutionLogEntry) error { + var nextSeq int64 = 1 + if err := tx.QueryRowContext(ctx, + `SELECT COALESCE(MAX(sequence), 0) + 1 FROM execution_logs WHERE execution_id = ?`, + entry.ExecutionID, + ).Scan(&nextSeq); err != nil { + return fmt.Errorf("failed to compute execution log sequence: %w", err) + } + entry.Sequence = nextSeq + + attributes := "{}" + if len(entry.Attributes) > 0 { + attributes = string(entry.Attributes) + } + + query := ` + INSERT INTO execution_logs ( + execution_id, workflow_id, run_id, root_workflow_id, parent_execution_id, sequence, + agent_node_id, reasoner_id, level, source, event_type, message, attributes, + system_generated, sdk_language, attempt, span_id, step_id, error_category, emitted_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` + + result, err := tx.ExecContext(ctx, query, + entry.ExecutionID, + entry.WorkflowID, + entry.RunID, + entry.RootWorkflowID, + entry.ParentExecutionID, + entry.Sequence, + entry.AgentNodeID, + entry.ReasonerID, + entry.Level, + entry.Source, + entry.EventType, + entry.Message, + attributes, + entry.SystemGenerated, + entry.SDKLanguage, + entry.Attempt, + entry.SpanID, + entry.StepID, + entry.ErrorCategory, + entry.EmittedAt, + ) + if err != nil { + return fmt.Errorf("failed to insert execution log entry: %w", err) + } + if id, err := result.LastInsertId(); err == nil { + entry.EventID = id + } + if entry.RecordedAt.IsZero() { + entry.RecordedAt = time.Now().UTC() + } + return nil +} + +// ListExecutionLogEntries retrieves structured execution logs ordered by sequence. +func (ls *LocalStorage) ListExecutionLogEntries(ctx context.Context, executionID string, afterSeq *int64, limit int, levels []string, nodeIDs []string, sources []string, query string) ([]*types.ExecutionLogEntry, error) { + baseQuery := ` + SELECT event_id, execution_id, workflow_id, run_id, root_workflow_id, parent_execution_id, sequence, + agent_node_id, reasoner_id, level, source, event_type, message, attributes, system_generated, + sdk_language, attempt, span_id, step_id, error_category, emitted_at, recorded_at + FROM execution_logs + WHERE execution_id = ?` + args := []interface{}{executionID} + + appendIn := func(column string, values []string) { + if len(values) == 0 { + return + } + holders := make([]string, 0, len(values)) + for _, value := range values { + if strings.TrimSpace(value) == "" { + continue + } + holders = append(holders, "?") + args = append(args, value) + } + if len(holders) > 0 { + baseQuery += " AND " + column + " IN (" + strings.Join(holders, ",") + ")" + } + } + + if afterSeq != nil { + baseQuery += " AND sequence > ?" + args = append(args, *afterSeq) + } + appendIn("level", levels) + appendIn("agent_node_id", nodeIDs) + appendIn("source", sources) + if trimmed := strings.TrimSpace(query); trimmed != "" { + baseQuery += " AND (message LIKE ? OR attributes LIKE ?)" + like := "%" + trimmed + "%" + args = append(args, like, like) + } + + descendingTail := afterSeq == nil && limit > 0 + if descendingTail { + baseQuery += " ORDER BY sequence DESC" + baseQuery += fmt.Sprintf(" LIMIT %d", limit) + } else { + baseQuery += " ORDER BY sequence ASC" + if limit > 0 { + baseQuery += fmt.Sprintf(" LIMIT %d", limit) + } + } + + rows, err := ls.db.QueryContext(ctx, baseQuery, args...) + if err != nil { + return nil, fmt.Errorf("failed to query execution logs: %w", err) + } + defer rows.Close() + + var entries []*types.ExecutionLogEntry + for rows.Next() { + entry := &types.ExecutionLogEntry{} + var runID, rootWorkflowID, parentExecutionID, reasonerID, eventType, sdkLanguage, spanID, stepID, errorCategory sql.NullString + var attributes sql.NullString + var attempt sql.NullInt64 + var emittedAt sql.NullTime + var recordedAt sql.NullTime + if err := rows.Scan( + &entry.EventID, + &entry.ExecutionID, + &entry.WorkflowID, + &runID, + &rootWorkflowID, + &parentExecutionID, + &entry.Sequence, + &entry.AgentNodeID, + &reasonerID, + &entry.Level, + &entry.Source, + &eventType, + &entry.Message, + &attributes, + &entry.SystemGenerated, + &sdkLanguage, + &attempt, + &spanID, + &stepID, + &errorCategory, + &emittedAt, + &recordedAt, + ); err != nil { + return nil, fmt.Errorf("failed to scan execution log entry: %w", err) + } + + if runID.Valid { + entry.RunID = &runID.String + } + if rootWorkflowID.Valid { + entry.RootWorkflowID = &rootWorkflowID.String + } + if parentExecutionID.Valid { + entry.ParentExecutionID = &parentExecutionID.String + } + if reasonerID.Valid { + entry.ReasonerID = &reasonerID.String + } + if eventType.Valid { + entry.EventType = &eventType.String + } + if sdkLanguage.Valid { + entry.SDKLanguage = &sdkLanguage.String + } + if spanID.Valid { + entry.SpanID = &spanID.String + } + if stepID.Valid { + entry.StepID = &stepID.String + } + if errorCategory.Valid { + entry.ErrorCategory = &errorCategory.String + } + if attempt.Valid { + value := int(attempt.Int64) + entry.Attempt = &value + } + if emittedAt.Valid { + entry.EmittedAt = emittedAt.Time + } + if recordedAt.Valid { + entry.RecordedAt = recordedAt.Time + } else { + entry.RecordedAt = entry.EmittedAt + } + if attributes.Valid { + entry.Attributes = json.RawMessage(attributes.String) + } else { + entry.Attributes = json.RawMessage("{}") + } + + entries = append(entries, entry) + } + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("error iterating execution logs: %w", err) + } + if descendingTail { + for left, right := 0, len(entries)-1; left < right; left, right = left+1, right-1 { + entries[left], entries[right] = entries[right], entries[left] + } + } + return entries, nil +} + +// PruneExecutionLogEntries trims old or excessive execution logs for a single execution. +func (ls *LocalStorage) PruneExecutionLogEntries(ctx context.Context, executionID string, maxEntries int, olderThan time.Time) error { + if strings.TrimSpace(executionID) == "" { + return nil + } + if !olderThan.IsZero() { + if _, err := ls.db.ExecContext(ctx, + `DELETE FROM execution_logs WHERE execution_id = ? AND emitted_at < ?`, + executionID, olderThan, + ); err != nil { + return fmt.Errorf("failed to prune execution logs by age: %w", err) + } + } + if maxEntries > 0 { + if _, err := ls.db.ExecContext(ctx, ` + DELETE FROM execution_logs + WHERE execution_id = ? + AND event_id NOT IN ( + SELECT event_id FROM execution_logs + WHERE execution_id = ? + ORDER BY sequence DESC + LIMIT ? + )`, + executionID, executionID, maxEntries, + ); err != nil { + return fmt.Errorf("failed to prune execution logs by count: %w", err) + } + } + return nil +} + // StoreExecutionWebhookEvent records webhook delivery attempts for SQLite deployments. func (ls *LocalStorage) StoreExecutionWebhookEvent(ctx context.Context, event *types.ExecutionWebhookEvent) error { if event == nil { diff --git a/control-plane/internal/storage/migrations.go b/control-plane/internal/storage/migrations.go index 203c79f78..3ffd6cbc9 100644 --- a/control-plane/internal/storage/migrations.go +++ b/control-plane/internal/storage/migrations.go @@ -214,6 +214,7 @@ func (ls *LocalStorage) autoMigrateSchema(ctx context.Context) error { &AgentPackageModel{}, &WorkflowExecutionModel{}, &WorkflowExecutionEventModel{}, + &ExecutionLogEntryModel{}, &WorkflowRunEventModel{}, &WorkflowRunModel{}, &WorkflowStepModel{}, diff --git a/control-plane/internal/storage/models.go b/control-plane/internal/storage/models.go index 302c5455c..142a15bef 100644 --- a/control-plane/internal/storage/models.go +++ b/control-plane/internal/storage/models.go @@ -175,6 +175,33 @@ type WorkflowExecutionEventModel struct { func (WorkflowExecutionEventModel) TableName() string { return "workflow_execution_events" } +type ExecutionLogEntryModel struct { + EventID int64 `gorm:"column:event_id;primaryKey;autoIncrement"` + ExecutionID string `gorm:"column:execution_id;not null;index:idx_execution_logs_execution,priority:1"` + WorkflowID string `gorm:"column:workflow_id;not null;index"` + RunID *string `gorm:"column:run_id;index"` + RootWorkflowID *string `gorm:"column:root_workflow_id;index"` + ParentExecutionID *string `gorm:"column:parent_execution_id;index"` + Sequence int64 `gorm:"column:sequence;not null;index:idx_execution_logs_execution,priority:2"` + AgentNodeID string `gorm:"column:agent_node_id;not null;index"` + ReasonerID *string `gorm:"column:reasoner_id;index"` + Level string `gorm:"column:level;not null;index"` + Source string `gorm:"column:source;not null;index"` + EventType *string `gorm:"column:event_type;index"` + Message string `gorm:"column:message;not null"` + Attributes string `gorm:"column:attributes;default:'{}'"` + SystemGenerated bool `gorm:"column:system_generated;not null;default:false"` + SDKLanguage *string `gorm:"column:sdk_language;index"` + Attempt *int `gorm:"column:attempt"` + SpanID *string `gorm:"column:span_id;index"` + StepID *string `gorm:"column:step_id;index"` + ErrorCategory *string `gorm:"column:error_category;index"` + EmittedAt time.Time `gorm:"column:emitted_at;not null;index"` + RecordedAt time.Time `gorm:"column:recorded_at;autoCreateTime"` +} + +func (ExecutionLogEntryModel) TableName() string { return "execution_logs" } + type WorkflowRunEventModel struct { EventID int64 `gorm:"column:event_id;primaryKey;autoIncrement"` RunID string `gorm:"column:run_id;not null;index:idx_workflow_run_events_run,priority:1"` diff --git a/control-plane/internal/storage/storage.go b/control-plane/internal/storage/storage.go index eeb42bd1a..e30d381f3 100644 --- a/control-plane/internal/storage/storage.go +++ b/control-plane/internal/storage/storage.go @@ -71,6 +71,9 @@ type StorageProvider interface { ListExecutionWebhookEventsBatch(ctx context.Context, executionIDs []string) (map[string][]*types.ExecutionWebhookEvent, error) StoreWorkflowExecutionEvent(ctx context.Context, event *types.WorkflowExecutionEvent) error ListWorkflowExecutionEvents(ctx context.Context, executionID string, afterSeq *int64, limit int) ([]*types.WorkflowExecutionEvent, error) + StoreExecutionLogEntry(ctx context.Context, entry *types.ExecutionLogEntry) error + ListExecutionLogEntries(ctx context.Context, executionID string, afterSeq *int64, limit int, levels []string, nodeIDs []string, sources []string, query string) ([]*types.ExecutionLogEntry, error) + PruneExecutionLogEntries(ctx context.Context, executionID string, maxEntries int, olderThan time.Time) error // Execution cleanup operations CleanupOldExecutions(ctx context.Context, retentionPeriod time.Duration, batchSize int) (int, error) @@ -165,6 +168,7 @@ type StorageProvider interface { // Execution event bus for real-time updates GetExecutionEventBus() *events.ExecutionEventBus GetWorkflowExecutionEventBus() *events.EventBus[*types.WorkflowExecutionEvent] + GetExecutionLogEventBus() *events.EventBus[*types.ExecutionLogEntry] // DID Registry operations StoreDID(ctx context.Context, did string, didDocument, publicKey, privateKeyRef, derivationPath string) error diff --git a/control-plane/pkg/types/types.go b/control-plane/pkg/types/types.go index 678080f87..2c152cd0a 100644 --- a/control-plane/pkg/types/types.go +++ b/control-plane/pkg/types/types.go @@ -730,6 +730,32 @@ type WorkflowExecutionEvent struct { RecordedAt time.Time `json:"recorded_at" db:"recorded_at"` } +// ExecutionLogEntry captures structured execution-correlated logs emitted by SDK runtimes. +type ExecutionLogEntry struct { + EventID int64 `json:"event_id" db:"event_id"` + ExecutionID string `json:"execution_id" db:"execution_id"` + WorkflowID string `json:"workflow_id" db:"workflow_id"` + RunID *string `json:"run_id,omitempty" db:"run_id"` + RootWorkflowID *string `json:"root_workflow_id,omitempty" db:"root_workflow_id"` + ParentExecutionID *string `json:"parent_execution_id,omitempty" db:"parent_execution_id"` + Sequence int64 `json:"seq" db:"sequence"` + AgentNodeID string `json:"agent_node_id" db:"agent_node_id"` + ReasonerID *string `json:"reasoner_id,omitempty" db:"reasoner_id"` + Level string `json:"level" db:"level"` + Source string `json:"source" db:"source"` + EventType *string `json:"event_type,omitempty" db:"event_type"` + Message string `json:"message" db:"message"` + Attributes json.RawMessage `json:"attributes,omitempty" db:"attributes"` + SystemGenerated bool `json:"system_generated,omitempty" db:"system_generated"` + SDKLanguage *string `json:"sdk_language,omitempty" db:"sdk_language"` + Attempt *int `json:"attempt,omitempty" db:"attempt"` + SpanID *string `json:"span_id,omitempty" db:"span_id"` + StepID *string `json:"step_id,omitempty" db:"step_id"` + ErrorCategory *string `json:"error_category,omitempty" db:"error_category"` + EmittedAt time.Time `json:"ts" db:"emitted_at"` + RecordedAt time.Time `json:"recorded_at" db:"recorded_at"` +} + // WorkflowRunEvent mirrors execution events at the workflow-run level. type WorkflowRunEvent struct { EventID int64 `json:"event_id" db:"event_id"` diff --git a/control-plane/web/client/package-lock.json b/control-plane/web/client/package-lock.json index 57599ff51..29498fe6f 100644 --- a/control-plane/web/client/package-lock.json +++ b/control-plane/web/client/package-lock.json @@ -21,6 +21,7 @@ "@radix-ui/react-dropdown-menu": "^2.1.15", "@radix-ui/react-hover-card": "^1.1.14", "@radix-ui/react-label": "^2.1.7", + "@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-scroll-area": "^1.2.10", "@radix-ui/react-select": "^2.2.5", "@radix-ui/react-separator": "^1.1.7", @@ -29,6 +30,7 @@ "@radix-ui/react-tabs": "^1.1.12", "@radix-ui/react-tooltip": "^1.2.8", "@tailwindcss/line-clamp": "^0.4.4", + "@tanstack/react-query": "^5.96.2", "@tanstack/react-virtual": "^3.13.12", "@types/dagre": "^0.7.53", "@types/react-router-dom": "^5.3.3", @@ -44,7 +46,7 @@ "react-dom": "^19.1.0", "react-markdown": "^10.1.0", "react-router-dom": "^7.6.2", - "recharts": "^3.2.1", + "recharts": "^2.15.4", "remark-gfm": "^4.0.1", "tailwind-merge": "^3.3.1", "tailwindcss-animate": "^1.0.7", @@ -461,7 +463,6 @@ "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -2135,6 +2136,147 @@ } } }, + "node_modules/@radix-ui/react-popover": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", + "integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", + "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-popper": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", + "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-popper": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.7.tgz", @@ -2858,32 +3000,6 @@ "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", "license": "MIT" }, - "node_modules/@reduxjs/toolkit": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.9.0.tgz", - "integrity": "sha512-fSfQlSRu9Z5yBkvsNhYF2rPS8cGXn/TZVrlwN1948QyZ8xMZ0JvP50S2acZNaf+o63u6aEeMjipFyksjIcWrog==", - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.0.0", - "@standard-schema/utils": "^0.3.0", - "immer": "^10.0.3", - "redux": "^5.0.1", - "redux-thunk": "^3.1.0", - "reselect": "^5.1.0" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", - "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" - }, - "peerDependenciesMeta": { - "react": { - "optional": true - }, - "react-redux": { - "optional": true - } - } - }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.11", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.11.tgz", @@ -3151,18 +3267,6 @@ "win32" ] }, - "node_modules/@standard-schema/spec": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", - "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", - "license": "MIT" - }, - "node_modules/@standard-schema/utils": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", - "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", - "license": "MIT" - }, "node_modules/@tailwindcss/line-clamp": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/@tailwindcss/line-clamp/-/line-clamp-0.4.4.tgz", @@ -3172,6 +3276,32 @@ "tailwindcss": ">=2.0.0 || >=3.0.0 || >=3.0.0-alpha.1" } }, + "node_modules/@tanstack/query-core": { + "version": "5.96.2", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.96.2.tgz", + "integrity": "sha512-hzI6cTVh4KNRk8UtoIBS7Lv9g6BnJPXvBKsvYH1aGWvv0347jT3BnSvztOE+kD76XGvZnRC/t6qdW1CaIfwCeA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.96.2", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.96.2.tgz", + "integrity": "sha512-sYyzzJT4G0g02azzJ8o55VFFV31XvFpdUpG+unxS0vSaYsJnSPKGoI6WdPwUucJL1wpgGfwfmntNX/Ub1uOViA==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.96.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, "node_modules/@tanstack/react-virtual": { "version": "3.13.12", "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.12.tgz", @@ -3411,9 +3541,9 @@ "license": "MIT" }, "node_modules/@types/d3-shape": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", - "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", "license": "MIT", "dependencies": { "@types/d3-path": "*" @@ -3599,12 +3729,6 @@ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", "license": "MIT" }, - "node_modules/@types/use-sync-external-store": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", - "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", - "license": "MIT" - }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.34.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.1.tgz", @@ -4908,9 +5032,9 @@ } }, "node_modules/d3-format": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", - "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", "license": "ISC", "engines": { "node": ">=12" @@ -5249,6 +5373,16 @@ "license": "MIT", "peer": true }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/earcut": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", @@ -5310,16 +5444,6 @@ "dev": true, "license": "MIT" }, - "node_modules/es-toolkit": { - "version": "1.39.10", - "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.39.10.tgz", - "integrity": "sha512-E0iGnTtbDhkeczB0T+mxmoVlT4YNweEKBLq7oaU4p11mecdsZpNWOglI4895Vh4usbQ+LsJiuLuI2L0Vdmfm2w==", - "license": "MIT", - "workspaces": [ - "docs", - "benchmarks" - ] - }, "node_modules/esbuild": { "version": "0.25.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", @@ -5582,9 +5706,9 @@ } }, "node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", "license": "MIT" }, "node_modules/expect-type": { @@ -5610,6 +5734,15 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-equals": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.0.tgz", + "integrity": "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -6082,16 +6215,6 @@ "node": ">= 4" } }, - "node_modules/immer": { - "version": "10.1.3", - "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.3.tgz", - "integrity": "sha512-tmjF/k8QDKydUlm3mZU+tjM6zeq9/fFpPqH9SzWmBnVVKsPBg/V66qsMwb3/Bo90cgUN+ghdVBess+hPsxUyRw==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/immer" - } - }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -8314,6 +8437,23 @@ "node": ">= 0.8" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, "node_modules/property-information": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", @@ -8423,6 +8563,7 @@ "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, "license": "MIT", "peer": true }, @@ -8453,29 +8594,6 @@ "react": ">=18" } }, - "node_modules/react-redux": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", - "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", - "license": "MIT", - "dependencies": { - "@types/use-sync-external-store": "^0.0.6", - "use-sync-external-store": "^1.4.0" - }, - "peerDependencies": { - "@types/react": "^18.2.25 || ^19", - "react": "^18.0 || ^19", - "redux": "^5.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "redux": { - "optional": true - } - } - }, "node_modules/react-refresh": { "version": "0.17.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", @@ -8571,6 +8689,21 @@ "react-dom": ">=18" } }, + "node_modules/react-smooth": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz", + "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==", + "license": "MIT", + "dependencies": { + "fast-equals": "^5.0.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/react-style-singleton": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", @@ -8593,6 +8726,22 @@ } } }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -8615,32 +8764,43 @@ } }, "node_modules/recharts": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.2.1.tgz", - "integrity": "sha512-0JKwHRiFZdmLq/6nmilxEZl3pqb4T+aKkOkOi/ZISRZwfBhVMgInxzlYU9D4KnCH3KINScLy68m/OvMXoYGZUw==", + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.4.tgz", + "integrity": "sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==", "license": "MIT", "dependencies": { - "@reduxjs/toolkit": "1.x.x || 2.x.x", - "clsx": "^2.1.1", - "decimal.js-light": "^2.5.1", - "es-toolkit": "^1.39.3", - "eventemitter3": "^5.0.1", - "immer": "^10.1.1", - "react-redux": "8.x.x || 9.x.x", - "reselect": "5.1.1", - "tiny-invariant": "^1.3.3", - "use-sync-external-store": "^1.2.2", - "victory-vendor": "^37.0.2" + "clsx": "^2.0.0", + "eventemitter3": "^4.0.1", + "lodash": "^4.17.21", + "react-is": "^18.3.1", + "react-smooth": "^4.0.4", + "recharts-scale": "^0.4.4", + "tiny-invariant": "^1.3.1", + "victory-vendor": "^36.6.8" }, "engines": { - "node": ">=18" + "node": ">=14" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/recharts-scale": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", + "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", + "license": "MIT", + "dependencies": { + "decimal.js-light": "^2.4.1" + } + }, + "node_modules/recharts/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -8673,21 +8833,6 @@ "dev": true, "license": "MIT" }, - "node_modules/redux": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", - "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT" - }, - "node_modules/redux-thunk": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", - "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", - "license": "MIT", - "peerDependencies": { - "redux": "^5.0.0" - } - }, "node_modules/remark-gfm": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", @@ -8754,12 +8899,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/reselect": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", - "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", - "license": "MIT" - }, "node_modules/resolve": { "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", @@ -10304,9 +10443,9 @@ } }, "node_modules/victory-vendor": { - "version": "37.3.6", - "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz", - "integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==", + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", + "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==", "license": "MIT AND ISC", "dependencies": { "@types/d3-array": "^3.0.3", diff --git a/control-plane/web/client/package.json b/control-plane/web/client/package.json index 3671540f7..90aa7af26 100644 --- a/control-plane/web/client/package.json +++ b/control-plane/web/client/package.json @@ -16,6 +16,9 @@ "@autoform/react": "^4.0.0", "@autoform/shadcn": "^1.0.1", "@autoform/zod": "^5.0.0", + "@deck.gl/core": "^9.2.0", + "@deck.gl/layers": "^9.2.0", + "@deck.gl/react": "^9.2.0", "@phosphor-icons/react": "^2.1.10", "@radix-ui/react-checkbox": "^1.3.2", "@radix-ui/react-collapsible": "^1.1.11", @@ -23,6 +26,7 @@ "@radix-ui/react-dropdown-menu": "^2.1.15", "@radix-ui/react-hover-card": "^1.1.14", "@radix-ui/react-label": "^2.1.7", + "@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-scroll-area": "^1.2.10", "@radix-ui/react-select": "^2.2.5", "@radix-ui/react-separator": "^1.1.7", @@ -31,6 +35,7 @@ "@radix-ui/react-tabs": "^1.1.12", "@radix-ui/react-tooltip": "^1.2.8", "@tailwindcss/line-clamp": "^0.4.4", + "@tanstack/react-query": "^5.96.2", "@tanstack/react-virtual": "^3.13.12", "@types/dagre": "^0.7.53", "@types/react-router-dom": "^5.3.3", @@ -46,15 +51,12 @@ "react-dom": "^19.1.0", "react-markdown": "^10.1.0", "react-router-dom": "^7.6.2", - "recharts": "^3.2.1", + "recharts": "^2.15.4", "remark-gfm": "^4.0.1", "tailwind-merge": "^3.3.1", "tailwindcss-animate": "^1.0.7", "vaul": "^1.1.2", - "zod": "^4.1.12", - "@deck.gl/core": "^9.2.0", - "@deck.gl/layers": "^9.2.0", - "@deck.gl/react": "^9.2.0" + "zod": "^4.1.12" }, "devDependencies": { "@eslint/js": "^9.25.0", diff --git a/control-plane/web/client/pnpm-lock.yaml b/control-plane/web/client/pnpm-lock.yaml index 9316a88b6..fed11c93c 100644 --- a/control-plane/web/client/pnpm-lock.yaml +++ b/control-plane/web/client/pnpm-lock.yaml @@ -8,9 +8,27 @@ importers: .: dependencies: - '@carbon/icons-react': - specifier: ^11.62.0 - version: 11.67.0(react@19.1.1) + '@autoform/react': + specifier: ^4.0.0 + version: 4.0.0(@hookform/resolvers@3.10.0(react-hook-form@7.72.1(react@19.1.1)))(react-hook-form@7.72.1(react@19.1.1))(react@19.1.1) + '@autoform/shadcn': + specifier: ^1.0.1 + version: 1.0.1(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(jiti@1.21.7)(postcss@8.5.6)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(tailwindcss@3.4.17)(typescript@5.8.3)(yaml@2.8.1) + '@autoform/zod': + specifier: ^5.0.0 + version: 5.0.0(zod@4.3.6) + '@deck.gl/core': + specifier: ^9.2.0 + version: 9.2.11 + '@deck.gl/layers': + specifier: ^9.2.0 + version: 9.2.11(@deck.gl/core@9.2.11)(@loaders.gl/core@4.3.4)(@luma.gl/core@9.2.6)(@luma.gl/engine@9.2.6(@luma.gl/core@9.2.6)(@luma.gl/shadertools@9.2.6(@luma.gl/core@9.2.6))) + '@deck.gl/react': + specifier: ^9.2.0 + version: 9.2.11(@deck.gl/core@9.2.11)(@deck.gl/widgets@9.2.11(@deck.gl/core@9.2.11)(@luma.gl/core@9.2.6))(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@phosphor-icons/react': + specifier: ^2.1.10 + version: 2.1.10(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@radix-ui/react-checkbox': specifier: ^1.3.2 version: 1.3.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) @@ -29,6 +47,9 @@ importers: '@radix-ui/react-label': specifier: ^2.1.7 version: 2.1.7(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-popover': + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@radix-ui/react-scroll-area': specifier: ^1.2.10 version: 1.2.10(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) @@ -53,6 +74,9 @@ importers: '@tailwindcss/line-clamp': specifier: ^0.4.4 version: 0.4.4(tailwindcss@3.4.17) + '@tanstack/react-query': + specifier: ^5.96.2 + version: 5.96.2(react@19.1.1) '@tanstack/react-virtual': specifier: ^3.13.12 version: 3.13.12(react-dom@19.1.1(react@19.1.1))(react@19.1.1) @@ -83,6 +107,9 @@ importers: lucide-react: specifier: ^0.516.0 version: 0.516.0(react@19.1.1) + next-themes: + specifier: ^0.4.6 + version: 0.4.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react: specifier: ^19.1.0 version: 19.1.1 @@ -96,8 +123,8 @@ importers: specifier: ^7.6.2 version: 7.9.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) recharts: - specifier: ^3.2.1 - version: 3.3.0(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react-is@16.13.1)(react@19.1.1)(redux@5.0.1) + specifier: ^2.15.4 + version: 2.15.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) remark-gfm: specifier: ^4.0.1 version: 4.0.1 @@ -110,10 +137,22 @@ importers: vaul: specifier: ^1.1.2 version: 1.1.2(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + zod: + specifier: ^4.1.12 + version: 4.3.6 devDependencies: '@eslint/js': specifier: ^9.25.0 version: 9.36.0 + '@testing-library/jest-dom': + specifier: ^6.6.3 + version: 6.9.1 + '@testing-library/react': + specifier: ^16.3.0 + version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@testing-library/user-event': + specifier: ^14.5.2 + version: 14.6.1(@testing-library/dom@10.4.1) '@types/node': specifier: ^24.0.3 version: 24.5.2 @@ -126,6 +165,9 @@ importers: '@vitejs/plugin-react': specifier: ^4.4.1 version: 4.7.0(vite@6.3.6(@types/node@24.5.2)(jiti@1.21.7)(yaml@2.8.1)) + '@vitest/coverage-v8': + specifier: ^3.1.4 + version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.5.2)(jiti@1.21.7)(jsdom@26.1.0)(yaml@2.8.1)) autoprefixer: specifier: ^10.4.21 version: 10.4.21(postcss@8.5.6) @@ -141,6 +183,9 @@ importers: globals: specifier: ^16.0.0 version: 16.4.0 + jsdom: + specifier: ^26.1.0 + version: 26.1.0 postcss: specifier: ^8.5.6 version: 8.5.6 @@ -159,13 +204,46 @@ importers: vite: specifier: ^6.3.5 version: 6.3.6(@types/node@24.5.2)(jiti@1.21.7)(yaml@2.8.1) + vitest: + specifier: ^3.1.4 + version: 3.2.4(@types/debug@4.1.12)(@types/node@24.5.2)(jiti@1.21.7)(jsdom@26.1.0)(yaml@2.8.1) packages: + '@adobe/css-tools@4.4.4': + resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==} + '@alloc/quick-lru@5.2.0': resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@asamuzakjp/css-color@3.2.0': + resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} + + '@autoform/core@3.0.0': + resolution: {integrity: sha512-wBBJgqhw2lDc8VCf4Om/ONZH7RyvaLoqkAcNaU+c8Qo+U5kfY7Fke1mltW6CV0njoWMLarQ+0ZKnWdPRulDpgA==} + + '@autoform/react@4.0.0': + resolution: {integrity: sha512-OVYRY7u+KnNxEZY6RF4tOmKgO3R68Y0amqyourU9vrFL/mi/4bW/6mi6hkWK8HwHK6a8C7clSm73/4nGQPSuaw==} + peerDependencies: + '@hookform/resolvers': ^3.9.0 + react: ^16.8.0 || ^17 || ^18 || ^19 + react-hook-form: ^7 + + '@autoform/shadcn@1.0.1': + resolution: {integrity: sha512-jveLAxZuqtoI7WkhFjRvo+NxdweXExBEWH5MyzuG2Ph93fBWaq/CkumhY9iEu7Jy8rgWf2GmYMGlHP15q4tnUw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + + '@autoform/zod@5.0.0': + resolution: {integrity: sha512-n9mxQAVbaPS3uP6Laxn/36lDv74pmkoRS4nIOKEohFg5me3nLFXrnOnrSk4aENjYpolhB5JuGWUMIwgEzRK8fg==} + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + '@babel/code-frame@7.27.1': resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} @@ -237,6 +315,10 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/runtime@7.29.2': + resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} + engines: {node: '>=6.9.0'} + '@babel/template@7.27.2': resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} @@ -249,13 +331,62 @@ packages: resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==} engines: {node: '>=6.9.0'} - '@carbon/icon-helpers@10.66.0': - resolution: {integrity: sha512-GEWUdmDv8f6eOcUCnPlIsIxPtde88x0W6v5AgqdC9CALegZy8pS/TkzD3KFPEuQo1vRWadWnMBMQe7wrPn2lMA==} + '@bcoe/v8-coverage@1.0.2': + resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} + engines: {node: '>=18'} + + '@csstools/color-helpers@5.1.0': + resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} + engines: {node: '>=18'} + + '@csstools/css-calc@2.1.4': + resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-color-parser@3.1.0': + resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-parser-algorithms@3.0.5': + resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-tokenizer@3.0.4': + resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} + engines: {node: '>=18'} + + '@deck.gl/core@9.2.11': + resolution: {integrity: sha512-lpdxXQuFSkd6ET7M6QxPI8QMhsLRY6vzLyk83sPGFb7JSb4OhrNHYt9sfIhcA/hxJW7bdBSMWWphf2GvQetVuA==} + + '@deck.gl/layers@9.2.11': + resolution: {integrity: sha512-2FSb0Qa6YR+Rg6GWhYOGTUug3vtZ4uKcFdnrdiJoVXGyibKJMScKZIsivY0r/yQQZsaBjYqty5QuVJvdtEHxSA==} + peerDependencies: + '@deck.gl/core': ~9.2.0 + '@loaders.gl/core': ~4.3.4 + '@luma.gl/core': ~9.2.6 + '@luma.gl/engine': ~9.2.6 + + '@deck.gl/react@9.2.11': + resolution: {integrity: sha512-7xrXlM++3A7cKZdDKSW2/EPT6sc0ob7J24sTPaHe3YlIIFvCZTkQ1U1rAT9cN2gjhkI6XsE+TugUsqhx4TGwHQ==} + peerDependencies: + '@deck.gl/core': ~9.2.0 + '@deck.gl/widgets': ~9.2.0 + react: '>=16.3.0' + react-dom: '>=16.3.0' - '@carbon/icons-react@11.67.0': - resolution: {integrity: sha512-m/wFkfWGGhe56ufzLfpeRrO1x9DZrGdBO2Lcm1b+ydgbAMh6NcoUOgADNLmQ48jm8aMovS1pEfHrORCRqiUQwg==} + '@deck.gl/widgets@9.2.11': + resolution: {integrity: sha512-90HWlQPsiRyTPWR4aYfLwnYDrJdHG2mqCzRcyMUKewWBNQLu4upB//l4ewIkUeXXCzAprjjVeRnNb7wdYj2CXQ==} peerDependencies: - react: '>=16' + '@deck.gl/core': ~9.2.0 + '@luma.gl/core': ~9.2.6 '@esbuild/aix-ppc64@0.25.10': resolution: {integrity: sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==} @@ -263,156 +394,312 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.27.7': + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.25.10': resolution: {integrity: sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==} engines: {node: '>=18'} cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.27.7': + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.25.10': resolution: {integrity: sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==} engines: {node: '>=18'} cpu: [arm] os: [android] + '@esbuild/android-arm@0.27.7': + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.25.10': resolution: {integrity: sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==} engines: {node: '>=18'} cpu: [x64] os: [android] + '@esbuild/android-x64@0.27.7': + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.25.10': resolution: {integrity: sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.27.7': + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.25.10': resolution: {integrity: sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==} engines: {node: '>=18'} cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.27.7': + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.25.10': resolution: {integrity: sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.27.7': + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.25.10': resolution: {integrity: sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.27.7': + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/linux-arm64@0.25.10': resolution: {integrity: sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==} engines: {node: '>=18'} cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.27.7': + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.25.10': resolution: {integrity: sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==} engines: {node: '>=18'} cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.27.7': + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.25.10': resolution: {integrity: sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==} engines: {node: '>=18'} cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.27.7': + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.25.10': resolution: {integrity: sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==} engines: {node: '>=18'} cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.27.7': + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.25.10': resolution: {integrity: sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.27.7': + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.25.10': resolution: {integrity: sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.27.7': + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.25.10': resolution: {integrity: sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.27.7': + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.25.10': resolution: {integrity: sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==} engines: {node: '>=18'} cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.27.7': + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.25.10': resolution: {integrity: sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==} engines: {node: '>=18'} cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.27.7': + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/netbsd-arm64@0.25.10': resolution: {integrity: sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-arm64@0.27.7': + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-x64@0.25.10': resolution: {integrity: sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.27.7': + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/openbsd-arm64@0.25.10': resolution: {integrity: sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-arm64@0.27.7': + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-x64@0.25.10': resolution: {integrity: sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.27.7': + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/openharmony-arm64@0.25.10': resolution: {integrity: sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] + '@esbuild/openharmony-arm64@0.27.7': + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/sunos-x64@0.25.10': resolution: {integrity: sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==} engines: {node: '>=18'} cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.25.10': resolution: {integrity: sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==} engines: {node: '>=18'} cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.27.7': + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.25.10': resolution: {integrity: sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==} engines: {node: '>=18'} cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.27.7': + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.25.10': resolution: {integrity: sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==} engines: {node: '>=18'} cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.27.7': + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.9.0': resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -466,6 +753,11 @@ packages: '@floating-ui/utils@0.2.10': resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + '@hookform/resolvers@3.10.0': + resolution: {integrity: sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==} + peerDependencies: + react-hook-form: ^7.0.0 + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -482,14 +774,14 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} - '@ibm/telemetry-js@1.10.2': - resolution: {integrity: sha512-F8+/NNUwtm8BuFz18O9KPvIFTFDo8GUSoyhPxPjEpk7nEyEzWGfhIiEPhL00B2NdHRLDSljh3AiCfSnL/tutiQ==} - hasBin: true - '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} + '@istanbuljs/schema@0.1.3': + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -506,6 +798,69 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@loaders.gl/core@4.3.4': + resolution: {integrity: sha512-cG0C5fMZ1jyW6WCsf4LoHGvaIAJCEVA/ioqKoYRwoSfXkOf+17KupK1OUQyUCw5XoRn+oWA1FulJQOYlXnb9Gw==} + + '@loaders.gl/images@4.3.4': + resolution: {integrity: sha512-qgc33BaNsqN9cWa/xvcGvQ50wGDONgQQdzHCKDDKhV2w/uptZoR5iofJfuG8UUV2vUMMd82Uk9zbopRx2rS4Ag==} + peerDependencies: + '@loaders.gl/core': ^4.3.0 + + '@loaders.gl/loader-utils@4.3.4': + resolution: {integrity: sha512-tjMZvlKQSaMl2qmYTAxg+ySR6zd6hQn5n3XaU8+Ehp90TD3WzxvDKOMNDqOa72fFmIV+KgPhcmIJTpq4lAdC4Q==} + peerDependencies: + '@loaders.gl/core': ^4.3.0 + + '@loaders.gl/schema@4.3.4': + resolution: {integrity: sha512-1YTYoatgzr/6JTxqBLwDiD3AVGwQZheYiQwAimWdRBVB0JAzych7s1yBuE0CVEzj4JDPKOzVAz8KnU1TiBvJGw==} + peerDependencies: + '@loaders.gl/core': ^4.3.0 + + '@loaders.gl/worker-utils@4.3.4': + resolution: {integrity: sha512-EbsszrASgT85GH3B7jkx7YXfQyIYo/rlobwMx6V3ewETapPUwdSAInv+89flnk5n2eu2Lpdeh+2zS6PvqbL2RA==} + peerDependencies: + '@loaders.gl/core': ^4.3.0 + + '@luma.gl/constants@9.2.6': + resolution: {integrity: sha512-rvFFrJuSm5JIWbDHFuR4Q2s4eudO3Ggsv0TsGKn9eqvO7bBiPm/ANugHredvh3KviEyYuMZZxtfJvBdr3kzldg==} + + '@luma.gl/core@9.2.6': + resolution: {integrity: sha512-d8KcH8ZZcjDAodSN/G2nueA9YE2X8kMz7Q0OxDGpCww6to1MZXM3Ydate/Jqsb5DDKVgUF6yD6RL8P5jOki9Yw==} + + '@luma.gl/engine@9.2.6': + resolution: {integrity: sha512-1AEDs2AUqOWh7Wl4onOhXmQF+Rz1zNdPXF+Kxm4aWl92RQ42Sh2CmTvRt2BJku83VQ91KFIEm/v3qd3Urzf+Uw==} + peerDependencies: + '@luma.gl/core': ~9.2.0 + '@luma.gl/shadertools': ~9.2.0 + + '@luma.gl/shadertools@9.2.6': + resolution: {integrity: sha512-4+uUbynqPUra9d/z1nQChyHmhLgmKfSMjS7kOwLB6exSnhKnpHL3+Hu9fv55qyaX50nGH3oHawhGtJ6RRvu65w==} + peerDependencies: + '@luma.gl/core': ~9.2.0 + + '@luma.gl/webgl@9.2.6': + resolution: {integrity: sha512-NGBTdxJMk7j8Ygr1zuTyAvr1Tw+EpupMIQo7RelFjEsZXg6pujFqiDMM+rgxex8voCeuhWBJc7Rs+MoSqd46UQ==} + peerDependencies: + '@luma.gl/core': ~9.2.0 + + '@mapbox/tiny-sdf@2.0.7': + resolution: {integrity: sha512-25gQLQMcpivjOSA40g3gO6qgiFPDpWRoMfd+G/GoppPIeP6JDaMMkMrEJnMZhKyyS6iKwVt5YKu02vCUyJM3Ug==} + + '@math.gl/core@4.1.0': + resolution: {integrity: sha512-FrdHBCVG3QdrworwrUSzXIaK+/9OCRLscxI2OUy6sLOHyHgBMyfnEGs99/m3KNvs+95BsnQLWklVfpKfQzfwKA==} + + '@math.gl/polygon@4.1.0': + resolution: {integrity: sha512-YA/9PzaCRHbIP5/0E9uTYrqe+jsYTQoqoDWhf6/b0Ixz8bPZBaGDEafLg3z7ffBomZLacUty9U3TlPjqMtzPjA==} + + '@math.gl/sun@4.1.0': + resolution: {integrity: sha512-i3q6OCBLSZ5wgZVhXg+X7gsjY/TUtuFW/2KBiq/U1ypLso3S4sEykoU/MGjxUv1xiiGtr+v8TeMbO1OBIh/HmA==} + + '@math.gl/types@4.1.0': + resolution: {integrity: sha512-clYZdHcmRvMzVK5fjeDkQlHUzXQSNdZ7s4xOqC3nJPgz4C/TZkUecTo9YS4PruZqtDda/ag4erndP0MIn40dGA==} + + '@math.gl/web-mercator@4.1.0': + resolution: {integrity: sha512-HZo3vO5GCMkXJThxRJ5/QYUYRr3XumfT8CzNNCwoJfinxy5NtKUd7dusNTXn7yJ40UoB8FMIwkVwNlqaiRZZAw==} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -518,10 +873,26 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@phosphor-icons/react@2.1.10': + resolution: {integrity: sha512-vt8Tvq8GLjheAZZYa+YG/pW7HDbov8El/MANW8pOAz4eGxrwhnbfrQZq0Cp4q8zBEu8NIhHdnr+r8thnfRSNYA==} + engines: {node: '>=10'} + peerDependencies: + react: '>= 16.8' + react-dom: '>= 16.8' + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@probe.gl/env@4.1.1': + resolution: {integrity: sha512-+68seNDMVsEegRB47pFA/Ws1Fjy8agcFYXxzorKToyPcD6zd+gZ5uhwoLd7TzsSw6Ydns//2KEszWn+EnNHTbA==} + + '@probe.gl/log@4.1.1': + resolution: {integrity: sha512-kcZs9BT44pL7hS1OkRGKYRXI/SN9KejUlPD+BY40DguRLzdC5tLG/28WGMyfKdn/51GT4a0p+0P8xvDn1Ez+Kg==} + + '@probe.gl/stats@4.1.1': + resolution: {integrity: sha512-4VpAyMHOqydSvPlEyHwXaE+AkIdR03nX+Qhlxsk2D/IW4OVmDZgIsvJB1cDzyEEtcfKcnaEbfXeiPgejBceT6g==} + '@radix-ui/number@1.1.1': resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} @@ -716,6 +1087,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-popover@1.1.15': + resolution: {integrity: sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-popper@1.2.8': resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==} peerDependencies: @@ -855,6 +1239,32 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-toggle-group@1.1.11': + resolution: {integrity: sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toggle@1.1.10': + resolution: {integrity: sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-tooltip@1.2.8': resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==} peerDependencies: @@ -956,17 +1366,6 @@ packages: '@radix-ui/rect@1.1.1': resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} - '@reduxjs/toolkit@2.9.2': - resolution: {integrity: sha512-ZAYu/NXkl/OhqTz7rfPaAhY0+e8Fr15jqNxte/2exKUxvHyQ/hcqmdekiN1f+Lcw3pE+34FCgX+26zcUE3duCg==} - peerDependencies: - react: ^16.9.0 || ^17.0.0 || ^18 || ^19 - react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0 - peerDependenciesMeta: - react: - optional: true - react-redux: - optional: true - '@rolldown/pluginutils@1.0.0-beta.27': resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} @@ -1080,17 +1479,19 @@ packages: cpu: [x64] os: [win32] - '@standard-schema/spec@1.0.0': - resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} - - '@standard-schema/utils@0.3.0': - resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} - '@tailwindcss/line-clamp@0.4.4': resolution: {integrity: sha512-5U6SY5z8N42VtrCrKlsTAA35gy2VSyYtHWCsg1H87NU1SXnEfekTVlrga9fzUDrrHcGi2Lb5KenUWb4lRQT5/g==} peerDependencies: tailwindcss: '>=2.0.0 || >=3.0.0 || >=3.0.0-alpha.1' + '@tanstack/query-core@5.96.2': + resolution: {integrity: sha512-hzI6cTVh4KNRk8UtoIBS7Lv9g6BnJPXvBKsvYH1aGWvv0347jT3BnSvztOE+kD76XGvZnRC/t6qdW1CaIfwCeA==} + + '@tanstack/react-query@5.96.2': + resolution: {integrity: sha512-sYyzzJT4G0g02azzJ8o55VFFV31XvFpdUpG+unxS0vSaYsJnSPKGoI6WdPwUucJL1wpgGfwfmntNX/Ub1uOViA==} + peerDependencies: + react: ^18 || ^19 + '@tanstack/react-virtual@3.13.12': resolution: {integrity: sha512-Gd13QdxPSukP8ZrkbgS2RwoZseTTbQPLnQEn7HY/rqtM+8Zt95f7xKC7N0EsKs7aoz0WzZ+fditZux+F8EzYxA==} peerDependencies: @@ -1100,6 +1501,38 @@ packages: '@tanstack/virtual-core@3.13.12': resolution: {integrity: sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA==} + '@testing-library/dom@10.4.1': + resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} + engines: {node: '>=18'} + + '@testing-library/jest-dom@6.9.1': + resolution: {integrity: sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==} + engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + + '@testing-library/react@16.3.2': + resolution: {integrity: sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==} + engines: {node: '>=18'} + peerDependencies: + '@testing-library/dom': ^10.0.0 + '@types/react': ^18.0.0 || ^19.0.0 + '@types/react-dom': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@testing-library/user-event@14.6.1': + resolution: {integrity: sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==} + engines: {node: '>=12', npm: '>=6'} + peerDependencies: + '@testing-library/dom': '>=7.21.4' + + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -1112,6 +1545,9 @@ packages: '@types/babel__traverse@7.28.0': resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + '@types/d3-array@3.2.2': resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} @@ -1157,12 +1593,18 @@ packages: '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/estree-jsx@1.0.5': resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/geojson@7946.0.16': + resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} + '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} @@ -1181,6 +1623,9 @@ packages: '@types/node@24.5.2': resolution: {integrity: sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==} + '@types/offscreencanvas@2019.7.3': + resolution: {integrity: sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==} + '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} @@ -1204,9 +1649,6 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} - '@types/use-sync-external-store@0.0.6': - resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} - '@typescript-eslint/eslint-plugin@8.44.0': resolution: {integrity: sha512-EGDAOGX+uwwekcS0iyxVDmRV9HX6FLSM5kzrAToLTsr9OWCIKG/y3lQheCq18yZ5Xh78rRKJiEpP0ZaCs4ryOQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1275,6 +1717,44 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + '@vitest/coverage-v8@3.2.4': + resolution: {integrity: sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==} + peerDependencies: + '@vitest/browser': 3.2.4 + vitest: 3.2.4 + peerDependenciesMeta: + '@vitest/browser': + optional: true + + '@vitest/expect@3.2.4': + resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} + + '@vitest/mocker@3.2.4': + resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@3.2.4': + resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} + + '@vitest/runner@3.2.4': + resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} + + '@vitest/snapshot@3.2.4': + resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} + + '@vitest/spy@3.2.4': + resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + + '@vitest/utils@3.2.4': + resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + '@xyflow/react@12.8.5': resolution: {integrity: sha512-NRwcE8QE7dh6BbaIT7GmNccP7/RMDZJOKtzK4HQw599TAfzC8e5E/zw/7MwtpnSbbkqBYc+jZyOisd57sp/hPQ==} peerDependencies: @@ -1306,6 +1786,15 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} @@ -1321,6 +1810,10 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + ansi-styles@6.2.3: resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} @@ -1342,6 +1835,20 @@ packages: resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} engines: {node: '>=10'} + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + ast-v8-to-istanbul@0.3.12: + resolution: {integrity: sha512-BRRC8VRZY2R4Z4lFIL35MwNXmwVqBityvOIwETtsCSwvjl0IdgFsy9NhdaA6j74nUdtJJlIypeRhpDam19Wq3g==} + autoprefixer@10.4.21: resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==} engines: {node: ^10 || ^12 || >=14} @@ -1355,6 +1862,10 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + baseline-browser-mapping@2.8.6: resolution: {integrity: sha512-wrH5NNqren/QMtKUEEJf7z86YjfqW/2uw3IL3/xpqZUC95SSVIFXYQeeGjL6FT/X68IROu6RMehZQS5foy2BXw==} hasBin: true @@ -1369,6 +1880,10 @@ packages: brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + brace-expansion@5.0.5: + resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} + engines: {node: 18 || 20 || >=22} + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -1378,10 +1893,20 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + bundle-require@5.1.0: + resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + peerDependencies: + esbuild: '>=0.18' + bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -1396,6 +1921,10 @@ packages: ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + engines: {node: '>=18'} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -1412,10 +1941,18 @@ packages: character-reference-invalid@2.0.1: resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + check-error@2.1.3: + resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} + engines: {node: '>= 16'} + chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} @@ -1460,6 +1997,13 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -1481,11 +2025,18 @@ packages: css-unit-converter@1.1.2: resolution: {integrity: sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA==} + css.escape@1.5.1: + resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} hasBin: true + cssstyle@4.6.0: + resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==} + engines: {node: '>=18'} + csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} @@ -1558,6 +2109,13 @@ packages: dagre@0.8.5: resolution: {integrity: sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==} + data-urls@5.0.0: + resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} + engines: {node: '>=18'} + + date-fns@3.6.0: + resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -1570,9 +2128,16 @@ packages: decimal.js-light@2.5.1: resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + decode-named-character-reference@1.2.0: resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==} + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -1600,6 +2165,18 @@ packages: dlv@1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + + dom-accessibility-api@0.6.3: + resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + + dom-helpers@5.2.1: + resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} + + earcut@2.2.4: + resolution: {integrity: sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==} + eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -1615,17 +2192,26 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + error-ex@1.3.4: resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} - es-toolkit@1.40.0: - resolution: {integrity: sha512-8o6w0KFmU0CiIl0/Q/BCEOabF2IJaELM1T2PWj6e8KqzHv1gdx+7JtFnDwOx1kJH/isJ5NwlDG1nCr1HrRF94Q==} + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} esbuild@0.25.10: resolution: {integrity: sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==} engines: {node: '>=18'} hasBin: true + esbuild@0.27.7: + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -1690,12 +2276,19 @@ packages: estree-util-is-identifier-name@3.0.0: resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} - eventemitter3@5.0.1: - resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} @@ -1703,6 +2296,10 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-equals@5.4.0: + resolution: {integrity: sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==} + engines: {node: '>=6.0.0'} + fast-glob@3.3.3: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} @@ -1737,6 +2334,9 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} + fix-dts-default-cjs-exports@1.0.1: + resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} + flat-cache@4.0.1: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} @@ -1774,6 +2374,9 @@ packages: resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} engines: {node: '>=6'} + gl-matrix@3.4.4: + resolution: {integrity: sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -1784,11 +2387,12 @@ packages: glob@10.4.5: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me globals@14.0.0: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} @@ -1830,6 +2434,13 @@ packages: hsla-regex@1.0.0: resolution: {integrity: sha512-7Wn5GMLuHBjZCb2bTmnDOycho0p/7UVaAeqXZGbHrBCl6Yd/xDhQJAXe6Ga9AXJH2I5zY1dEdYw2u1UptnSBJA==} + html-encoding-sniffer@4.0.0: + resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} + engines: {node: '>=18'} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + html-tags@3.3.1: resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==} engines: {node: '>=8'} @@ -1837,6 +2448,18 @@ packages: html-url-attributes@3.0.1: resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -1856,6 +2479,10 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. @@ -1919,9 +2546,28 @@ packages: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@5.0.6: + resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} @@ -1929,13 +2575,32 @@ packages: resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} hasBin: true + joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + + js-tokens@10.0.0: + resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true + jsdom@26.1.0: + resolution: {integrity: sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==} + engines: {node: '>=18'} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} @@ -1979,6 +2644,10 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + load-tsconfig@0.2.5: + resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -1999,6 +2668,9 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -2010,6 +2682,20 @@ packages: peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + magicast@0.3.5: + resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + markdown-table@3.0.4: resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} @@ -2150,6 +2836,14 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -2164,6 +2858,12 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} + mjolnir.js@3.0.0: + resolution: {integrity: sha512-siX3YCG7N2HnmN1xMH3cK4JkUZJhbkhRFJL+G5N1vH0mh1t5088rJknIoqDFWDIU6NPGvRRgLnYW3ZHjSMEBLA==} + + mlly@1.8.2: + resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} + modern-normalize@1.1.0: resolution: {integrity: sha512-2lMlY1Yc1+CUy0gw4H95uNN7vjbpoED7NNRSBHE25nWfLBdmMzFCsPshlzbxHz+gYMcBEUN8V4pU16prcdPSgA==} engines: {node: '>=6'} @@ -2182,6 +2882,12 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + next-themes@0.4.6: + resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==} + peerDependencies: + react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc + react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc + node-emoji@1.11.0: resolution: {integrity: sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==} @@ -2196,6 +2902,9 @@ packages: resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} engines: {node: '>=0.10.0'} + nwsapi@2.2.23: + resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==} + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -2237,6 +2946,9 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} + parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -2260,6 +2972,13 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -2279,6 +2998,9 @@ packages: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + postcss-import@15.1.0: resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} engines: {node: '>=14.0.0'} @@ -2319,6 +3041,24 @@ packages: ts-node: optional: true + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + postcss-nested@5.0.6: resolution: {integrity: sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==} engines: {node: '>=12.0'} @@ -2345,10 +3085,17 @@ packages: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} + preact@10.29.1: + resolution: {integrity: sha512-gQCLc/vWroE8lIpleXtdJhTFDogTdZG9AjMUpVkDf2iTCNwYNWA+u16dL41TqUDJO4gm2IgrcMv3uTpjd4Pwmg==} + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + pretty-hrtime@1.0.3: resolution: {integrity: sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==} engines: {node: '>= 0.8'} @@ -2374,32 +3121,38 @@ packages: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} + react-day-picker@8.10.1: + resolution: {integrity: sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA==} + peerDependencies: + date-fns: ^2.28.0 || ^3.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom@19.1.1: resolution: {integrity: sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==} peerDependencies: react: ^19.1.1 + react-hook-form@7.72.1: + resolution: {integrity: sha512-RhwBoy2ygeVZje+C+bwJ8g0NjTdBmDlJvAUHTxRjTmSUKPYsKfMphkS2sgEMotsY03bP358yEYlnUeZy//D9Ig==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^16.8.0 || ^17 || ^18 || ^19 + react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + react-markdown@10.1.0: resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==} peerDependencies: '@types/react': '>=18' react: '>=18' - react-redux@9.2.0: - resolution: {integrity: sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==} - peerDependencies: - '@types/react': ^18.2.25 || ^19 - react: ^18.0 || ^19 - redux: ^5.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - redux: - optional: true - react-refresh@0.17.0: resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} engines: {node: '>=0.10.0'} @@ -2441,6 +3194,12 @@ packages: react-dom: optional: true + react-smooth@4.0.4: + resolution: {integrity: sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-style-singleton@2.2.3: resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} engines: {node: '>=10'} @@ -2451,6 +3210,12 @@ packages: '@types/react': optional: true + react-transition-group@4.4.5: + resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} + peerDependencies: + react: '>=16.6.0' + react-dom: '>=16.6.0' + react@19.1.1: resolution: {integrity: sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==} engines: {node: '>=0.10.0'} @@ -2462,25 +3227,27 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} - recharts@3.3.0: - resolution: {integrity: sha512-Vi0qmTB0iz1+/Cz9o5B7irVyUjX2ynvEgImbgMt/3sKRREcUM07QiYjS1QpAVrkmVlXqy5gykq4nGWMz9AS4Rg==} - engines: {node: '>=18'} + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + recharts-scale@0.4.5: + resolution: {integrity: sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==} + + recharts@2.15.4: + resolution: {integrity: sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==} + engines: {node: '>=14'} peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-is: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} reduce-css-calc@2.1.8: resolution: {integrity: sha512-8liAVezDmUcH+tdzoEGrhfbGcP7nOV4NkGE3a74+qqvE7nt9i4sKLGBuZNOnpI4WiGksiNPklZxva80061QiPg==} - redux-thunk@3.1.0: - resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==} - peerDependencies: - redux: ^5.0.0 - - redux@5.0.1: - resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==} - remark-gfm@4.0.1: resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} @@ -2493,13 +3260,14 @@ packages: remark-stringify@11.0.0: resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} - reselect@5.1.1: - resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==} - resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + resolve@1.22.10: resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} engines: {node: '>= 0.4'} @@ -2520,9 +3288,19 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + rrweb-cssom@0.8.0: + resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + scheduler@0.26.0: resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} @@ -2546,6 +3324,9 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} @@ -2557,9 +3338,19 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + space-separated-tokens@2.0.2: resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -2579,10 +3370,17 @@ packages: resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} engines: {node: '>=12'} + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + strip-literal@3.1.0: + resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} + style-to-js@1.1.17: resolution: {integrity: sha512-xQcBGDxJb6jjFCTzvQtfiPn6YvvP2O8U1MDIPNfJQlWMYfktPy+iGsHE7cssjs7y84d9fQaK4UF3RIJaAHSoYA==} @@ -2602,6 +3400,12 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + + tailwind-merge@2.6.1: + resolution: {integrity: sha512-Oo6tHdpZsGpkKG88HJ8RR1rg/RdnEkQEfMoEk2x1XRI3F1AxeU+ijRXpiVUF4UbLfcxxRGw6TbUINKYdWVsQTQ==} + tailwind-merge@3.3.1: resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==} @@ -2627,6 +3431,10 @@ packages: engines: {node: '>=14.0.0'} hasBin: true + test-exclude@7.0.2: + resolution: {integrity: sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==} + engines: {node: '>=18'} + thenify-all@1.6.0: resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} engines: {node: '>=0.8'} @@ -2637,10 +3445,35 @@ packages: tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + + tinyspy@4.0.4: + resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} + engines: {node: '>=14.0.0'} + + tldts-core@6.1.86: + resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==} + + tldts@6.1.86: + resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==} + hasBin: true + tmp@0.2.5: resolution: {integrity: sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==} engines: {node: '>=14.14'} @@ -2649,6 +3482,18 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + tough-cookie@5.1.2: + resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==} + engines: {node: '>=16'} + + tr46@5.1.1: + resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} + engines: {node: '>=18'} + + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} @@ -2667,6 +3512,25 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsup@8.5.1: + resolution: {integrity: sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + '@microsoft/api-extractor': ^7.36.0 + '@swc/core': ^1 + postcss: ^8.4.12 + typescript: '>=4.5.0' + peerDependenciesMeta: + '@microsoft/api-extractor': + optional: true + '@swc/core': + optional: true + postcss: + optional: true + typescript: + optional: true + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -2683,6 +3547,9 @@ packages: engines: {node: '>=14.17'} hasBin: true + ufo@1.6.3: + resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} + undici-types@7.12.0: resolution: {integrity: sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==} @@ -2757,8 +3624,13 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - victory-vendor@37.3.6: - resolution: {integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==} + victory-vendor@36.9.2: + resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==} + + vite-node@3.2.4: + resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true vite@6.3.6: resolution: {integrity: sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA==} @@ -2800,11 +3672,68 @@ packages: yaml: optional: true + vitest@3.2.4: + resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.2.4 + '@vitest/ui': 3.2.4 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + + wgsl_reflect@1.2.3: + resolution: {integrity: sha512-BQWBIsOn411M+ffBxmA6QRLvAOVbuz3Uk4NusxnqC1H7aeQcVLhdA3k2k/EFFFtqVjhz3z7JOOZF1a9hj2tv4Q==} + + whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation + + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + + whatwg-url@14.2.0: + resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} + engines: {node: '>=18'} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} hasBin: true + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} @@ -2820,6 +3749,25 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + ws@8.20.0: + resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} @@ -2840,6 +3788,12 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + + zod@4.3.6: + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + zustand@4.5.7: resolution: {integrity: sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==} engines: {node: '>=12.7.0'} @@ -2860,8 +3814,78 @@ packages: snapshots: + '@adobe/css-tools@4.4.4': {} + '@alloc/quick-lru@5.2.0': {} + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@asamuzakjp/css-color@3.2.0': + dependencies: + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + lru-cache: 10.4.3 + + '@autoform/core@3.0.0': {} + + '@autoform/react@4.0.0(@hookform/resolvers@3.10.0(react-hook-form@7.72.1(react@19.1.1)))(react-hook-form@7.72.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@autoform/core': 3.0.0 + '@hookform/resolvers': 3.10.0(react-hook-form@7.72.1(react@19.1.1)) + react: 19.1.1 + react-hook-form: 7.72.1(react@19.1.1) + + '@autoform/shadcn@1.0.1(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(jiti@1.21.7)(postcss@8.5.6)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(tailwindcss@3.4.17)(typescript@5.8.3)(yaml@2.8.1)': + dependencies: + '@autoform/core': 3.0.0 + '@autoform/react': 4.0.0(@hookform/resolvers@3.10.0(react-hook-form@7.72.1(react@19.1.1)))(react-hook-form@7.72.1(react@19.1.1))(react@19.1.1) + '@autoform/zod': 5.0.0(zod@3.25.76) + '@hookform/resolvers': 3.10.0(react-hook-form@7.72.1(react@19.1.1)) + '@radix-ui/react-checkbox': 1.3.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-label': 2.1.7(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-select': 2.2.6(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.13)(react@19.1.1) + '@radix-ui/react-switch': 1.2.6(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-toggle': 1.1.10(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-toggle-group': 1.1.11(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + class-variance-authority: 0.7.1 + clsx: 2.1.1 + date-fns: 3.6.0 + lucide-react: 0.516.0(react@19.1.1) + react: 19.1.1 + react-day-picker: 8.10.1(date-fns@3.6.0)(react@19.1.1) + react-hook-form: 7.72.1(react@19.1.1) + tailwind-merge: 2.6.1 + tailwindcss-animate: 1.0.7(tailwindcss@3.4.17) + tsup: 8.5.1(jiti@1.21.7)(postcss@8.5.6)(typescript@5.8.3)(yaml@2.8.1) + zod: 3.25.76 + transitivePeerDependencies: + - '@microsoft/api-extractor' + - '@swc/core' + - '@types/react' + - '@types/react-dom' + - jiti + - postcss + - react-dom + - supports-color + - tailwindcss + - tsx + - typescript + - yaml + + '@autoform/zod@5.0.0(zod@3.25.76)': + dependencies: + zod: 3.25.76 + + '@autoform/zod@5.0.0(zod@4.3.6)': + dependencies: + zod: 4.3.6 + '@babel/code-frame@7.27.1': dependencies: '@babel/helper-validator-identifier': 7.27.1 @@ -2951,6 +3975,8 @@ snapshots: '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 + '@babel/runtime@7.29.2': {} + '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 @@ -2974,95 +4000,232 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 - '@carbon/icon-helpers@10.66.0': + '@bcoe/v8-coverage@1.0.2': {} + + '@csstools/color-helpers@5.1.0': {} + + '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': dependencies: - '@ibm/telemetry-js': 1.10.2 + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 - '@carbon/icons-react@11.67.0(react@19.1.1)': + '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': dependencies: - '@carbon/icon-helpers': 10.66.0 - '@ibm/telemetry-js': 1.10.2 - prop-types: 15.8.1 + '@csstools/color-helpers': 5.1.0 + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-tokenizer@3.0.4': {} + + '@deck.gl/core@9.2.11': + dependencies: + '@loaders.gl/core': 4.3.4 + '@loaders.gl/images': 4.3.4(@loaders.gl/core@4.3.4) + '@luma.gl/constants': 9.2.6 + '@luma.gl/core': 9.2.6 + '@luma.gl/engine': 9.2.6(@luma.gl/core@9.2.6)(@luma.gl/shadertools@9.2.6(@luma.gl/core@9.2.6)) + '@luma.gl/shadertools': 9.2.6(@luma.gl/core@9.2.6) + '@luma.gl/webgl': 9.2.6(@luma.gl/core@9.2.6) + '@math.gl/core': 4.1.0 + '@math.gl/sun': 4.1.0 + '@math.gl/types': 4.1.0 + '@math.gl/web-mercator': 4.1.0 + '@probe.gl/env': 4.1.1 + '@probe.gl/log': 4.1.1 + '@probe.gl/stats': 4.1.1 + '@types/offscreencanvas': 2019.7.3 + gl-matrix: 3.4.4 + mjolnir.js: 3.0.0 + + '@deck.gl/layers@9.2.11(@deck.gl/core@9.2.11)(@loaders.gl/core@4.3.4)(@luma.gl/core@9.2.6)(@luma.gl/engine@9.2.6(@luma.gl/core@9.2.6)(@luma.gl/shadertools@9.2.6(@luma.gl/core@9.2.6)))': + dependencies: + '@deck.gl/core': 9.2.11 + '@loaders.gl/core': 4.3.4 + '@loaders.gl/images': 4.3.4(@loaders.gl/core@4.3.4) + '@loaders.gl/schema': 4.3.4(@loaders.gl/core@4.3.4) + '@luma.gl/core': 9.2.6 + '@luma.gl/engine': 9.2.6(@luma.gl/core@9.2.6)(@luma.gl/shadertools@9.2.6(@luma.gl/core@9.2.6)) + '@luma.gl/shadertools': 9.2.6(@luma.gl/core@9.2.6) + '@mapbox/tiny-sdf': 2.0.7 + '@math.gl/core': 4.1.0 + '@math.gl/polygon': 4.1.0 + '@math.gl/web-mercator': 4.1.0 + earcut: 2.2.4 + + '@deck.gl/react@9.2.11(@deck.gl/core@9.2.11)(@deck.gl/widgets@9.2.11(@deck.gl/core@9.2.11)(@luma.gl/core@9.2.6))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@deck.gl/core': 9.2.11 + '@deck.gl/widgets': 9.2.11(@deck.gl/core@9.2.11)(@luma.gl/core@9.2.6) react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + + '@deck.gl/widgets@9.2.11(@deck.gl/core@9.2.11)(@luma.gl/core@9.2.6)': + dependencies: + '@deck.gl/core': 9.2.11 + '@luma.gl/core': 9.2.6 + preact: 10.29.1 '@esbuild/aix-ppc64@0.25.10': optional: true + '@esbuild/aix-ppc64@0.27.7': + optional: true + '@esbuild/android-arm64@0.25.10': optional: true + '@esbuild/android-arm64@0.27.7': + optional: true + '@esbuild/android-arm@0.25.10': optional: true + '@esbuild/android-arm@0.27.7': + optional: true + '@esbuild/android-x64@0.25.10': optional: true + '@esbuild/android-x64@0.27.7': + optional: true + '@esbuild/darwin-arm64@0.25.10': optional: true + '@esbuild/darwin-arm64@0.27.7': + optional: true + '@esbuild/darwin-x64@0.25.10': optional: true + '@esbuild/darwin-x64@0.27.7': + optional: true + '@esbuild/freebsd-arm64@0.25.10': optional: true + '@esbuild/freebsd-arm64@0.27.7': + optional: true + '@esbuild/freebsd-x64@0.25.10': optional: true + '@esbuild/freebsd-x64@0.27.7': + optional: true + '@esbuild/linux-arm64@0.25.10': optional: true + '@esbuild/linux-arm64@0.27.7': + optional: true + '@esbuild/linux-arm@0.25.10': optional: true + '@esbuild/linux-arm@0.27.7': + optional: true + '@esbuild/linux-ia32@0.25.10': optional: true + '@esbuild/linux-ia32@0.27.7': + optional: true + '@esbuild/linux-loong64@0.25.10': optional: true + '@esbuild/linux-loong64@0.27.7': + optional: true + '@esbuild/linux-mips64el@0.25.10': optional: true + '@esbuild/linux-mips64el@0.27.7': + optional: true + '@esbuild/linux-ppc64@0.25.10': optional: true + '@esbuild/linux-ppc64@0.27.7': + optional: true + '@esbuild/linux-riscv64@0.25.10': optional: true + '@esbuild/linux-riscv64@0.27.7': + optional: true + '@esbuild/linux-s390x@0.25.10': optional: true + '@esbuild/linux-s390x@0.27.7': + optional: true + '@esbuild/linux-x64@0.25.10': optional: true + '@esbuild/linux-x64@0.27.7': + optional: true + '@esbuild/netbsd-arm64@0.25.10': optional: true + '@esbuild/netbsd-arm64@0.27.7': + optional: true + '@esbuild/netbsd-x64@0.25.10': optional: true + '@esbuild/netbsd-x64@0.27.7': + optional: true + '@esbuild/openbsd-arm64@0.25.10': optional: true + '@esbuild/openbsd-arm64@0.27.7': + optional: true + '@esbuild/openbsd-x64@0.25.10': optional: true + '@esbuild/openbsd-x64@0.27.7': + optional: true + '@esbuild/openharmony-arm64@0.25.10': optional: true + '@esbuild/openharmony-arm64@0.27.7': + optional: true + '@esbuild/sunos-x64@0.25.10': optional: true + '@esbuild/sunos-x64@0.27.7': + optional: true + '@esbuild/win32-arm64@0.25.10': optional: true + '@esbuild/win32-arm64@0.27.7': + optional: true + '@esbuild/win32-ia32@0.25.10': optional: true + '@esbuild/win32-ia32@0.27.7': + optional: true + '@esbuild/win32-x64@0.25.10': optional: true + '@esbuild/win32-x64@0.27.7': + optional: true + '@eslint-community/eslint-utils@4.9.0(eslint@9.36.0(jiti@1.21.7))': dependencies: eslint: 9.36.0(jiti@1.21.7) @@ -3124,6 +4287,10 @@ snapshots: '@floating-ui/utils@0.2.10': {} + '@hookform/resolvers@3.10.0(react-hook-form@7.72.1(react@19.1.1))': + dependencies: + react-hook-form: 7.72.1(react@19.1.1) + '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.7': @@ -3135,8 +4302,6 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} - '@ibm/telemetry-js@1.10.2': {} - '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -3146,6 +4311,8 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 + '@istanbuljs/schema@0.1.3': {} + '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -3165,6 +4332,86 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@loaders.gl/core@4.3.4': + dependencies: + '@loaders.gl/loader-utils': 4.3.4(@loaders.gl/core@4.3.4) + '@loaders.gl/schema': 4.3.4(@loaders.gl/core@4.3.4) + '@loaders.gl/worker-utils': 4.3.4(@loaders.gl/core@4.3.4) + '@probe.gl/log': 4.1.1 + + '@loaders.gl/images@4.3.4(@loaders.gl/core@4.3.4)': + dependencies: + '@loaders.gl/core': 4.3.4 + '@loaders.gl/loader-utils': 4.3.4(@loaders.gl/core@4.3.4) + + '@loaders.gl/loader-utils@4.3.4(@loaders.gl/core@4.3.4)': + dependencies: + '@loaders.gl/core': 4.3.4 + '@loaders.gl/schema': 4.3.4(@loaders.gl/core@4.3.4) + '@loaders.gl/worker-utils': 4.3.4(@loaders.gl/core@4.3.4) + '@probe.gl/log': 4.1.1 + '@probe.gl/stats': 4.1.1 + + '@loaders.gl/schema@4.3.4(@loaders.gl/core@4.3.4)': + dependencies: + '@loaders.gl/core': 4.3.4 + '@types/geojson': 7946.0.16 + + '@loaders.gl/worker-utils@4.3.4(@loaders.gl/core@4.3.4)': + dependencies: + '@loaders.gl/core': 4.3.4 + + '@luma.gl/constants@9.2.6': {} + + '@luma.gl/core@9.2.6': + dependencies: + '@math.gl/types': 4.1.0 + '@probe.gl/env': 4.1.1 + '@probe.gl/log': 4.1.1 + '@probe.gl/stats': 4.1.1 + '@types/offscreencanvas': 2019.7.3 + + '@luma.gl/engine@9.2.6(@luma.gl/core@9.2.6)(@luma.gl/shadertools@9.2.6(@luma.gl/core@9.2.6))': + dependencies: + '@luma.gl/core': 9.2.6 + '@luma.gl/shadertools': 9.2.6(@luma.gl/core@9.2.6) + '@math.gl/core': 4.1.0 + '@math.gl/types': 4.1.0 + '@probe.gl/log': 4.1.1 + '@probe.gl/stats': 4.1.1 + + '@luma.gl/shadertools@9.2.6(@luma.gl/core@9.2.6)': + dependencies: + '@luma.gl/core': 9.2.6 + '@math.gl/core': 4.1.0 + '@math.gl/types': 4.1.0 + wgsl_reflect: 1.2.3 + + '@luma.gl/webgl@9.2.6(@luma.gl/core@9.2.6)': + dependencies: + '@luma.gl/constants': 9.2.6 + '@luma.gl/core': 9.2.6 + '@math.gl/types': 4.1.0 + '@probe.gl/env': 4.1.1 + + '@mapbox/tiny-sdf@2.0.7': {} + + '@math.gl/core@4.1.0': + dependencies: + '@math.gl/types': 4.1.0 + + '@math.gl/polygon@4.1.0': + dependencies: + '@math.gl/core': 4.1.0 + + '@math.gl/sun@4.1.0': {} + + '@math.gl/types@4.1.0': {} + + '@math.gl/web-mercator@4.1.0': + dependencies: + '@math.gl/core': 4.1.0 + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -3177,9 +4424,22 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.19.1 + '@phosphor-icons/react@2.1.10(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + '@pkgjs/parseargs@0.11.0': optional: true + '@probe.gl/env@4.1.1': {} + + '@probe.gl/log@4.1.1': + dependencies: + '@probe.gl/env': 4.1.1 + + '@probe.gl/stats@4.1.1': {} + '@radix-ui/number@1.1.1': {} '@radix-ui/primitive@1.1.3': {} @@ -3381,6 +4641,29 @@ snapshots: '@types/react': 19.1.13 '@types/react-dom': 19.1.9(@types/react@19.1.13) + '@radix-ui/react-popover@1.1.15(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.13)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.13)(react@19.1.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.1.13)(react@19.1.1) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.13)(react@19.1.1) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.13)(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.13)(react@19.1.1) + aria-hidden: 1.2.6 + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + react-remove-scroll: 2.7.1(@types/react@19.1.13)(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.13 + '@types/react-dom': 19.1.9(@types/react@19.1.13) + '@radix-ui/react-popper@1.2.8(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: '@floating-ui/react-dom': 2.1.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1) @@ -3538,6 +4821,32 @@ snapshots: '@types/react': 19.1.13 '@types/react-dom': 19.1.9(@types/react@19.1.13) + '@radix-ui/react-toggle-group@1.1.11(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@19.1.13)(react@19.1.1) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.13)(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-toggle': 1.1.10(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.13)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.13 + '@types/react-dom': 19.1.9(@types/react@19.1.13) + + '@radix-ui/react-toggle@1.1.10(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.13)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.13 + '@types/react-dom': 19.1.9(@types/react@19.1.13) + '@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -3623,18 +4932,6 @@ snapshots: '@radix-ui/rect@1.1.1': {} - '@reduxjs/toolkit@2.9.2(react-redux@9.2.0(@types/react@19.1.13)(react@19.1.1)(redux@5.0.1))(react@19.1.1)': - dependencies: - '@standard-schema/spec': 1.0.0 - '@standard-schema/utils': 0.3.0 - immer: 10.1.3 - redux: 5.0.1 - redux-thunk: 3.1.0(redux@5.0.1) - reselect: 5.1.1 - optionalDependencies: - react: 19.1.1 - react-redux: 9.2.0(@types/react@19.1.13)(react@19.1.1)(redux@5.0.1) - '@rolldown/pluginutils@1.0.0-beta.27': {} '@rollup/rollup-android-arm-eabi@4.52.0': @@ -3703,14 +5000,17 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.52.0': optional: true - '@standard-schema/spec@1.0.0': {} - - '@standard-schema/utils@0.3.0': {} - '@tailwindcss/line-clamp@0.4.4(tailwindcss@3.4.17)': dependencies: tailwindcss: 3.4.17 + '@tanstack/query-core@5.96.2': {} + + '@tanstack/react-query@5.96.2(react@19.1.1)': + dependencies: + '@tanstack/query-core': 5.96.2 + react: 19.1.1 + '@tanstack/react-virtual@3.13.12(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: '@tanstack/virtual-core': 3.13.12 @@ -3719,6 +5019,42 @@ snapshots: '@tanstack/virtual-core@3.13.12': {} + '@testing-library/dom@10.4.1': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/runtime': 7.29.2 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + picocolors: 1.1.1 + pretty-format: 27.5.1 + + '@testing-library/jest-dom@6.9.1': + dependencies: + '@adobe/css-tools': 4.4.4 + aria-query: 5.3.2 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + picocolors: 1.1.1 + redent: 3.0.0 + + '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@babel/runtime': 7.29.2 + '@testing-library/dom': 10.4.1 + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.13 + '@types/react-dom': 19.1.9(@types/react@19.1.13) + + '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)': + dependencies: + '@testing-library/dom': 10.4.1 + + '@types/aria-query@5.0.4': {} + '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.28.4 @@ -3740,6 +5076,11 @@ snapshots: dependencies: '@babel/types': 7.28.4 + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + '@types/d3-array@3.2.2': {} '@types/d3-color@3.1.3': {} @@ -3785,12 +5126,16 @@ snapshots: dependencies: '@types/ms': 2.1.0 + '@types/deep-eql@4.0.2': {} + '@types/estree-jsx@1.0.5': dependencies: '@types/estree': 1.0.8 '@types/estree@1.0.8': {} + '@types/geojson@7946.0.16': {} + '@types/hast@3.0.4': dependencies: '@types/unist': 3.0.3 @@ -3809,6 +5154,8 @@ snapshots: dependencies: undici-types: 7.12.0 + '@types/offscreencanvas@2019.7.3': {} + '@types/parse-json@4.0.2': {} '@types/react-dom@19.1.9(@types/react@19.1.13)': @@ -3834,8 +5181,6 @@ snapshots: '@types/unist@3.0.3': {} - '@types/use-sync-external-store@0.0.6': {} - '@typescript-eslint/eslint-plugin@8.44.0(@typescript-eslint/parser@8.44.0(eslint@9.36.0(jiti@1.21.7))(typescript@5.8.3))(eslint@9.36.0(jiti@1.21.7))(typescript@5.8.3)': dependencies: '@eslint-community/regexpp': 4.12.1 @@ -3943,6 +5288,67 @@ snapshots: transitivePeerDependencies: - supports-color + '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.5.2)(jiti@1.21.7)(jsdom@26.1.0)(yaml@2.8.1))': + dependencies: + '@ampproject/remapping': 2.3.0 + '@bcoe/v8-coverage': 1.0.2 + ast-v8-to-istanbul: 0.3.12 + debug: 4.4.3 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.2.0 + magic-string: 0.30.21 + magicast: 0.3.5 + std-env: 3.10.0 + test-exclude: 7.0.2 + tinyrainbow: 2.0.0 + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.5.2)(jiti@1.21.7)(jsdom@26.1.0)(yaml@2.8.1) + transitivePeerDependencies: + - supports-color + + '@vitest/expect@3.2.4': + dependencies: + '@types/chai': 5.2.3 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 + tinyrainbow: 2.0.0 + + '@vitest/mocker@3.2.4(vite@6.3.6(@types/node@24.5.2)(jiti@1.21.7)(yaml@2.8.1))': + dependencies: + '@vitest/spy': 3.2.4 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 6.3.6(@types/node@24.5.2)(jiti@1.21.7)(yaml@2.8.1) + + '@vitest/pretty-format@3.2.4': + dependencies: + tinyrainbow: 2.0.0 + + '@vitest/runner@3.2.4': + dependencies: + '@vitest/utils': 3.2.4 + pathe: 2.0.3 + strip-literal: 3.1.0 + + '@vitest/snapshot@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@3.2.4': + dependencies: + tinyspy: 4.0.4 + + '@vitest/utils@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + loupe: 3.2.1 + tinyrainbow: 2.0.0 + '@xyflow/react@12.8.5(@types/react@19.1.13)(immer@10.1.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: '@xyflow/system': 0.0.69 @@ -3982,6 +5388,10 @@ snapshots: acorn@8.15.0: {} + acorn@8.16.0: {} + + agent-base@7.1.4: {} + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -3997,6 +5407,8 @@ snapshots: dependencies: color-convert: 2.0.1 + ansi-styles@5.2.0: {} + ansi-styles@6.2.3: {} any-promise@1.3.0: {} @@ -4014,6 +5426,20 @@ snapshots: dependencies: tslib: 2.8.1 + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + + aria-query@5.3.2: {} + + assertion-error@2.0.1: {} + + ast-v8-to-istanbul@0.3.12: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + estree-walker: 3.0.3 + js-tokens: 10.0.0 + autoprefixer@10.4.21(postcss@8.5.6): dependencies: browserslist: 4.26.2 @@ -4028,6 +5454,8 @@ snapshots: balanced-match@1.0.2: {} + balanced-match@4.0.4: {} + baseline-browser-mapping@2.8.6: {} binary-extensions@2.3.0: {} @@ -4041,6 +5469,10 @@ snapshots: dependencies: balanced-match: 1.0.2 + brace-expansion@5.0.5: + dependencies: + balanced-match: 4.0.4 + braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -4053,8 +5485,15 @@ snapshots: node-releases: 2.0.21 update-browserslist-db: 1.1.3(browserslist@4.26.2) + bundle-require@5.1.0(esbuild@0.27.7): + dependencies: + esbuild: 0.27.7 + load-tsconfig: 0.2.5 + bytes@3.1.2: {} + cac@6.7.14: {} + callsites@3.1.0: {} camelcase-css@2.0.1: {} @@ -4063,6 +5502,14 @@ snapshots: ccount@2.0.1: {} + chai@5.3.3: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.3 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -4076,6 +5523,8 @@ snapshots: character-reference-invalid@2.0.1: {} + check-error@2.1.3: {} + chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -4088,6 +5537,10 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + class-variance-authority@0.7.1: dependencies: clsx: 2.1.1 @@ -4132,6 +5585,10 @@ snapshots: concat-map@0.0.1: {} + confbox@0.1.8: {} + + consola@3.4.2: {} + convert-source-map@2.0.0: {} cookie@1.0.2: {} @@ -4154,8 +5611,15 @@ snapshots: css-unit-converter@1.1.2: {} + css.escape@1.5.1: {} + cssesc@3.0.0: {} + cssstyle@4.6.0: + dependencies: + '@asamuzakjp/css-color': 3.2.0 + rrweb-cssom: 0.8.0 + csstype@3.1.3: {} d3-array@3.2.4: @@ -4227,16 +5691,27 @@ snapshots: graphlib: 2.1.8 lodash: 4.17.21 + data-urls@5.0.0: + dependencies: + whatwg-mimetype: 4.0.0 + whatwg-url: 14.2.0 + + date-fns@3.6.0: {} + debug@4.4.3: dependencies: ms: 2.1.3 decimal.js-light@2.5.1: {} + decimal.js@10.6.0: {} + decode-named-character-reference@1.2.0: dependencies: character-entities: 2.0.2 + deep-eql@5.0.2: {} + deep-is@0.1.4: {} defined@1.0.1: {} @@ -4259,6 +5734,17 @@ snapshots: dlv@1.1.3: {} + dom-accessibility-api@0.5.16: {} + + dom-accessibility-api@0.6.3: {} + + dom-helpers@5.2.1: + dependencies: + '@babel/runtime': 7.29.2 + csstype: 3.1.3 + + earcut@2.2.4: {} + eastasianwidth@0.2.0: {} electron-to-chromium@1.5.222: {} @@ -4269,11 +5755,13 @@ snapshots: emoji-regex@9.2.2: {} + entities@6.0.1: {} + error-ex@1.3.4: dependencies: is-arrayish: 0.2.1 - es-toolkit@1.40.0: {} + es-module-lexer@1.7.0: {} esbuild@0.25.10: optionalDependencies: @@ -4304,6 +5792,35 @@ snapshots: '@esbuild/win32-ia32': 0.25.10 '@esbuild/win32-x64': 0.25.10 + esbuild@0.27.7: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.7 + '@esbuild/android-arm': 0.27.7 + '@esbuild/android-arm64': 0.27.7 + '@esbuild/android-x64': 0.27.7 + '@esbuild/darwin-arm64': 0.27.7 + '@esbuild/darwin-x64': 0.27.7 + '@esbuild/freebsd-arm64': 0.27.7 + '@esbuild/freebsd-x64': 0.27.7 + '@esbuild/linux-arm': 0.27.7 + '@esbuild/linux-arm64': 0.27.7 + '@esbuild/linux-ia32': 0.27.7 + '@esbuild/linux-loong64': 0.27.7 + '@esbuild/linux-mips64el': 0.27.7 + '@esbuild/linux-ppc64': 0.27.7 + '@esbuild/linux-riscv64': 0.27.7 + '@esbuild/linux-s390x': 0.27.7 + '@esbuild/linux-x64': 0.27.7 + '@esbuild/netbsd-arm64': 0.27.7 + '@esbuild/netbsd-x64': 0.27.7 + '@esbuild/openbsd-arm64': 0.27.7 + '@esbuild/openbsd-x64': 0.27.7 + '@esbuild/openharmony-arm64': 0.27.7 + '@esbuild/sunos-x64': 0.27.7 + '@esbuild/win32-arm64': 0.27.7 + '@esbuild/win32-ia32': 0.27.7 + '@esbuild/win32-x64': 0.27.7 + escalade@3.2.0: {} escape-string-regexp@4.0.0: {} @@ -4387,14 +5904,22 @@ snapshots: estree-util-is-identifier-name@3.0.0: {} + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + esutils@2.0.3: {} - eventemitter3@5.0.1: {} + eventemitter3@4.0.7: {} + + expect-type@1.3.0: {} extend@3.0.2: {} fast-deep-equal@3.1.3: {} + fast-equals@5.4.0: {} + fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -4428,6 +5953,12 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 + fix-dts-default-cjs-exports@1.0.1: + dependencies: + magic-string: 0.30.21 + mlly: 1.8.2 + rollup: 4.52.0 + flat-cache@4.0.1: dependencies: flatted: 3.3.3 @@ -4459,6 +5990,8 @@ snapshots: get-nonce@1.0.1: {} + gl-matrix@3.4.4: {} + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -4533,15 +6066,40 @@ snapshots: hsla-regex@1.0.0: {} + html-encoding-sniffer@4.0.0: + dependencies: + whatwg-encoding: 3.1.1 + + html-escaper@2.0.2: {} + html-tags@3.3.1: {} html-url-attributes@3.0.1: {} + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + ignore@5.3.2: {} ignore@7.0.5: {} - immer@10.1.3: {} + immer@10.1.3: + optional: true import-fresh@3.3.1: dependencies: @@ -4550,6 +6108,8 @@ snapshots: imurmurhash@0.1.4: {} + indent-string@4.0.0: {} + inflight@1.0.6: dependencies: once: 1.4.0 @@ -4605,8 +6165,31 @@ snapshots: is-plain-obj@4.1.0: {} + is-potential-custom-element-name@1.0.1: {} + isexe@2.0.0: {} + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@5.0.6: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + debug: 4.4.3 + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + jackspeak@3.4.3: dependencies: '@isaacs/cliui': 8.0.2 @@ -4615,12 +6198,45 @@ snapshots: jiti@1.21.7: {} + joycon@3.1.1: {} + + js-tokens@10.0.0: {} + js-tokens@4.0.0: {} + js-tokens@9.0.1: {} + js-yaml@4.1.0: dependencies: argparse: 2.0.1 + jsdom@26.1.0: + dependencies: + cssstyle: 4.6.0 + data-urls: 5.0.0 + decimal.js: 10.6.0 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.23 + parse5: 7.3.0 + rrweb-cssom: 0.8.0 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 5.1.2 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 14.2.0 + ws: 8.20.0 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + jsesc@3.1.0: {} json-buffer@3.0.1: {} @@ -4654,6 +6270,8 @@ snapshots: lines-and-columns@1.2.4: {} + load-tsconfig@0.2.5: {} + locate-path@6.0.0: dependencies: p-locate: 5.0.0 @@ -4670,6 +6288,8 @@ snapshots: dependencies: js-tokens: 4.0.0 + loupe@3.2.1: {} + lru-cache@10.4.3: {} lru-cache@5.1.1: @@ -4680,6 +6300,22 @@ snapshots: dependencies: react: 19.1.1 + lz-string@1.5.0: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + magicast@0.3.5: + dependencies: + '@babel/parser': 7.28.4 + '@babel/types': 7.28.4 + source-map-js: 1.2.1 + + make-dir@4.0.0: + dependencies: + semver: 7.7.2 + markdown-table@3.0.4: {} mdast-util-find-and-replace@3.0.2: @@ -5033,6 +6669,12 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 + min-indent@1.0.1: {} + + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.5 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.12 @@ -5045,6 +6687,15 @@ snapshots: minipass@7.1.2: {} + mjolnir.js@3.0.0: {} + + mlly@1.8.2: + dependencies: + acorn: 8.16.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.3 + modern-normalize@1.1.0: {} ms@2.1.3: {} @@ -5059,6 +6710,11 @@ snapshots: natural-compare@1.4.0: {} + next-themes@0.4.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + dependencies: + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + node-emoji@1.11.0: dependencies: lodash: 4.17.21 @@ -5069,6 +6725,8 @@ snapshots: normalize-range@0.1.2: {} + nwsapi@2.2.23: {} + object-assign@4.1.1: {} object-hash@2.2.0: {} @@ -5119,6 +6777,10 @@ snapshots: json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 + parse5@7.3.0: + dependencies: + entities: 6.0.1 + path-exists@4.0.0: {} path-is-absolute@1.0.1: {} @@ -5134,6 +6796,10 @@ snapshots: path-type@4.0.0: {} + pathe@2.0.3: {} + + pathval@2.0.1: {} + picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -5144,6 +6810,12 @@ snapshots: pirates@4.0.7: {} + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.8.2 + pathe: 2.0.3 + postcss-import@15.1.0(postcss@8.5.6): dependencies: postcss: 8.5.6 @@ -5175,6 +6847,14 @@ snapshots: optionalDependencies: postcss: 8.5.6 + postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.6)(yaml@2.8.1): + dependencies: + lilconfig: 3.1.3 + optionalDependencies: + jiti: 1.21.7 + postcss: 8.5.6 + yaml: 2.8.1 + postcss-nested@5.0.6(postcss@8.5.6): dependencies: postcss: 8.5.6 @@ -5200,8 +6880,16 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + preact@10.29.1: {} + prelude-ls@1.2.1: {} + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + pretty-hrtime@1.0.3: {} prop-types@15.8.1: @@ -5225,13 +6913,26 @@ snapshots: quick-lru@5.1.1: {} + react-day-picker@8.10.1(date-fns@3.6.0)(react@19.1.1): + dependencies: + date-fns: 3.6.0 + react: 19.1.1 + react-dom@19.1.1(react@19.1.1): dependencies: react: 19.1.1 scheduler: 0.26.0 + react-hook-form@7.72.1(react@19.1.1): + dependencies: + react: 19.1.1 + react-is@16.13.1: {} + react-is@17.0.2: {} + + react-is@18.3.1: {} + react-markdown@10.1.0(@types/react@19.1.13)(react@19.1.1): dependencies: '@types/hast': 3.0.4 @@ -5250,15 +6951,6 @@ snapshots: transitivePeerDependencies: - supports-color - react-redux@9.2.0(@types/react@19.1.13)(react@19.1.1)(redux@5.0.1): - dependencies: - '@types/use-sync-external-store': 0.0.6 - react: 19.1.1 - use-sync-external-store: 1.5.0(react@19.1.1) - optionalDependencies: - '@types/react': 19.1.13 - redux: 5.0.1 - react-refresh@0.17.0: {} react-remove-scroll-bar@2.3.8(@types/react@19.1.13)(react@19.1.1): @@ -5294,6 +6986,14 @@ snapshots: optionalDependencies: react-dom: 19.1.1(react@19.1.1) + react-smooth@4.0.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + dependencies: + fast-equals: 5.4.0 + prop-types: 15.8.1 + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + react-transition-group: 4.4.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react-style-singleton@2.2.3(@types/react@19.1.13)(react@19.1.1): dependencies: get-nonce: 1.0.1 @@ -5302,6 +7002,15 @@ snapshots: optionalDependencies: '@types/react': 19.1.13 + react-transition-group@4.4.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + dependencies: + '@babel/runtime': 7.29.2 + dom-helpers: 5.2.1 + loose-envify: 1.4.0 + prop-types: 15.8.1 + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + react@19.1.1: {} read-cache@1.0.0: @@ -5312,37 +7021,35 @@ snapshots: dependencies: picomatch: 2.3.1 - recharts@3.3.0(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react-is@16.13.1)(react@19.1.1)(redux@5.0.1): + readdirp@4.1.2: {} + + recharts-scale@0.4.5: dependencies: - '@reduxjs/toolkit': 2.9.2(react-redux@9.2.0(@types/react@19.1.13)(react@19.1.1)(redux@5.0.1))(react@19.1.1) - clsx: 2.1.1 decimal.js-light: 2.5.1 - es-toolkit: 1.40.0 - eventemitter3: 5.0.1 - immer: 10.1.3 + + recharts@2.15.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + dependencies: + clsx: 2.1.1 + eventemitter3: 4.0.7 + lodash: 4.17.21 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - react-is: 16.13.1 - react-redux: 9.2.0(@types/react@19.1.13)(react@19.1.1)(redux@5.0.1) - reselect: 5.1.1 + react-is: 18.3.1 + react-smooth: 4.0.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + recharts-scale: 0.4.5 tiny-invariant: 1.3.3 - use-sync-external-store: 1.5.0(react@19.1.1) - victory-vendor: 37.3.6 - transitivePeerDependencies: - - '@types/react' - - redux + victory-vendor: 36.9.2 + + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 reduce-css-calc@2.1.8: dependencies: css-unit-converter: 1.1.2 postcss-value-parser: 3.3.1 - redux-thunk@3.1.0(redux@5.0.1): - dependencies: - redux: 5.0.1 - - redux@5.0.1: {} - remark-gfm@4.0.1: dependencies: '@types/mdast': 4.0.4 @@ -5377,10 +7084,10 @@ snapshots: mdast-util-to-markdown: 2.1.2 unified: 11.0.5 - reselect@5.1.1: {} - resolve-from@4.0.0: {} + resolve-from@5.0.0: {} + resolve@1.22.10: dependencies: is-core-module: 2.16.1 @@ -5421,10 +7128,18 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.52.0 fsevents: 2.3.3 + rrweb-cssom@0.8.0: {} + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 + safer-buffer@2.1.2: {} + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + scheduler@0.26.0: {} semver@6.3.1: {} @@ -5439,6 +7154,8 @@ snapshots: shebang-regex@3.0.0: {} + siginfo@2.0.0: {} + signal-exit@4.1.0: {} simple-swizzle@0.2.4: @@ -5447,8 +7164,14 @@ snapshots: source-map-js@1.2.1: {} + source-map@0.7.6: {} + space-separated-tokens@2.0.2: {} + stackback@0.0.2: {} + + std-env@3.10.0: {} + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -5474,8 +7197,16 @@ snapshots: dependencies: ansi-regex: 6.2.2 + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + strip-json-comments@3.1.1: {} + strip-literal@3.1.0: + dependencies: + js-tokens: 9.0.1 + style-to-js@1.1.17: dependencies: style-to-object: 1.0.9 @@ -5500,6 +7231,10 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + symbol-tree@3.2.4: {} + + tailwind-merge@2.6.1: {} + tailwind-merge@3.3.1: {} tailwindcss-animate@1.0.7(tailwindcss@3.4.17): @@ -5580,6 +7315,12 @@ snapshots: transitivePeerDependencies: - ts-node + test-exclude@7.0.2: + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 10.4.5 + minimatch: 10.2.5 + thenify-all@1.6.0: dependencies: thenify: 3.3.1 @@ -5590,17 +7331,43 @@ snapshots: tiny-invariant@1.3.3: {} + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + tinyglobby@0.2.15: dependencies: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 + tinypool@1.1.1: {} + + tinyrainbow@2.0.0: {} + + tinyspy@4.0.4: {} + + tldts-core@6.1.86: {} + + tldts@6.1.86: + dependencies: + tldts-core: 6.1.86 + tmp@0.2.5: {} to-regex-range@5.0.1: dependencies: is-number: 7.0.0 + tough-cookie@5.1.2: + dependencies: + tldts: 6.1.86 + + tr46@5.1.1: + dependencies: + punycode: 2.3.1 + + tree-kill@1.2.2: {} + trim-lines@3.0.1: {} trough@2.2.0: {} @@ -5613,6 +7380,34 @@ snapshots: tslib@2.8.1: {} + tsup@8.5.1(jiti@1.21.7)(postcss@8.5.6)(typescript@5.8.3)(yaml@2.8.1): + dependencies: + bundle-require: 5.1.0(esbuild@0.27.7) + cac: 6.7.14 + chokidar: 4.0.3 + consola: 3.4.2 + debug: 4.4.3 + esbuild: 0.27.7 + fix-dts-default-cjs-exports: 1.0.1 + joycon: 3.1.1 + picocolors: 1.1.1 + postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.6)(yaml@2.8.1) + resolve-from: 5.0.0 + rollup: 4.52.0 + source-map: 0.7.6 + sucrase: 3.35.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.15 + tree-kill: 1.2.2 + optionalDependencies: + postcss: 8.5.6 + typescript: 5.8.3 + transitivePeerDependencies: + - jiti + - supports-color + - tsx + - yaml + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -5630,6 +7425,8 @@ snapshots: typescript@5.8.3: {} + ufo@1.6.3: {} + undici-types@7.12.0: {} unified@11.0.5: @@ -5717,7 +7514,7 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - victory-vendor@37.3.6: + victory-vendor@36.9.2: dependencies: '@types/d3-array': 3.2.2 '@types/d3-ease': 3.0.2 @@ -5734,6 +7531,27 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 + vite-node@3.2.4(@types/node@24.5.2)(jiti@1.21.7)(yaml@2.8.1): + dependencies: + cac: 6.7.14 + debug: 4.4.3 + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 6.3.6(@types/node@24.5.2)(jiti@1.21.7)(yaml@2.8.1) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + vite@6.3.6(@types/node@24.5.2)(jiti@1.21.7)(yaml@2.8.1): dependencies: esbuild: 0.25.10 @@ -5748,10 +7566,77 @@ snapshots: jiti: 1.21.7 yaml: 2.8.1 + vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.5.2)(jiti@1.21.7)(jsdom@26.1.0)(yaml@2.8.1): + dependencies: + '@types/chai': 5.2.3 + '@vitest/expect': 3.2.4 + '@vitest/mocker': 3.2.4(vite@6.3.6(@types/node@24.5.2)(jiti@1.21.7)(yaml@2.8.1)) + '@vitest/pretty-format': 3.2.4 + '@vitest/runner': 3.2.4 + '@vitest/snapshot': 3.2.4 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 + debug: 4.4.3 + expect-type: 1.3.0 + magic-string: 0.30.21 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.15 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 + vite: 6.3.6(@types/node@24.5.2)(jiti@1.21.7)(yaml@2.8.1) + vite-node: 3.2.4(@types/node@24.5.2)(jiti@1.21.7)(yaml@2.8.1) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/debug': 4.1.12 + '@types/node': 24.5.2 + jsdom: 26.1.0 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + + webidl-conversions@7.0.0: {} + + wgsl_reflect@1.2.3: {} + + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + + whatwg-mimetype@4.0.0: {} + + whatwg-url@14.2.0: + dependencies: + tr46: 5.1.1 + webidl-conversions: 7.0.0 + which@2.0.2: dependencies: isexe: 2.0.0 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + word-wrap@1.2.5: {} wrap-ansi@7.0.0: @@ -5768,6 +7653,12 @@ snapshots: wrappy@1.0.2: {} + ws@8.20.0: {} + + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + xtend@4.0.2: {} yallist@3.1.1: {} @@ -5778,6 +7669,10 @@ snapshots: yocto-queue@0.1.0: {} + zod@3.25.76: {} + + zod@4.3.6: {} + zustand@4.5.7(@types/react@19.1.13)(immer@10.1.3)(react@19.1.1): dependencies: use-sync-external-store: 1.5.0(react@19.1.1) diff --git a/control-plane/web/client/src/App.tsx b/control-plane/web/client/src/App.tsx index 32d94449f..0fa4b1eac 100644 --- a/control-plane/web/client/src/App.tsx +++ b/control-plane/web/client/src/App.tsx @@ -1,133 +1,91 @@ -import { Route, BrowserRouter as Router, Routes } from "react-router-dom"; -import { SidebarNew } from "./components/Navigation/SidebarNew"; -import { TopNavigation } from "./components/Navigation/TopNavigation"; +import { Navigate, Route, BrowserRouter as Router, Routes, useParams } from "react-router-dom"; +import { QueryClientProvider } from "@tanstack/react-query"; import { RootRedirect } from "./components/RootRedirect"; -import { navigationSections } from "./config/navigation"; import { ModeProvider } from "./contexts/ModeContext"; import { ThemeProvider } from "./components/theme-provider"; import { useFocusManagement } from "./hooks/useFocusManagement"; -import { SidebarProvider, SidebarInset } from "./components/ui/sidebar"; -import { AllReasonersPage } from "./pages/AllReasonersPage.tsx"; +import { AppLayout } from "./components/AppLayout"; import { EnhancedDashboardPage } from "./pages/EnhancedDashboardPage"; -import { ExecutionsPage } from "./pages/ExecutionsPage"; -import { EnhancedExecutionDetailPage } from "./pages/EnhancedExecutionDetailPage"; -import { EnhancedWorkflowDetailPage } from "./pages/EnhancedWorkflowDetailPage"; -import { NodeDetailPage } from "./pages/NodeDetailPage"; -import { NodesPage } from "./pages/NodesPage"; -import { PackagesPage } from "./pages/PackagesPage"; -import { ReasonerDetailPage } from "./pages/ReasonerDetailPage.tsx"; -import { WorkflowsPage } from "./pages/WorkflowsPage.tsx"; -import { WorkflowDeckGLTestPage } from "./pages/WorkflowDeckGLTestPage"; -import { DIDExplorerPage } from "./pages/DIDExplorerPage"; -import { CredentialsPage } from "./pages/CredentialsPage"; -import { ObservabilityWebhookSettingsPage } from "./pages/ObservabilityWebhookSettingsPage"; -import { AuthorizationPage } from "./pages/AuthorizationPage"; +import { NewDashboardPage } from "./pages/NewDashboardPage"; +import { NewSettingsPage } from "./pages/NewSettingsPage"; +import { AgentsPage } from "./pages/AgentsPage"; +import { RunsPage } from "./pages/RunsPage"; +import { RunDetailPage } from "./pages/RunDetailPage"; +import { VerifyProvenancePage } from "./pages/VerifyProvenancePage"; +import { ComparisonPage } from "./pages/ComparisonPage"; +import { PlaygroundPage } from "./pages/PlaygroundPage"; +import { AccessManagementPage } from "./pages/AccessManagementPage"; import { AuthProvider } from "./contexts/AuthContext"; import { AuthGuard } from "./components/AuthGuard"; +import { queryClient } from "./lib/query-client"; +import { ErrorBoundary } from "./components/ErrorBoundary"; -// Placeholder pages for new routes - -function AgentsPage() { - return ( -
-
-

- My Agents -

-

- Your configured and running agents -

-
-
- ); -} - -function SettingsPage() { - return ( -
-
-

- Settings -

-

- System configuration and preferences -

-
-
- ); +function NavigateToPlayground() { + const { reasonerId } = useParams(); + return ; } function AppContent() { - // Use focus management hook to ensure trackpad navigation works useFocusManagement(); return ( - -
- {/* Sidebar */} - - - {/* Main Content */} - - {/* Top Navigation */} - + + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> - {/* Main Content Area */} -
- -
} /> - } /> - } /> - } /> - } /> - } - /> - } /> - } - /> - } /> - } - /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - - - -
+ {/* Old → New redirects */} + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + ); } function App() { return ( - - - - - - - - - - - + + + + + + + + + + + + + + + ); } diff --git a/control-plane/web/client/src/assets/logos/logo-short-dark-v2.svg b/control-plane/web/client/src/assets/logos/logo-short-dark-v2.svg new file mode 100644 index 000000000..71dd6aef0 --- /dev/null +++ b/control-plane/web/client/src/assets/logos/logo-short-dark-v2.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/control-plane/web/client/src/assets/logos/logo-short-light-v2.svg b/control-plane/web/client/src/assets/logos/logo-short-light-v2.svg new file mode 100644 index 000000000..c0c7d379e --- /dev/null +++ b/control-plane/web/client/src/assets/logos/logo-short-light-v2.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/control-plane/web/client/src/components/AdminTokenPrompt.tsx b/control-plane/web/client/src/components/AdminTokenPrompt.tsx index 1558c38bc..68cac5ae2 100644 --- a/control-plane/web/client/src/components/AdminTokenPrompt.tsx +++ b/control-plane/web/client/src/components/AdminTokenPrompt.tsx @@ -1,9 +1,11 @@ import { useState } from "react"; import type { FormEvent } from "react"; import { useAuth } from "../contexts/AuthContext"; +import { HintIcon } from "@/components/authorization/HintIcon"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; +import { TooltipProvider } from "@/components/ui/tooltip"; /** * Inline prompt shown on admin pages for managing the admin token. @@ -33,10 +35,15 @@ export function AdminTokenPrompt({ onTokenSet }: { onTokenSet?: () => void }) { // Token is set — show compact status with change/clear actions if (adminToken && !editing) { return ( +
- + - Admin token set + Admin token saved in this browser + + Matches server admin_token or env. Not Settings. + Unchanged repo default is often admin-secret. +
+
); } // No token or editing — show the input form return ( + - -
- Admin Token -
+ +
+ + Admin token + + Must match server admin_token or{" "} + AGENTFIELD_AUTHORIZATION_ADMIN_TOKEN. This browser only. + Default YAML is often admin-secret. + + + setInputToken(e.target.value)} - placeholder="Enter admin token" - className="max-w-xs h-8" + placeholder="Same value as on the server" + className="h-8 max-w-xs" autoFocus={editing} /> {editing && (
+ ); } diff --git a/control-plane/web/client/src/components/AgentNodesTable.tsx b/control-plane/web/client/src/components/AgentNodesTable.tsx index b5f0b202e..3a5c4f8fd 100644 --- a/control-plane/web/client/src/components/AgentNodesTable.tsx +++ b/control-plane/web/client/src/components/AgentNodesTable.tsx @@ -12,7 +12,7 @@ import { Button } from '@/components/ui/button'; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; import { ChevronDown, ChevronUp, ServerProxy, Time, Earth, Network_3 } from '@/components/ui/icon-bridge'; import { getNodeDetails } from '../services/api'; -import StatusIndicator from './ui/status-indicator'; +import StatusIndicator from '@/components/status/UnifiedStatusIndicator'; import ReasonersList from './ReasonersList'; import SkillsList from './SkillsList'; import { Skeleton } from '@/components/ui/skeleton'; @@ -68,7 +68,7 @@ const AgentNodesTable: React.FC = ({ nodes, isLoading, err
-

No Agent Nodes

+

No Agent Nodes

No agent nodes are currently registered with the AgentField server.

); @@ -162,7 +162,7 @@ const NodeRow: React.FC = ({ nodeSummary }) => { {isLoadingDetails && (
-

Loading node details...

+

Loading node details...

)} {errorDetails && ( @@ -174,14 +174,14 @@ const NodeRow: React.FC = ({ nodeSummary }) => {
-

Base URL

+

Base URL

{nodeDetails.base_url}

-

Registered

+

Registered

{nodeDetails.registered_at ? (() => { const date = new Date(nodeDetails.registered_at); return !isNaN(date.getTime()) ? date.toLocaleString() : 'Invalid Date'; @@ -191,7 +191,7 @@ const NodeRow: React.FC = ({ nodeSummary }) => {