@stigmer/react 3.2.3 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent/steps/ReviewStep.d.ts.map +1 -1
- package/agent/steps/ReviewStep.js +2 -3
- package/agent/steps/ReviewStep.js.map +1 -1
- package/billing/BaselineEditor.d.ts +9 -0
- package/billing/BaselineEditor.d.ts.map +1 -0
- package/billing/BaselineEditor.js +253 -0
- package/billing/BaselineEditor.js.map +1 -0
- package/billing/ModelCatalogPanel.d.ts +28 -0
- package/billing/ModelCatalogPanel.d.ts.map +1 -0
- package/billing/ModelCatalogPanel.js +102 -0
- package/billing/ModelCatalogPanel.js.map +1 -0
- package/billing/ModelGovernanceDetail.d.ts +8 -0
- package/billing/ModelGovernanceDetail.d.ts.map +1 -0
- package/billing/ModelGovernanceDetail.js +64 -0
- package/billing/ModelGovernanceDetail.js.map +1 -0
- package/billing/OperatorAccessNotice.d.ts +12 -0
- package/billing/OperatorAccessNotice.d.ts.map +1 -0
- package/billing/OperatorAccessNotice.js +15 -0
- package/billing/OperatorAccessNotice.js.map +1 -0
- package/billing/PricingGovernanceConsole.d.ts +57 -0
- package/billing/PricingGovernanceConsole.d.ts.map +1 -0
- package/billing/PricingGovernanceConsole.js +164 -0
- package/billing/PricingGovernanceConsole.js.map +1 -0
- package/billing/PricingGovernancePanel.d.ts +26 -0
- package/billing/PricingGovernancePanel.d.ts.map +1 -0
- package/billing/PricingGovernancePanel.js +64 -0
- package/billing/PricingGovernancePanel.js.map +1 -0
- package/billing/RetireConfirm.d.ts +9 -0
- package/billing/RetireConfirm.d.ts.map +1 -0
- package/billing/RetireConfirm.js +19 -0
- package/billing/RetireConfirm.js.map +1 -0
- package/billing/form-primitives.d.ts +18 -0
- package/billing/form-primitives.d.ts.map +1 -0
- package/billing/form-primitives.js +19 -0
- package/billing/form-primitives.js.map +1 -0
- package/billing/governance-primitives.d.ts +33 -0
- package/billing/governance-primitives.d.ts.map +1 -0
- package/billing/governance-primitives.js +43 -0
- package/billing/governance-primitives.js.map +1 -0
- package/billing/index.d.ts +18 -0
- package/billing/index.d.ts.map +1 -1
- package/billing/index.js +9 -0
- package/billing/index.js.map +1 -1
- package/billing/pricing-format.d.ts +12 -0
- package/billing/pricing-format.d.ts.map +1 -0
- package/billing/pricing-format.js +50 -0
- package/billing/pricing-format.js.map +1 -0
- package/billing/useDecidePricingOverride.d.ts +46 -0
- package/billing/useDecidePricingOverride.d.ts.map +1 -0
- package/billing/useDecidePricingOverride.js +44 -0
- package/billing/useDecidePricingOverride.js.map +1 -0
- package/billing/useModelGovernanceView.d.ts +103 -0
- package/billing/useModelGovernanceView.d.ts.map +1 -0
- package/billing/useModelGovernanceView.js +144 -0
- package/billing/useModelGovernanceView.js.map +1 -0
- package/billing/useModelPricingBaselines.d.ts +40 -0
- package/billing/useModelPricingBaselines.d.ts.map +1 -0
- package/billing/useModelPricingBaselines.js +32 -0
- package/billing/useModelPricingBaselines.js.map +1 -0
- package/billing/usePricingGovernance.d.ts +39 -0
- package/billing/usePricingGovernance.d.ts.map +1 -0
- package/billing/usePricingGovernance.js +31 -0
- package/billing/usePricingGovernance.js.map +1 -0
- package/billing/useRetireModelPricingBaseline.d.ts +31 -0
- package/billing/useRetireModelPricingBaseline.d.ts.map +1 -0
- package/billing/useRetireModelPricingBaseline.js +36 -0
- package/billing/useRetireModelPricingBaseline.js.map +1 -0
- package/billing/useUpsertModelPricingBaseline.d.ts +38 -0
- package/billing/useUpsertModelPricingBaseline.d.ts.map +1 -0
- package/billing/useUpsertModelPricingBaseline.js +37 -0
- package/billing/useUpsertModelPricingBaseline.js.map +1 -0
- package/channel/AgentChannelsPanel.d.ts.map +1 -1
- package/channel/AgentChannelsPanel.js +11 -5
- package/channel/AgentChannelsPanel.js.map +1 -1
- package/composer/SessionComposer.d.ts +1 -1
- package/cursor-accounts/CursorAccountEditor.d.ts +28 -0
- package/cursor-accounts/CursorAccountEditor.d.ts.map +1 -0
- package/cursor-accounts/CursorAccountEditor.js +51 -0
- package/cursor-accounts/CursorAccountEditor.js.map +1 -0
- package/cursor-accounts/CursorAccountsAccessNotice.d.ts +11 -0
- package/cursor-accounts/CursorAccountsAccessNotice.d.ts.map +1 -0
- package/cursor-accounts/CursorAccountsAccessNotice.js +14 -0
- package/cursor-accounts/CursorAccountsAccessNotice.js.map +1 -0
- package/cursor-accounts/CursorAccountsConsole.d.ts +28 -0
- package/cursor-accounts/CursorAccountsConsole.d.ts.map +1 -0
- package/cursor-accounts/CursorAccountsConsole.js +275 -0
- package/cursor-accounts/CursorAccountsConsole.js.map +1 -0
- package/cursor-accounts/cursor-account-format.d.ts +10 -0
- package/cursor-accounts/cursor-account-format.d.ts.map +1 -0
- package/cursor-accounts/cursor-account-format.js +26 -0
- package/cursor-accounts/cursor-account-format.js.map +1 -0
- package/cursor-accounts/index.d.ts +18 -0
- package/cursor-accounts/index.d.ts.map +1 -0
- package/cursor-accounts/index.js +13 -0
- package/cursor-accounts/index.js.map +1 -0
- package/cursor-accounts/useCursorAccountView.d.ts +30 -0
- package/cursor-accounts/useCursorAccountView.d.ts.map +1 -0
- package/cursor-accounts/useCursorAccountView.js +25 -0
- package/cursor-accounts/useCursorAccountView.js.map +1 -0
- package/cursor-accounts/useCursorAccounts.d.ts +33 -0
- package/cursor-accounts/useCursorAccounts.d.ts.map +1 -0
- package/cursor-accounts/useCursorAccounts.js +25 -0
- package/cursor-accounts/useCursorAccounts.js.map +1 -0
- package/cursor-accounts/useCursorMemberKeyActions.d.ts +49 -0
- package/cursor-accounts/useCursorMemberKeyActions.d.ts.map +1 -0
- package/cursor-accounts/useCursorMemberKeyActions.js +38 -0
- package/cursor-accounts/useCursorMemberKeyActions.js.map +1 -0
- package/cursor-accounts/useDeleteCursorAccount.d.ts +30 -0
- package/cursor-accounts/useDeleteCursorAccount.d.ts.map +1 -0
- package/cursor-accounts/useDeleteCursorAccount.js +32 -0
- package/cursor-accounts/useDeleteCursorAccount.js.map +1 -0
- package/cursor-accounts/useSyncCursorAccount.d.ts +23 -0
- package/cursor-accounts/useSyncCursorAccount.d.ts.map +1 -0
- package/cursor-accounts/useSyncCursorAccount.js +31 -0
- package/cursor-accounts/useSyncCursorAccount.js.map +1 -0
- package/cursor-accounts/useUpsertCursorAccount.d.ts +26 -0
- package/cursor-accounts/useUpsertCursorAccount.d.ts.map +1 -0
- package/cursor-accounts/useUpsertCursorAccount.js +34 -0
- package/cursor-accounts/useUpsertCursorAccount.js.map +1 -0
- package/datastore/CollectionRecordsBrowser.d.ts +34 -0
- package/datastore/CollectionRecordsBrowser.d.ts.map +1 -0
- package/datastore/CollectionRecordsBrowser.js +209 -0
- package/datastore/CollectionRecordsBrowser.js.map +1 -0
- package/datastore/CollectionSchemaView.d.ts +29 -0
- package/datastore/CollectionSchemaView.d.ts.map +1 -0
- package/datastore/CollectionSchemaView.js +93 -0
- package/datastore/CollectionSchemaView.js.map +1 -0
- package/datastore/DatastoreDetailView.d.ts +44 -0
- package/datastore/DatastoreDetailView.d.ts.map +1 -0
- package/datastore/DatastoreDetailView.js +76 -0
- package/datastore/DatastoreDetailView.js.map +1 -0
- package/datastore/DatastoreSyncReport.d.ts +19 -0
- package/datastore/DatastoreSyncReport.d.ts.map +1 -0
- package/datastore/DatastoreSyncReport.js +45 -0
- package/datastore/DatastoreSyncReport.js.map +1 -0
- package/datastore/DeleteDatastoreDialog.d.ts +35 -0
- package/datastore/DeleteDatastoreDialog.d.ts.map +1 -0
- package/datastore/DeleteDatastoreDialog.js +90 -0
- package/datastore/DeleteDatastoreDialog.js.map +1 -0
- package/datastore/FieldValueControl.d.ts +55 -0
- package/datastore/FieldValueControl.d.ts.map +1 -0
- package/datastore/FieldValueControl.js +138 -0
- package/datastore/FieldValueControl.js.map +1 -0
- package/datastore/RecordFilterBuilder.d.ts +30 -0
- package/datastore/RecordFilterBuilder.d.ts.map +1 -0
- package/datastore/RecordFilterBuilder.js +122 -0
- package/datastore/RecordFilterBuilder.js.map +1 -0
- package/datastore/RecordFormPanel.d.ts +45 -0
- package/datastore/RecordFormPanel.d.ts.map +1 -0
- package/datastore/RecordFormPanel.js +215 -0
- package/datastore/RecordFormPanel.js.map +1 -0
- package/datastore/index.d.ts +43 -0
- package/datastore/index.d.ts.map +1 -0
- package/datastore/index.js +22 -0
- package/datastore/index.js.map +1 -0
- package/datastore/recordFilter.d.ts +54 -0
- package/datastore/recordFilter.d.ts.map +1 -0
- package/datastore/recordFilter.js +93 -0
- package/datastore/recordFilter.js.map +1 -0
- package/datastore/recordValues.d.ts +85 -0
- package/datastore/recordValues.d.ts.map +1 -0
- package/datastore/recordValues.js +282 -0
- package/datastore/recordValues.js.map +1 -0
- package/datastore/useDatastore.d.ts +39 -0
- package/datastore/useDatastore.d.ts.map +1 -0
- package/datastore/useDatastore.js +44 -0
- package/datastore/useDatastore.js.map +1 -0
- package/datastore/useDatastoreCount.d.ts +48 -0
- package/datastore/useDatastoreCount.d.ts.map +1 -0
- package/datastore/useDatastoreCount.js +27 -0
- package/datastore/useDatastoreCount.js.map +1 -0
- package/datastore/useDatastoreDescription.d.ts +54 -0
- package/datastore/useDatastoreDescription.d.ts.map +1 -0
- package/datastore/useDatastoreDescription.js +50 -0
- package/datastore/useDatastoreDescription.js.map +1 -0
- package/datastore/useDatastoreList.d.ts +57 -0
- package/datastore/useDatastoreList.d.ts.map +1 -0
- package/datastore/useDatastoreList.js +29 -0
- package/datastore/useDatastoreList.js.map +1 -0
- package/datastore/useDatastoreSearch.d.ts +19 -0
- package/datastore/useDatastoreSearch.d.ts.map +1 -0
- package/datastore/useDatastoreSearch.js +22 -0
- package/datastore/useDatastoreSearch.js.map +1 -0
- package/datastore/useDeleteDatastore.d.ts +53 -0
- package/datastore/useDeleteDatastore.d.ts.map +1 -0
- package/datastore/useDeleteDatastore.js +40 -0
- package/datastore/useDeleteDatastore.js.map +1 -0
- package/datastore/useDeleteRecord.d.ts +34 -0
- package/datastore/useDeleteRecord.d.ts.map +1 -0
- package/datastore/useDeleteRecord.js +43 -0
- package/datastore/useDeleteRecord.js.map +1 -0
- package/datastore/useInsertRecord.d.ts +50 -0
- package/datastore/useInsertRecord.d.ts.map +1 -0
- package/datastore/useInsertRecord.js +55 -0
- package/datastore/useInsertRecord.js.map +1 -0
- package/datastore/useRecordCollection.d.ts +104 -0
- package/datastore/useRecordCollection.d.ts.map +1 -0
- package/datastore/useRecordCollection.js +136 -0
- package/datastore/useRecordCollection.js.map +1 -0
- package/datastore/useRecordList.d.ts +66 -0
- package/datastore/useRecordList.d.ts.map +1 -0
- package/datastore/useRecordList.js +58 -0
- package/datastore/useRecordList.js.map +1 -0
- package/datastore/useUpdateRecord.d.ts +52 -0
- package/datastore/useUpdateRecord.d.ts.map +1 -0
- package/datastore/useUpdateRecord.js +55 -0
- package/datastore/useUpdateRecord.js.map +1 -0
- package/environment/EnvironmentListPanel.d.ts.map +1 -1
- package/environment/EnvironmentListPanel.js +12 -7
- package/environment/EnvironmentListPanel.js.map +1 -1
- package/iam-policy/index.d.ts +1 -1
- package/iam-policy/index.d.ts.map +1 -1
- package/iam-policy/index.js.map +1 -1
- package/iam-policy/useCheckPermission.d.ts +32 -11
- package/iam-policy/useCheckPermission.d.ts.map +1 -1
- package/iam-policy/useCheckPermission.js +24 -19
- package/iam-policy/useCheckPermission.js.map +1 -1
- package/index.d.ts +12 -6
- package/index.d.ts.map +1 -1
- package/index.js +14 -3
- package/index.js.map +1 -1
- package/library/index.d.ts +0 -7
- package/library/index.d.ts.map +1 -1
- package/library/index.js +0 -4
- package/library/index.js.map +1 -1
- package/library/useApplyResource.d.ts +12 -12
- package/library/useApplyResource.d.ts.map +1 -1
- package/library/useApplyResource.js +16 -25
- package/library/useApplyResource.js.map +1 -1
- package/library/useExportResource.d.ts +13 -9
- package/library/useExportResource.d.ts.map +1 -1
- package/library/useExportResource.js +18 -36
- package/library/useExportResource.js.map +1 -1
- package/manifest/ApplyManifestDialog.d.ts +48 -0
- package/manifest/ApplyManifestDialog.d.ts.map +1 -0
- package/manifest/ApplyManifestDialog.js +135 -0
- package/manifest/ApplyManifestDialog.js.map +1 -0
- package/manifest/EditResourceYamlDialog.d.ts +51 -0
- package/manifest/EditResourceYamlDialog.d.ts.map +1 -0
- package/manifest/EditResourceYamlDialog.js +104 -0
- package/manifest/EditResourceYamlDialog.js.map +1 -0
- package/manifest/RedactedSecretsNotice.d.ts +14 -0
- package/manifest/RedactedSecretsNotice.d.ts.map +1 -0
- package/manifest/RedactedSecretsNotice.js +14 -0
- package/manifest/RedactedSecretsNotice.js.map +1 -0
- package/manifest/YamlEditor.d.ts +44 -0
- package/manifest/YamlEditor.d.ts.map +1 -0
- package/manifest/YamlEditor.js +216 -0
- package/manifest/YamlEditor.js.map +1 -0
- package/manifest/index.d.ts +13 -0
- package/manifest/index.d.ts.map +1 -0
- package/manifest/index.js +13 -0
- package/manifest/index.js.map +1 -0
- package/manifest/useApplyManifest.d.ts +82 -0
- package/manifest/useApplyManifest.d.ts.map +1 -0
- package/manifest/useApplyManifest.js +180 -0
- package/manifest/useApplyManifest.js.map +1 -0
- package/manifest/useEditResourceYaml.d.ts +106 -0
- package/manifest/useEditResourceYaml.d.ts.map +1 -0
- package/manifest/useEditResourceYaml.js +168 -0
- package/manifest/useEditResourceYaml.js.map +1 -0
- package/mcp-server/steps/ReviewStep.d.ts.map +1 -1
- package/mcp-server/steps/ReviewStep.js +2 -3
- package/mcp-server/steps/ReviewStep.js.map +1 -1
- package/package.json +4 -4
- package/resource-creation/templates/workflow-templates.js +4 -4
- package/session/useEditSessionPrep.js +3 -3
- package/session/useEditSessionPrep.js.map +1 -1
- package/settings/index.d.ts +2 -1
- package/settings/index.d.ts.map +1 -1
- package/settings/index.js +2 -1
- package/settings/index.js.map +1 -1
- package/settings/settings-nav.d.ts +22 -0
- package/settings/settings-nav.d.ts.map +1 -1
- package/settings/settings-nav.js +32 -1
- package/settings/settings-nav.js.map +1 -1
- package/settings/useSettingsNavGroups.d.ts +29 -0
- package/settings/useSettingsNavGroups.d.ts.map +1 -0
- package/settings/useSettingsNavGroups.js +61 -0
- package/settings/useSettingsNavGroups.js.map +1 -0
- package/src/agent/steps/ReviewStep.tsx +2 -3
- package/src/billing/BaselineEditor.tsx +619 -0
- package/src/billing/ModelCatalogPanel.tsx +283 -0
- package/src/billing/ModelGovernanceDetail.tsx +289 -0
- package/src/billing/OperatorAccessNotice.tsx +32 -0
- package/src/billing/PricingGovernanceConsole.tsx +465 -0
- package/src/billing/PricingGovernancePanel.tsx +166 -0
- package/src/billing/RetireConfirm.tsx +82 -0
- package/src/billing/__tests__/ModelCatalogPanel.test.tsx +253 -0
- package/src/billing/__tests__/PricingGovernanceConsole.test.tsx +410 -0
- package/src/billing/__tests__/useModelGovernanceView.test.tsx +308 -0
- package/src/billing/form-primitives.tsx +68 -0
- package/src/billing/governance-primitives.tsx +133 -0
- package/src/billing/index.ts +37 -0
- package/src/billing/pricing-format.ts +53 -0
- package/src/billing/useDecidePricingOverride.ts +78 -0
- package/src/billing/useModelGovernanceView.ts +290 -0
- package/src/billing/useModelPricingBaselines.ts +69 -0
- package/src/billing/usePricingGovernance.ts +57 -0
- package/src/billing/useRetireModelPricingBaseline.ts +65 -0
- package/src/billing/useUpsertModelPricingBaseline.ts +72 -0
- package/src/channel/AgentChannelsPanel.tsx +27 -1
- package/src/composer/SessionComposer.tsx +1 -1
- package/src/cursor-accounts/CursorAccountEditor.tsx +181 -0
- package/src/cursor-accounts/CursorAccountsAccessNotice.tsx +36 -0
- package/src/cursor-accounts/CursorAccountsConsole.tsx +810 -0
- package/src/cursor-accounts/__tests__/CursorAccountsConsole.test.tsx +384 -0
- package/src/cursor-accounts/cursor-account-format.ts +29 -0
- package/src/cursor-accounts/index.ts +29 -0
- package/src/cursor-accounts/useCursorAccountView.ts +49 -0
- package/src/cursor-accounts/useCursorAccounts.ts +49 -0
- package/src/cursor-accounts/useCursorMemberKeyActions.ts +106 -0
- package/src/cursor-accounts/useDeleteCursorAccount.ts +62 -0
- package/src/cursor-accounts/useSyncCursorAccount.ts +54 -0
- package/src/cursor-accounts/useUpsertCursorAccount.ts +57 -0
- package/src/datastore/CollectionRecordsBrowser.tsx +544 -0
- package/src/datastore/CollectionSchemaView.tsx +247 -0
- package/src/datastore/DatastoreDetailView.tsx +201 -0
- package/src/datastore/DatastoreSyncReport.tsx +105 -0
- package/src/datastore/DeleteDatastoreDialog.tsx +202 -0
- package/src/datastore/FieldValueControl.tsx +289 -0
- package/src/datastore/RecordFilterBuilder.tsx +411 -0
- package/src/datastore/RecordFormPanel.tsx +432 -0
- package/src/datastore/__tests__/CollectionRecordsBrowser.test.tsx +207 -0
- package/src/datastore/__tests__/DeleteDatastoreDialog.test.tsx +133 -0
- package/src/datastore/__tests__/FieldValueControl.test.tsx +160 -0
- package/src/datastore/__tests__/RecordFilterBuilder.test.tsx +141 -0
- package/src/datastore/__tests__/RecordFormPanel.test.tsx +227 -0
- package/src/datastore/__tests__/recordFilter.test.ts +156 -0
- package/src/datastore/__tests__/recordMutations.test.tsx +195 -0
- package/src/datastore/__tests__/recordValues.test.ts +359 -0
- package/src/datastore/__tests__/schemaAndSyncViews.test.tsx +142 -0
- package/src/datastore/__tests__/useDatastoreDescription.test.tsx +143 -0
- package/src/datastore/__tests__/useRecordCollection.test.tsx +250 -0
- package/src/datastore/__tests__/useRecordList.test.tsx +85 -0
- package/src/datastore/index.ts +111 -0
- package/src/datastore/recordFilter.ts +140 -0
- package/src/datastore/recordValues.ts +327 -0
- package/src/datastore/useDatastore.ts +67 -0
- package/src/datastore/useDatastoreCount.ts +67 -0
- package/src/datastore/useDatastoreDescription.ts +90 -0
- package/src/datastore/useDatastoreList.ts +79 -0
- package/src/datastore/useDatastoreSearch.ts +39 -0
- package/src/datastore/useDeleteDatastore.ts +87 -0
- package/src/datastore/useDeleteRecord.ts +77 -0
- package/src/datastore/useInsertRecord.ts +92 -0
- package/src/datastore/useRecordCollection.ts +256 -0
- package/src/datastore/useRecordList.ts +131 -0
- package/src/datastore/useUpdateRecord.ts +95 -0
- package/src/environment/EnvironmentListPanel.tsx +24 -0
- package/src/execution/__tests__/useArtifactInspection.test.tsx +12 -2
- package/src/iam-policy/__tests__/useCheckPermission.test.tsx +221 -0
- package/src/iam-policy/index.ts +1 -0
- package/src/iam-policy/useCheckPermission.ts +45 -19
- package/src/index.ts +163 -13
- package/src/library/index.ts +0 -20
- package/src/library/useApplyResource.ts +27 -34
- package/src/library/useExportResource.ts +21 -40
- package/src/manifest/ApplyManifestDialog.tsx +315 -0
- package/src/manifest/EditResourceYamlDialog.tsx +265 -0
- package/src/manifest/RedactedSecretsNotice.tsx +30 -0
- package/src/manifest/YamlEditor.tsx +272 -0
- package/src/manifest/__tests__/EditResourceYamlDialog.test.tsx +118 -0
- package/src/manifest/__tests__/useApplyManifest.test.tsx +237 -0
- package/src/manifest/__tests__/useEditResourceYaml.test.tsx +252 -0
- package/src/manifest/index.ts +33 -0
- package/src/manifest/useApplyManifest.ts +270 -0
- package/src/manifest/useEditResourceYaml.ts +273 -0
- package/src/mcp-server/steps/ReviewStep.tsx +2 -3
- package/src/resource-creation/templates/workflow-templates.ts +4 -4
- package/src/session/useEditSessionPrep.ts +3 -3
- package/src/settings/__tests__/useSettingsNavGroups.test.tsx +138 -0
- package/src/settings/index.ts +5 -1
- package/src/settings/settings-nav.ts +43 -0
- package/src/settings/useSettingsNavGroups.ts +81 -0
- package/src/workflow/WorkflowYamlEditor.tsx +6 -270
- package/styles.css +1 -1
- package/workflow/CanvasTransitionEdge.d.ts +1 -1
- package/workflow/WorkflowYamlEditor.d.ts +2 -42
- package/workflow/WorkflowYamlEditor.d.ts.map +1 -1
- package/workflow/WorkflowYamlEditor.js +5 -214
- package/workflow/WorkflowYamlEditor.js.map +1 -1
- package/library/ImportResourceDialog.d.ts +0 -43
- package/library/ImportResourceDialog.d.ts.map +0 -1
- package/library/ImportResourceDialog.js +0 -90
- package/library/ImportResourceDialog.js.map +0 -1
- package/library/parse-resource-yaml.d.ts +0 -63
- package/library/parse-resource-yaml.d.ts.map +0 -1
- package/library/parse-resource-yaml.js +0 -407
- package/library/parse-resource-yaml.js.map +0 -1
- package/library/serialize-resource-yaml.d.ts +0 -103
- package/library/serialize-resource-yaml.d.ts.map +0 -1
- package/library/serialize-resource-yaml.js +0 -565
- package/library/serialize-resource-yaml.js.map +0 -1
- package/library/useImportResource.d.ts +0 -84
- package/library/useImportResource.d.ts.map +0 -1
- package/library/useImportResource.js +0 -188
- package/library/useImportResource.js.map +0 -1
- package/src/library/ImportResourceDialog.tsx +0 -227
- package/src/library/parse-resource-yaml.ts +0 -630
- package/src/library/serialize-resource-yaml.ts +0 -756
- package/src/library/useImportResource.ts +0 -253
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
+
import type { Message } from "@bufbuild/protobuf";
|
|
5
|
+
import {
|
|
6
|
+
containsRedactedSecrets,
|
|
7
|
+
parseManifest,
|
|
8
|
+
serializeManifest,
|
|
9
|
+
type AppliedManifest,
|
|
10
|
+
type ManifestDocument,
|
|
11
|
+
} from "@stigmer/sdk";
|
|
12
|
+
import { useStigmer } from "../hooks.js";
|
|
13
|
+
import { toError } from "../internal/toError.js";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Result of validating the edited YAML against the source resource.
|
|
17
|
+
*
|
|
18
|
+
* Discriminated on `status` so consumers can render exactly one of:
|
|
19
|
+
* a disabled state (`empty`), an inline error (`invalid`), or the
|
|
20
|
+
* apply affordance (`valid`).
|
|
21
|
+
*/
|
|
22
|
+
export type EditYamlValidation =
|
|
23
|
+
| { readonly status: "empty" }
|
|
24
|
+
| { readonly status: "invalid"; readonly message: string }
|
|
25
|
+
| { readonly status: "valid"; readonly document: ManifestDocument };
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* What applying the current content will do — resolved against live
|
|
29
|
+
* server state so renames surface as "create" before the user commits.
|
|
30
|
+
*/
|
|
31
|
+
export type EditYamlTarget =
|
|
32
|
+
| { readonly action: "update"; readonly slug: string }
|
|
33
|
+
| { readonly action: "create"; readonly slug: string }
|
|
34
|
+
/** Existence lookup failed (offline, permissions) — wording degrades. */
|
|
35
|
+
| { readonly action: "unknown"; readonly slug: string };
|
|
36
|
+
|
|
37
|
+
/** Options for {@link useEditResourceYaml}. */
|
|
38
|
+
export interface UseEditResourceYamlOptions {
|
|
39
|
+
/**
|
|
40
|
+
* The resource to edit, as returned by the server (e.g. from `useAgent`
|
|
41
|
+
* or a list panel row). `null` while the resource is loading — the hook
|
|
42
|
+
* seeds the editor when it arrives.
|
|
43
|
+
*/
|
|
44
|
+
readonly resource: Message | null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Return value of {@link useEditResourceYaml}. */
|
|
48
|
+
export interface UseEditResourceYamlReturn {
|
|
49
|
+
/** Current editor content. */
|
|
50
|
+
readonly yaml: string;
|
|
51
|
+
/** Update the editor content (wire to the YAML editor's `onChange`). */
|
|
52
|
+
readonly setYaml: (value: string) => void;
|
|
53
|
+
/** `true` when the content differs from the seeded serialization. */
|
|
54
|
+
readonly isDirty: boolean;
|
|
55
|
+
/** Live validation of the current content. */
|
|
56
|
+
readonly validation: EditYamlValidation;
|
|
57
|
+
/**
|
|
58
|
+
* Create-vs-update preview for the current content, or `null` while
|
|
59
|
+
* validation fails or the existence check is in flight.
|
|
60
|
+
*/
|
|
61
|
+
readonly target: EditYamlTarget | null;
|
|
62
|
+
/** `true` when the content contains `***REDACTED***` secret markers. */
|
|
63
|
+
readonly hasRedactedSecrets: boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Apply the edited YAML. Resolves with the applied resource, or `null`
|
|
66
|
+
* when the content is not currently valid (check `validation`).
|
|
67
|
+
*/
|
|
68
|
+
readonly apply: () => Promise<AppliedManifest | null>;
|
|
69
|
+
/** `true` while the apply RPC is in flight. */
|
|
70
|
+
readonly isApplying: boolean;
|
|
71
|
+
/** Error from the last failed apply, or `null` when healthy. */
|
|
72
|
+
readonly error: Error | null;
|
|
73
|
+
/** Clear the apply error (e.g. when the user edits again). */
|
|
74
|
+
readonly clearError: () => void;
|
|
75
|
+
/** Discard edits and reseed from the resource. */
|
|
76
|
+
readonly reset: () => void;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Existence checks run against live server state; wait for a typing pause.
|
|
80
|
+
const TARGET_CHECK_DEBOUNCE_MS = 400;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Behavior hook for editing an existing resource as YAML and applying the
|
|
84
|
+
* result — the console's kind-agnostic "Edit YAML" flow.
|
|
85
|
+
*
|
|
86
|
+
* Seeds the editor from {@link serializeManifest} (full fidelity minus
|
|
87
|
+
* system-managed state), validates every edit against the kind's generated
|
|
88
|
+
* proto schema via {@link parseManifest} (strict — unknown fields fail
|
|
89
|
+
* loudly), previews create-vs-update by resolving the edited document's
|
|
90
|
+
* reference against the server, and applies through
|
|
91
|
+
* `stigmer.manifest.apply()`.
|
|
92
|
+
*
|
|
93
|
+
* Guardrails beyond the raw engine:
|
|
94
|
+
* - the edited document must stay the same kind as the source resource
|
|
95
|
+
* (pasting an Environment into an Agent editor is an error, not an apply)
|
|
96
|
+
* - the edited manifest must contain exactly one document
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```tsx
|
|
100
|
+
* const edit = useEditResourceYaml({ resource: agent });
|
|
101
|
+
*
|
|
102
|
+
* <YamlEditor value={edit.yaml} onChange={edit.setYaml} />
|
|
103
|
+
* {edit.validation.status === "invalid" && <p>{edit.validation.message}</p>}
|
|
104
|
+
* <button disabled={edit.validation.status !== "valid"} onClick={edit.apply}>
|
|
105
|
+
* {edit.target?.action === "create" ? "Create" : "Apply changes"}
|
|
106
|
+
* </button>
|
|
107
|
+
* ```
|
|
108
|
+
*
|
|
109
|
+
* @see {@link EditResourceYamlDialog} for the styled component composing this hook
|
|
110
|
+
* @see {@link useApplyManifest} for the paste/upload counterpart
|
|
111
|
+
*/
|
|
112
|
+
export function useEditResourceYaml({
|
|
113
|
+
resource,
|
|
114
|
+
}: UseEditResourceYamlOptions): UseEditResourceYamlReturn {
|
|
115
|
+
const stigmer = useStigmer();
|
|
116
|
+
|
|
117
|
+
// The serialized source of truth. Recomputed when the resource changes;
|
|
118
|
+
// only reseeds the editor while the user hasn't started editing.
|
|
119
|
+
const seeded = useMemo(
|
|
120
|
+
() => (resource ? serializeManifest(resource) : ""),
|
|
121
|
+
[resource],
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
const [yaml, setYamlState] = useState(seeded);
|
|
125
|
+
const [isDirty, setIsDirty] = useState(false);
|
|
126
|
+
const [isApplying, setIsApplying] = useState(false);
|
|
127
|
+
const [error, setError] = useState<Error | null>(null);
|
|
128
|
+
const [target, setTarget] = useState<EditYamlTarget | null>(null);
|
|
129
|
+
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
if (!isDirty) setYamlState(seeded);
|
|
132
|
+
}, [seeded, isDirty]);
|
|
133
|
+
|
|
134
|
+
const setYaml = useCallback(
|
|
135
|
+
(value: string) => {
|
|
136
|
+
setYamlState(value);
|
|
137
|
+
setIsDirty(value !== seeded);
|
|
138
|
+
},
|
|
139
|
+
[seeded],
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
const reset = useCallback(() => {
|
|
143
|
+
setYamlState(seeded);
|
|
144
|
+
setIsDirty(false);
|
|
145
|
+
setError(null);
|
|
146
|
+
}, [seeded]);
|
|
147
|
+
|
|
148
|
+
const clearError = useCallback(() => setError(null), []);
|
|
149
|
+
|
|
150
|
+
const sourceKind = useMemo(() => {
|
|
151
|
+
if (!resource) return null;
|
|
152
|
+
// The seeded YAML's kind is authoritative for the source resource; a
|
|
153
|
+
// parse of our own serialization cannot fail.
|
|
154
|
+
return seeded ? parseManifest(seeded)[0].handler.yamlKind : null;
|
|
155
|
+
}, [resource, seeded]);
|
|
156
|
+
|
|
157
|
+
const validation = useMemo<EditYamlValidation>(() => {
|
|
158
|
+
if (!yaml.trim()) return { status: "empty" };
|
|
159
|
+
|
|
160
|
+
let documents: ManifestDocument[];
|
|
161
|
+
try {
|
|
162
|
+
documents = parseManifest(yaml);
|
|
163
|
+
} catch (err) {
|
|
164
|
+
return { status: "invalid", message: toError(err).message };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (documents.length > 1) {
|
|
168
|
+
return {
|
|
169
|
+
status: "invalid",
|
|
170
|
+
message:
|
|
171
|
+
"This editor applies a single resource. Remove the extra " +
|
|
172
|
+
"documents, or use Apply YAML in the Library for multi-document manifests.",
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const document = documents[0];
|
|
177
|
+
if (sourceKind !== null && document.handler.yamlKind !== sourceKind) {
|
|
178
|
+
return {
|
|
179
|
+
status: "invalid",
|
|
180
|
+
message:
|
|
181
|
+
`The document's kind changed from ${sourceKind} to ` +
|
|
182
|
+
`${document.handler.yamlKind}. To apply a different resource kind, ` +
|
|
183
|
+
"use Apply YAML in the Library instead.",
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return { status: "valid", document };
|
|
188
|
+
}, [yaml, sourceKind]);
|
|
189
|
+
|
|
190
|
+
// Resolve create-vs-update against live state whenever the content
|
|
191
|
+
// becomes valid, debounced across keystrokes and cancellation-safe.
|
|
192
|
+
const checkSeq = useRef(0);
|
|
193
|
+
useEffect(() => {
|
|
194
|
+
if (validation.status !== "valid") {
|
|
195
|
+
setTarget(null);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const { document } = validation;
|
|
200
|
+
const seq = ++checkSeq.current;
|
|
201
|
+
setTarget(null);
|
|
202
|
+
|
|
203
|
+
const timer = setTimeout(async () => {
|
|
204
|
+
let action: EditYamlTarget["action"];
|
|
205
|
+
try {
|
|
206
|
+
const existing = await stigmer.manifest.getByReference(
|
|
207
|
+
document.handler.yamlKind,
|
|
208
|
+
document.org,
|
|
209
|
+
document.slug,
|
|
210
|
+
);
|
|
211
|
+
action = existing !== null ? "update" : "create";
|
|
212
|
+
} catch {
|
|
213
|
+
action = "unknown";
|
|
214
|
+
}
|
|
215
|
+
if (checkSeq.current === seq) {
|
|
216
|
+
setTarget({ action, slug: document.slug });
|
|
217
|
+
}
|
|
218
|
+
}, TARGET_CHECK_DEBOUNCE_MS);
|
|
219
|
+
|
|
220
|
+
return () => clearTimeout(timer);
|
|
221
|
+
}, [validation, stigmer]);
|
|
222
|
+
|
|
223
|
+
const apply = useCallback(async (): Promise<AppliedManifest | null> => {
|
|
224
|
+
if (validation.status !== "valid") return null;
|
|
225
|
+
|
|
226
|
+
setIsApplying(true);
|
|
227
|
+
setError(null);
|
|
228
|
+
try {
|
|
229
|
+
const applied = await stigmer.manifest.apply(validation.document);
|
|
230
|
+
setIsDirty(false);
|
|
231
|
+
return applied;
|
|
232
|
+
} catch (err) {
|
|
233
|
+
setError(toError(err));
|
|
234
|
+
throw err;
|
|
235
|
+
} finally {
|
|
236
|
+
setIsApplying(false);
|
|
237
|
+
}
|
|
238
|
+
}, [validation, stigmer]);
|
|
239
|
+
|
|
240
|
+
const hasRedactedSecrets = useMemo(
|
|
241
|
+
() => containsRedactedSecrets(yaml),
|
|
242
|
+
[yaml],
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
return useMemo(
|
|
246
|
+
() => ({
|
|
247
|
+
yaml,
|
|
248
|
+
setYaml,
|
|
249
|
+
isDirty,
|
|
250
|
+
validation,
|
|
251
|
+
target,
|
|
252
|
+
hasRedactedSecrets,
|
|
253
|
+
apply,
|
|
254
|
+
isApplying,
|
|
255
|
+
error,
|
|
256
|
+
clearError,
|
|
257
|
+
reset,
|
|
258
|
+
}),
|
|
259
|
+
[
|
|
260
|
+
yaml,
|
|
261
|
+
setYaml,
|
|
262
|
+
isDirty,
|
|
263
|
+
validation,
|
|
264
|
+
target,
|
|
265
|
+
hasRedactedSecrets,
|
|
266
|
+
apply,
|
|
267
|
+
isApplying,
|
|
268
|
+
error,
|
|
269
|
+
clearError,
|
|
270
|
+
reset,
|
|
271
|
+
],
|
|
272
|
+
);
|
|
273
|
+
}
|
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import { useMemo } from "react";
|
|
4
4
|
import { cn } from "@stigmer/theme";
|
|
5
|
-
import { getUserMessage } from "@stigmer/sdk";
|
|
5
|
+
import { buildMcpServerProto, getUserMessage, serializeManifest } from "@stigmer/sdk";
|
|
6
6
|
import type { McpServerInput } from "@stigmer/sdk";
|
|
7
|
-
import { serializeMcpServerInputYaml } from "../../library/serialize-resource-yaml.js";
|
|
8
7
|
import type { McpServerWizardData } from "./types.js";
|
|
9
8
|
|
|
10
9
|
/** Props for {@link ReviewStep}. */
|
|
@@ -33,7 +32,7 @@ export function ReviewStep({
|
|
|
33
32
|
[org, data],
|
|
34
33
|
);
|
|
35
34
|
const yamlPreview = useMemo(
|
|
36
|
-
() =>
|
|
35
|
+
() => serializeManifest(buildMcpServerProto(mcpServerInput)),
|
|
37
36
|
[mcpServerInput],
|
|
38
37
|
);
|
|
39
38
|
|
|
@@ -122,7 +122,7 @@ spec:
|
|
|
122
122
|
- name: extract_findings
|
|
123
123
|
kind: llm_call
|
|
124
124
|
task_config:
|
|
125
|
-
model: "
|
|
125
|
+
model: "claude-haiku-4.5"
|
|
126
126
|
system_prompt: "Extract structured key findings from the analysis."
|
|
127
127
|
prompt: >
|
|
128
128
|
Extract the key findings from this analysis as structured data:
|
|
@@ -280,7 +280,7 @@ spec:
|
|
|
280
280
|
- name: classify_ticket
|
|
281
281
|
kind: llm_call
|
|
282
282
|
task_config:
|
|
283
|
-
model: "
|
|
283
|
+
model: "claude-haiku-4.5"
|
|
284
284
|
system_prompt: >
|
|
285
285
|
You are a support ticket classifier. Analyze the ticket and classify
|
|
286
286
|
it by severity and category. Be conservative with critical/high
|
|
@@ -923,7 +923,7 @@ spec:
|
|
|
923
923
|
- name: diagnose_error
|
|
924
924
|
kind: llm_call
|
|
925
925
|
task_config:
|
|
926
|
-
model: "
|
|
926
|
+
model: "claude-haiku-4.5"
|
|
927
927
|
system_prompt: >
|
|
928
928
|
You are an API integration specialist. Given an error from
|
|
929
929
|
an HTTP call, provide a brief, actionable diagnosis.
|
|
@@ -1010,7 +1010,7 @@ spec:
|
|
|
1010
1010
|
description: "Comma-separated list of topics the response should cover"
|
|
1011
1011
|
optional: true
|
|
1012
1012
|
MODEL_UNDER_TEST:
|
|
1013
|
-
description: "Model to evaluate (default:
|
|
1013
|
+
description: "Model to evaluate (default: claude-haiku-4.5)"
|
|
1014
1014
|
optional: true
|
|
1015
1015
|
budget:
|
|
1016
1016
|
max_cost_micros: 2000000
|
|
@@ -7,7 +7,7 @@ import { useStigmer } from "../hooks.js";
|
|
|
7
7
|
import { useAgent } from "../agent/index.js";
|
|
8
8
|
import { useMcpServer } from "../mcp-server/index.js";
|
|
9
9
|
import { useSkill } from "../skill/index.js";
|
|
10
|
-
import {
|
|
10
|
+
import { serializeManifest } from "@stigmer/sdk";
|
|
11
11
|
import type { DraftResourceType } from "./draft.js";
|
|
12
12
|
|
|
13
13
|
/** Return value of {@link useEditSessionPrep}. */
|
|
@@ -64,7 +64,7 @@ export function useEditSessionPrep(
|
|
|
64
64
|
if (draftType === "agent" && editAgent) {
|
|
65
65
|
built.current = true;
|
|
66
66
|
try {
|
|
67
|
-
const yaml =
|
|
67
|
+
const yaml = serializeManifest(editAgent);
|
|
68
68
|
const slug = editAgent.metadata?.slug ?? "agent";
|
|
69
69
|
setFiles([new File([yaml], `${slug}.yaml`, { type: "text/yaml" })]);
|
|
70
70
|
} catch {
|
|
@@ -75,7 +75,7 @@ export function useEditSessionPrep(
|
|
|
75
75
|
if (draftType === "mcp-server" && editMcpServer) {
|
|
76
76
|
built.current = true;
|
|
77
77
|
try {
|
|
78
|
-
const yaml =
|
|
78
|
+
const yaml = serializeManifest(editMcpServer);
|
|
79
79
|
const slug = editMcpServer.metadata?.slug ?? "mcp-server";
|
|
80
80
|
setFiles([new File([yaml], `${slug}.yaml`, { type: "text/yaml" })]);
|
|
81
81
|
} catch {
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { describe, it, expect, vi, afterEach } from "vitest";
|
|
2
|
+
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
|
|
3
|
+
import { useRef, useState } from "react";
|
|
4
|
+
import { SETTINGS_NAV_GROUPS, PLATFORM_SETTINGS_NAV_GROUP } from "../settings-nav";
|
|
5
|
+
import { useSettingsNavGroups } from "../useSettingsNavGroups";
|
|
6
|
+
|
|
7
|
+
// Drive the nav gate through the permission hook. The fail-closed
|
|
8
|
+
// semantics themselves are covered by useCheckPermission's own suite —
|
|
9
|
+
// here we assert the nav hook's per-item composition of the verdicts.
|
|
10
|
+
// Verdicts are keyed by relation so each platform permission can be
|
|
11
|
+
// granted independently.
|
|
12
|
+
let verdicts: Record<string, boolean> = {};
|
|
13
|
+
let checkedRelations: string[] = [];
|
|
14
|
+
let lastCheckArgs: readonly unknown[] = [];
|
|
15
|
+
|
|
16
|
+
vi.mock("../../iam-policy/useCheckPermission", () => ({
|
|
17
|
+
useCheckPermission: (
|
|
18
|
+
...args: [unknown, string, { fail?: string } | undefined]
|
|
19
|
+
) => {
|
|
20
|
+
lastCheckArgs = args;
|
|
21
|
+
checkedRelations.push(args[1]);
|
|
22
|
+
return { allowed: verdicts[args[1]] === true, isLoading: false, error: null };
|
|
23
|
+
},
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
cleanup();
|
|
28
|
+
verdicts = {};
|
|
29
|
+
checkedRelations = [];
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
function GroupsProbe() {
|
|
33
|
+
const groups = useSettingsNavGroups();
|
|
34
|
+
const platform = groups.find((g) => g.label === PLATFORM_SETTINGS_NAV_GROUP.label);
|
|
35
|
+
return (
|
|
36
|
+
<div
|
|
37
|
+
data-testid="groups"
|
|
38
|
+
data-labels={groups.map((g) => g.label).join(",")}
|
|
39
|
+
data-platform-items={platform?.items.map((i) => i.label).join(",") ?? ""}
|
|
40
|
+
/>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const BASE_LABELS = SETTINGS_NAV_GROUPS.map((g) => g.label).join(",");
|
|
45
|
+
|
|
46
|
+
describe("useSettingsNavGroups", () => {
|
|
47
|
+
it("appends the full Platform group when the operator holds every platform permission", () => {
|
|
48
|
+
verdicts = {
|
|
49
|
+
can_manage_model_pricing: true,
|
|
50
|
+
can_manage_cursor_accounts: true,
|
|
51
|
+
};
|
|
52
|
+
render(<GroupsProbe />);
|
|
53
|
+
|
|
54
|
+
const el = screen.getByTestId("groups");
|
|
55
|
+
expect(el.getAttribute("data-labels")).toBe(
|
|
56
|
+
`${BASE_LABELS},${PLATFORM_SETTINGS_NAV_GROUP.label}`,
|
|
57
|
+
);
|
|
58
|
+
expect(el.getAttribute("data-platform-items")).toBe(
|
|
59
|
+
"Pricing Governance,Cursor Accounts",
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("filters per item: pricing-only operator sees only Pricing Governance", () => {
|
|
64
|
+
verdicts = { can_manage_model_pricing: true };
|
|
65
|
+
render(<GroupsProbe />);
|
|
66
|
+
|
|
67
|
+
expect(screen.getByTestId("groups").getAttribute("data-platform-items")).toBe(
|
|
68
|
+
"Pricing Governance",
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("filters per item: cursor-accounts-only operator sees only Cursor Accounts", () => {
|
|
73
|
+
verdicts = { can_manage_cursor_accounts: true };
|
|
74
|
+
render(<GroupsProbe />);
|
|
75
|
+
|
|
76
|
+
expect(screen.getByTestId("groups").getAttribute("data-platform-items")).toBe(
|
|
77
|
+
"Cursor Accounts",
|
|
78
|
+
);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("returns exactly the base groups when not authorized", () => {
|
|
82
|
+
function IdentityProbe() {
|
|
83
|
+
const groups = useSettingsNavGroups();
|
|
84
|
+
return (
|
|
85
|
+
<div
|
|
86
|
+
data-testid="identity"
|
|
87
|
+
data-is-base={String(groups === SETTINGS_NAV_GROUPS)}
|
|
88
|
+
/>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
render(<IdentityProbe />);
|
|
92
|
+
|
|
93
|
+
// Not just equivalent — the same array, no needless copy.
|
|
94
|
+
expect(screen.getByTestId("identity").getAttribute("data-is-base")).toBe("true");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("checks every declared platform permission on platform:stigmer, fail-closed", () => {
|
|
98
|
+
render(<GroupsProbe />);
|
|
99
|
+
|
|
100
|
+
// Exhaustiveness guard: every requiredPermission declared on the
|
|
101
|
+
// platform group must have a corresponding hook check — a new
|
|
102
|
+
// platform surface that forgets to add its check fails here.
|
|
103
|
+
const declared = PLATFORM_SETTINGS_NAV_GROUP.items.map(
|
|
104
|
+
(item) => item.requiredPermission,
|
|
105
|
+
);
|
|
106
|
+
for (const permission of declared) {
|
|
107
|
+
expect(permission, "platform items must declare requiredPermission").toBeDefined();
|
|
108
|
+
expect(checkedRelations).toContain(permission);
|
|
109
|
+
}
|
|
110
|
+
expect(lastCheckArgs[0]).toEqual({ kind: "platform", id: "stigmer" });
|
|
111
|
+
expect(lastCheckArgs[2]).toEqual({ fail: "closed" });
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("keeps the array reference stable across re-renders", () => {
|
|
115
|
+
verdicts = {
|
|
116
|
+
can_manage_model_pricing: true,
|
|
117
|
+
can_manage_cursor_accounts: true,
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
function StabilityProbe() {
|
|
121
|
+
const groups = useSettingsNavGroups();
|
|
122
|
+
const first = useRef(groups);
|
|
123
|
+
const [, force] = useState(0);
|
|
124
|
+
return (
|
|
125
|
+
<button
|
|
126
|
+
data-testid="stability"
|
|
127
|
+
data-stable={String(first.current === groups)}
|
|
128
|
+
onClick={() => force((n) => n + 1)}
|
|
129
|
+
/>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
render(<StabilityProbe />);
|
|
134
|
+
const el = screen.getByTestId("stability");
|
|
135
|
+
fireEvent.click(el);
|
|
136
|
+
expect(el.getAttribute("data-stable")).toBe("true");
|
|
137
|
+
});
|
|
138
|
+
});
|
package/src/settings/index.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export {
|
|
2
|
+
SETTINGS_NAV_GROUPS,
|
|
3
|
+
PLATFORM_SETTINGS_NAV_GROUP,
|
|
4
|
+
} from "./settings-nav.js";
|
|
2
5
|
export type { SettingsNavItem, SettingsNavGroup } from "./settings-nav.js";
|
|
6
|
+
export { useSettingsNavGroups } from "./useSettingsNavGroups.js";
|
|
3
7
|
|
|
4
8
|
export { ApiKeysSection } from "./ApiKeysSection.js";
|
|
5
9
|
export { BillingSection } from "./BillingSection.js";
|
|
@@ -8,7 +8,9 @@ import {
|
|
|
8
8
|
KeyRound,
|
|
9
9
|
Link,
|
|
10
10
|
MessageSquare,
|
|
11
|
+
MousePointerClick,
|
|
11
12
|
Plug,
|
|
13
|
+
Scale,
|
|
12
14
|
ShieldCheck,
|
|
13
15
|
Users,
|
|
14
16
|
} from "lucide-react";
|
|
@@ -24,6 +26,14 @@ export interface SettingsNavItem {
|
|
|
24
26
|
/** Optional CSS class name applied to the icon. */
|
|
25
27
|
className?: string;
|
|
26
28
|
}>;
|
|
29
|
+
/**
|
|
30
|
+
* Platform-level permission (on `platform:stigmer`) required for this
|
|
31
|
+
* entry to appear. Only meaningful inside
|
|
32
|
+
* {@link PLATFORM_SETTINGS_NAV_GROUP} — items in the base groups are
|
|
33
|
+
* visible to every signed-in user and leave this unset.
|
|
34
|
+
* {@link useSettingsNavGroups} checks it fail-closed.
|
|
35
|
+
*/
|
|
36
|
+
readonly requiredPermission?: string;
|
|
27
37
|
}
|
|
28
38
|
|
|
29
39
|
/** Grouping model used to render settings navigation sections. */
|
|
@@ -82,3 +92,36 @@ export const SETTINGS_NAV_GROUPS: readonly SettingsNavGroup[] = [
|
|
|
82
92
|
],
|
|
83
93
|
},
|
|
84
94
|
];
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Stigmer-internal platform-operator navigation group.
|
|
98
|
+
*
|
|
99
|
+
* Deliberately NOT part of {@link SETTINGS_NAV_GROUPS}: that constant
|
|
100
|
+
* means "groups every signed-in user sees", and its members must never
|
|
101
|
+
* change based on who is looking. This group is appended for platform
|
|
102
|
+
* operators only — use {@link useSettingsNavGroups} to get the
|
|
103
|
+
* permission-aware list instead of composing the two by hand.
|
|
104
|
+
*
|
|
105
|
+
* Every item here declares its `requiredPermission`; the hook filters
|
|
106
|
+
* per item (fail-closed), so an operator holding one platform permission
|
|
107
|
+
* but not another sees exactly their slice of this group.
|
|
108
|
+
*/
|
|
109
|
+
export const PLATFORM_SETTINGS_NAV_GROUP: SettingsNavGroup = {
|
|
110
|
+
label: "Platform",
|
|
111
|
+
description:
|
|
112
|
+
"Stigmer-internal operations. Visible to platform operators only.",
|
|
113
|
+
items: [
|
|
114
|
+
{
|
|
115
|
+
href: "/settings/pricing-governance",
|
|
116
|
+
label: "Pricing Governance",
|
|
117
|
+
icon: Scale,
|
|
118
|
+
requiredPermission: "can_manage_model_pricing",
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
href: "/settings/cursor-accounts",
|
|
122
|
+
label: "Cursor Accounts",
|
|
123
|
+
icon: MousePointerClick,
|
|
124
|
+
requiredPermission: "can_manage_cursor_accounts",
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
import { useCheckPermission } from "../iam-policy/useCheckPermission.js";
|
|
5
|
+
import {
|
|
6
|
+
PLATFORM_SETTINGS_NAV_GROUP,
|
|
7
|
+
SETTINGS_NAV_GROUPS,
|
|
8
|
+
type SettingsNavGroup,
|
|
9
|
+
} from "./settings-nav.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The `platform` singleton — platform-level permissions are always
|
|
13
|
+
* checked against this one resource (mirrors the server-side
|
|
14
|
+
* `resource_id = "stigmer"` RPC config).
|
|
15
|
+
*/
|
|
16
|
+
const PLATFORM_RESOURCE = { kind: "platform", id: "stigmer" } as const;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Permission-aware settings navigation: {@link SETTINGS_NAV_GROUPS}
|
|
20
|
+
* for everyone, plus the {@link PLATFORM_SETTINGS_NAV_GROUP} items the
|
|
21
|
+
* caller's platform permissions unlock (per-item `requiredPermission`,
|
|
22
|
+
* checked against `platform:stigmer`). The group appears when at least
|
|
23
|
+
* one of its items is visible.
|
|
24
|
+
*
|
|
25
|
+
* The checks run fail-closed — the opposite of the `useCheckPermission`
|
|
26
|
+
* default — because navigation is *discoverability*, not *capability*:
|
|
27
|
+
* an action gate can safely fail open (the server re-checks every
|
|
28
|
+
* request), but an operator-only nav entry must not appear while the
|
|
29
|
+
* check is loading, when it errors, or on deployments where the IAM
|
|
30
|
+
* service (and the surface it points to) does not exist.
|
|
31
|
+
*
|
|
32
|
+
* Implementation note: hooks must be called unconditionally, so each
|
|
33
|
+
* distinct platform permission gets its own static check below. When a
|
|
34
|
+
* new platform surface adds a new permission, add its check to
|
|
35
|
+
* PLATFORM_PERMISSION_CHECKS' shape here — the exhaustiveness guard in
|
|
36
|
+
* the tests catches a forgotten one.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```tsx
|
|
40
|
+
* const groups = useSettingsNavGroups();
|
|
41
|
+
* return groups.map((group) => <NavGroup key={group.label} group={group} />);
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export function useSettingsNavGroups(): readonly SettingsNavGroup[] {
|
|
45
|
+
const pricing = useCheckPermission(
|
|
46
|
+
PLATFORM_RESOURCE,
|
|
47
|
+
"can_manage_model_pricing",
|
|
48
|
+
{ fail: "closed" },
|
|
49
|
+
);
|
|
50
|
+
const cursorAccounts = useCheckPermission(
|
|
51
|
+
PLATFORM_RESOURCE,
|
|
52
|
+
"can_manage_cursor_accounts",
|
|
53
|
+
{ fail: "closed" },
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const pricingAllowed = pricing.allowed;
|
|
57
|
+
const cursorAccountsAllowed = cursorAccounts.allowed;
|
|
58
|
+
|
|
59
|
+
return useMemo(() => {
|
|
60
|
+
const verdicts: Record<string, boolean> = {
|
|
61
|
+
can_manage_model_pricing: pricingAllowed,
|
|
62
|
+
can_manage_cursor_accounts: cursorAccountsAllowed,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const visibleItems = PLATFORM_SETTINGS_NAV_GROUP.items.filter(
|
|
66
|
+
// Fail closed on both axes: an item with no declared permission or
|
|
67
|
+
// with a permission this hook does not check yet stays hidden.
|
|
68
|
+
(item) =>
|
|
69
|
+
item.requiredPermission !== undefined &&
|
|
70
|
+
verdicts[item.requiredPermission] === true,
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
if (visibleItems.length === 0) {
|
|
74
|
+
return SETTINGS_NAV_GROUPS;
|
|
75
|
+
}
|
|
76
|
+
return [
|
|
77
|
+
...SETTINGS_NAV_GROUPS,
|
|
78
|
+
{ ...PLATFORM_SETTINGS_NAV_GROUP, items: visibleItems },
|
|
79
|
+
];
|
|
80
|
+
}, [pricingAllowed, cursorAccountsAllowed]);
|
|
81
|
+
}
|