create-principles-disciple 1.146.3 → 1.146.6
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/_release/product-identity.json +1 -1
- package/console/dist/web/assets/app.js +26 -26
- package/console/package.json +1 -1
- package/package.json +3 -3
- package/pd-cli/package.json +1 -1
- package/release-manager/package.json +3 -3
- package/console/dist/ui/App.d.ts +0 -1
- package/console/dist/ui/App.js +0 -172
- package/console/dist/ui/api.d.ts +0 -611
- package/console/dist/ui/api.js +0 -1094
- package/console/dist/ui/components/auth/login-form.d.ts +0 -4
- package/console/dist/ui/components/auth/login-form.js +0 -142
- package/console/dist/ui/components/auth/splash-screen.d.ts +0 -9
- package/console/dist/ui/components/auth/splash-screen.js +0 -22
- package/console/dist/ui/components/error-boundary.d.ts +0 -15
- package/console/dist/ui/components/error-boundary.js +0 -30
- package/console/dist/ui/components/focus/daily-thought-card.d.ts +0 -1
- package/console/dist/ui/components/focus/daily-thought-card.js +0 -41
- package/console/dist/ui/components/language-switcher.d.ts +0 -2
- package/console/dist/ui/components/language-switcher.js +0 -17
- package/console/dist/ui/components/layout/app-sidebar.d.ts +0 -27
- package/console/dist/ui/components/layout/app-sidebar.js +0 -128
- package/console/dist/ui/components/layout/page-loading.d.ts +0 -13
- package/console/dist/ui/components/layout/page-loading.js +0 -14
- package/console/dist/ui/components/layout/page-shell.d.ts +0 -12
- package/console/dist/ui/components/layout/page-shell.js +0 -11
- package/console/dist/ui/components/layout/section-title.d.ts +0 -10
- package/console/dist/ui/components/layout/section-title.js +0 -9
- package/console/dist/ui/components/notifications/NotificationProvider.d.ts +0 -14
- package/console/dist/ui/components/notifications/NotificationProvider.js +0 -136
- package/console/dist/ui/components/notifications/favicon-badge.d.ts +0 -3
- package/console/dist/ui/components/notifications/favicon-badge.js +0 -68
- package/console/dist/ui/components/notifications/notification-reducer.d.ts +0 -9
- package/console/dist/ui/components/notifications/notification-reducer.js +0 -9
- package/console/dist/ui/components/notifications/sound-storage.d.ts +0 -3
- package/console/dist/ui/components/notifications/sound-storage.js +0 -37
- package/console/dist/ui/components/notifications/useNotifications.d.ts +0 -1
- package/console/dist/ui/components/notifications/useNotifications.js +0 -9
- package/console/dist/ui/components/onboarding/CircuitDiagram.d.ts +0 -8
- package/console/dist/ui/components/onboarding/CircuitDiagram.js +0 -18
- package/console/dist/ui/components/onboarding/DemoResultView.d.ts +0 -22
- package/console/dist/ui/components/onboarding/DemoResultView.js +0 -33
- package/console/dist/ui/components/onboarding/SlashCommandsCard.d.ts +0 -16
- package/console/dist/ui/components/onboarding/SlashCommandsCard.js +0 -20
- package/console/dist/ui/components/onboarding/slashCommands.d.ts +0 -20
- package/console/dist/ui/components/onboarding/slashCommands.js +0 -22
- package/console/dist/ui/components/receipts/ReceiptCoverageDisclosure.d.ts +0 -6
- package/console/dist/ui/components/receipts/ReceiptCoverageDisclosure.js +0 -66
- package/console/dist/ui/components/theme-provider.d.ts +0 -14
- package/console/dist/ui/components/theme-provider.js +0 -40
- package/console/dist/ui/components/theme-toggle.d.ts +0 -2
- package/console/dist/ui/components/theme-toggle.js +0 -11
- package/console/dist/ui/components/ui/alert-dialog.d.ts +0 -20
- package/console/dist/ui/components/ui/alert-dialog.js +0 -26
- package/console/dist/ui/components/ui/badge.d.ts +0 -9
- package/console/dist/ui/components/ui/badge.js +0 -22
- package/console/dist/ui/components/ui/button.d.ts +0 -11
- package/console/dist/ui/components/ui/button.js +0 -34
- package/console/dist/ui/components/ui/card.d.ts +0 -8
- package/console/dist/ui/components/ui/card.js +0 -16
- package/console/dist/ui/components/ui/charts.d.ts +0 -51
- package/console/dist/ui/components/ui/charts.js +0 -72
- package/console/dist/ui/components/ui/dialog.d.ts +0 -19
- package/console/dist/ui/components/ui/dialog.js +0 -22
- package/console/dist/ui/components/ui/input.d.ts +0 -3
- package/console/dist/ui/components/ui/input.js +0 -8
- package/console/dist/ui/components/ui/markdown.d.ts +0 -24
- package/console/dist/ui/components/ui/markdown.js +0 -119
- package/console/dist/ui/components/ui/progress-bar.d.ts +0 -20
- package/console/dist/ui/components/ui/progress-bar.js +0 -38
- package/console/dist/ui/components/ui/select.d.ts +0 -13
- package/console/dist/ui/components/ui/select.js +0 -26
- package/console/dist/ui/components/ui/separator.d.ts +0 -3
- package/console/dist/ui/components/ui/separator.js +0 -6
- package/console/dist/ui/components/ui/sheet.d.ts +0 -25
- package/console/dist/ui/components/ui/sheet.js +0 -37
- package/console/dist/ui/components/ui/shiny-text.d.ts +0 -28
- package/console/dist/ui/components/ui/shiny-text.js +0 -46
- package/console/dist/ui/components/ui/skeleton.d.ts +0 -3
- package/console/dist/ui/components/ui/skeleton.js +0 -6
- package/console/dist/ui/components/ui/sonner.d.ts +0 -3
- package/console/dist/ui/components/ui/sonner.js +0 -13
- package/console/dist/ui/components/ui/tooltip.d.ts +0 -7
- package/console/dist/ui/components/ui/tooltip.js +0 -10
- package/console/dist/ui/data/daily-thoughts.d.ts +0 -15
- package/console/dist/ui/data/daily-thoughts.js +0 -164
- package/console/dist/ui/hooks/useNotificationSound.d.ts +0 -12
- package/console/dist/ui/hooks/useNotificationSound.js +0 -74
- package/console/dist/ui/i18n/en.json +0 -2399
- package/console/dist/ui/i18n/index.d.ts +0 -2
- package/console/dist/ui/i18n/index.js +0 -32
- package/console/dist/ui/i18n/zh-CN.json +0 -2399
- package/console/dist/ui/main.d.ts +0 -1
- package/console/dist/ui/main.js +0 -9
- package/console/dist/ui/pages/ReportProblemValidators.d.ts +0 -144
- package/console/dist/ui/pages/ReportProblemValidators.js +0 -418
- package/console/dist/ui/pages/activation/ActivationPage.d.ts +0 -1
- package/console/dist/ui/pages/activation/ActivationPage.js +0 -512
- package/console/dist/ui/pages/activation/ActivationValidators.d.ts +0 -20
- package/console/dist/ui/pages/activation/ActivationValidators.js +0 -160
- package/console/dist/ui/pages/control-center/AgentCard.d.ts +0 -31
- package/console/dist/ui/pages/control-center/AgentCard.js +0 -212
- package/console/dist/ui/pages/control-center/AgentGroup.d.ts +0 -26
- package/console/dist/ui/pages/control-center/AgentGroup.js +0 -38
- package/console/dist/ui/pages/control-center/ControlCenterPage.d.ts +0 -1
- package/console/dist/ui/pages/control-center/ControlCenterPage.js +0 -509
- package/console/dist/ui/pages/control-center/DesignGatePanel.d.ts +0 -1
- package/console/dist/ui/pages/control-center/DesignGatePanel.js +0 -137
- package/console/dist/ui/pages/control-center/RuntimeProfileManager.d.ts +0 -31
- package/console/dist/ui/pages/control-center/RuntimeProfileManager.js +0 -199
- package/console/dist/ui/pages/control-center/WorkflowDiagram.d.ts +0 -17
- package/console/dist/ui/pages/control-center/WorkflowDiagram.js +0 -55
- package/console/dist/ui/pages/debt/DebtPage.d.ts +0 -2
- package/console/dist/ui/pages/debt/DebtPage.js +0 -222
- package/console/dist/ui/pages/design-system/DesignSystemPage.d.ts +0 -2
- package/console/dist/ui/pages/design-system/DesignSystemPage.js +0 -30
- package/console/dist/ui/pages/examiner/ExaminerPage.d.ts +0 -15
- package/console/dist/ui/pages/examiner/ExaminerPage.js +0 -158
- package/console/dist/ui/pages/failed-tasks/FailedTasksPage.d.ts +0 -1
- package/console/dist/ui/pages/failed-tasks/FailedTasksPage.js +0 -421
- package/console/dist/ui/pages/focus/ApprovalSubjectReading.d.ts +0 -24
- package/console/dist/ui/pages/focus/ApprovalSubjectReading.js +0 -133
- package/console/dist/ui/pages/focus/FocusPage.d.ts +0 -123
- package/console/dist/ui/pages/focus/FocusPage.js +0 -567
- package/console/dist/ui/pages/focus/OwnerDecisionCard.d.ts +0 -9
- package/console/dist/ui/pages/focus/OwnerDecisionCard.js +0 -111
- package/console/dist/ui/pages/focus/OwnerWorkbench.d.ts +0 -19
- package/console/dist/ui/pages/focus/OwnerWorkbench.js +0 -75
- package/console/dist/ui/pages/focus/RuleCandidateBindingPanel.d.ts +0 -34
- package/console/dist/ui/pages/focus/RuleCandidateBindingPanel.js +0 -298
- package/console/dist/ui/pages/focus/focus-validation.d.ts +0 -40
- package/console/dist/ui/pages/focus/focus-validation.js +0 -211
- package/console/dist/ui/pages/intent/IntentOnboarding.d.ts +0 -67
- package/console/dist/ui/pages/intent/IntentOnboarding.js +0 -289
- package/console/dist/ui/pages/intent/IntentPage.d.ts +0 -1
- package/console/dist/ui/pages/intent/IntentPage.js +0 -252
- package/console/dist/ui/pages/pain/PainEvidenceValidators.d.ts +0 -59
- package/console/dist/ui/pages/pain/PainEvidenceValidators.js +0 -125
- package/console/dist/ui/pages/pain/PainPage.d.ts +0 -1
- package/console/dist/ui/pages/pain/PainPage.js +0 -420
- package/console/dist/ui/pages/pain/pain-card-helpers.d.ts +0 -175
- package/console/dist/ui/pages/pain/pain-card-helpers.js +0 -181
- package/console/dist/ui/pages/principles/CorePrinciplesPage.d.ts +0 -10
- package/console/dist/ui/pages/principles/CorePrinciplesPage.js +0 -59
- package/console/dist/ui/pages/principles/PrincipleDetailPage.d.ts +0 -60
- package/console/dist/ui/pages/principles/PrincipleDetailPage.js +0 -602
- package/console/dist/ui/pages/principles/PrinciplesPage.d.ts +0 -1
- package/console/dist/ui/pages/principles/PrinciplesPage.js +0 -327
- package/console/dist/ui/pages/principles/PrinciplesViewNav.d.ts +0 -8
- package/console/dist/ui/pages/principles/PrinciplesViewNav.js +0 -15
- package/console/dist/ui/pages/report-problem/ReportProblemPage.d.ts +0 -1
- package/console/dist/ui/pages/report-problem/ReportProblemPage.js +0 -467
- package/console/dist/ui/pages/settings/SettingsPage.d.ts +0 -4
- package/console/dist/ui/pages/settings/SettingsPage.js +0 -457
- package/console/dist/ui/pages/settings/UpdatePage.d.ts +0 -1
- package/console/dist/ui/pages/settings/UpdatePage.js +0 -215
- package/console/dist/ui/pages/welcome/WelcomePage.d.ts +0 -5
- package/console/dist/ui/pages/welcome/WelcomePage.js +0 -187
- package/console/dist/ui/utils/agent-metadata.d.ts +0 -71
- package/console/dist/ui/utils/agent-metadata.js +0 -284
- package/console/dist/ui/utils/approval-review.d.ts +0 -30
- package/console/dist/ui/utils/approval-review.js +0 -50
- package/console/dist/ui/utils/approval-warning-localization.d.ts +0 -34
- package/console/dist/ui/utils/approval-warning-localization.js +0 -148
- package/console/dist/ui/utils/attention-presentation.d.ts +0 -35
- package/console/dist/ui/utils/attention-presentation.js +0 -46
- package/console/dist/ui/utils/blocker-localization.d.ts +0 -23
- package/console/dist/ui/utils/blocker-localization.js +0 -36
- package/console/dist/ui/utils/control-center-helpers.d.ts +0 -79
- package/console/dist/ui/utils/control-center-helpers.js +0 -189
- package/console/dist/ui/utils/enum-labels.d.ts +0 -50
- package/console/dist/ui/utils/enum-labels.js +0 -171
- package/console/dist/ui/utils/format-date.d.ts +0 -11
- package/console/dist/ui/utils/format-date.js +0 -17
- package/console/dist/ui/utils/onboarding-state.d.ts +0 -25
- package/console/dist/ui/utils/onboarding-state.js +0 -77
- package/console/dist/ui/utils/reading-layout.d.ts +0 -20
- package/console/dist/ui/utils/reading-layout.js +0 -46
- package/console/dist/ui/utils/rule-binding-validators.d.ts +0 -13
- package/console/dist/ui/utils/rule-binding-validators.js +0 -188
- package/console/dist/ui/utils/runtime-mode.d.ts +0 -11
- package/console/dist/ui/utils/runtime-mode.js +0 -11
- package/console/dist/ui/utils/token-storage.d.ts +0 -22
- package/console/dist/ui/utils/token-storage.js +0 -47
- package/console/dist/ui/utils/validators.d.ts +0 -1178
- package/console/dist/ui/utils/validators.js +0 -3297
- package/release-manager/dist/bootstrap-entry.d.ts +0 -2
- package/release-manager/dist/bootstrap-entry.d.ts.map +0 -1
- package/release-manager/dist/bootstrap-entry.js.map +0 -1
- package/release-manager/dist/i18n.d.ts +0 -6
- package/release-manager/dist/i18n.d.ts.map +0 -1
- package/release-manager/dist/i18n.js.map +0 -1
- package/release-manager/dist/index.d.ts +0 -7
- package/release-manager/dist/index.d.ts.map +0 -1
- package/release-manager/dist/index.js +0 -656
- package/release-manager/dist/index.js.map +0 -1
- package/release-manager/dist/installer.d.ts +0 -629
- package/release-manager/dist/installer.d.ts.map +0 -1
- package/release-manager/dist/installer.js.map +0 -1
- package/release-manager/dist/installers/codex-host-installer.d.ts +0 -39
- package/release-manager/dist/installers/codex-host-installer.d.ts.map +0 -1
- package/release-manager/dist/installers/codex-host-installer.js.map +0 -1
- package/release-manager/dist/installers/index.d.ts +0 -51
- package/release-manager/dist/installers/index.d.ts.map +0 -1
- package/release-manager/dist/installers/index.js.map +0 -1
- package/release-manager/dist/installers/openclaw-host-installer.d.ts +0 -14
- package/release-manager/dist/installers/openclaw-host-installer.d.ts.map +0 -1
- package/release-manager/dist/installers/openclaw-host-installer.js.map +0 -1
- package/release-manager/dist/installers/workbuddy-host-installer.d.ts +0 -335
- package/release-manager/dist/installers/workbuddy-host-installer.d.ts.map +0 -1
- package/release-manager/dist/installers/workbuddy-host-installer.js.map +0 -1
- package/release-manager/dist/mvp-config.d.ts +0 -233
- package/release-manager/dist/mvp-config.d.ts.map +0 -1
- package/release-manager/dist/mvp-config.js.map +0 -1
- package/release-manager/dist/prompts.d.ts +0 -43
- package/release-manager/dist/prompts.d.ts.map +0 -1
- package/release-manager/dist/prompts.js +0 -268
- package/release-manager/dist/prompts.js.map +0 -1
- package/release-manager/dist/skill-language.d.ts +0 -32
- package/release-manager/dist/skill-language.d.ts.map +0 -1
- package/release-manager/dist/skill-language.js.map +0 -1
- package/release-manager/dist/uninstaller.d.ts +0 -68
- package/release-manager/dist/uninstaller.d.ts.map +0 -1
- package/release-manager/dist/uninstaller.js +0 -746
- package/release-manager/dist/uninstaller.js.map +0 -1
- package/release-manager/dist/update/apply-payload.d.ts +0 -143
- package/release-manager/dist/update/apply-payload.d.ts.map +0 -1
- package/release-manager/dist/update/apply-payload.js.map +0 -1
- package/release-manager/dist/update/atomic-file.d.ts +0 -35
- package/release-manager/dist/update/atomic-file.d.ts.map +0 -1
- package/release-manager/dist/update/atomic-file.js.map +0 -1
- package/release-manager/dist/update/bootstrap-executor.d.ts +0 -21
- package/release-manager/dist/update/bootstrap-executor.d.ts.map +0 -1
- package/release-manager/dist/update/bootstrap-executor.js.map +0 -1
- package/release-manager/dist/update/bootstrap-protocol.d.ts +0 -56
- package/release-manager/dist/update/bootstrap-protocol.d.ts.map +0 -1
- package/release-manager/dist/update/bootstrap-protocol.js.map +0 -1
- package/release-manager/dist/update/channel-metadata.d.ts +0 -28
- package/release-manager/dist/update/channel-metadata.d.ts.map +0 -1
- package/release-manager/dist/update/channel-metadata.js.map +0 -1
- package/release-manager/dist/update/channel-promotion.d.ts +0 -39
- package/release-manager/dist/update/channel-promotion.d.ts.map +0 -1
- package/release-manager/dist/update/channel-promotion.js +0 -55
- package/release-manager/dist/update/channel-promotion.js.map +0 -1
- package/release-manager/dist/update/console-surface.d.ts +0 -19
- package/release-manager/dist/update/console-surface.d.ts.map +0 -1
- package/release-manager/dist/update/console-surface.js.map +0 -1
- package/release-manager/dist/update/data-compatibility.d.ts +0 -35
- package/release-manager/dist/update/data-compatibility.d.ts.map +0 -1
- package/release-manager/dist/update/data-compatibility.js +0 -45
- package/release-manager/dist/update/data-compatibility.js.map +0 -1
- package/release-manager/dist/update/install-layout.d.ts +0 -105
- package/release-manager/dist/update/install-layout.d.ts.map +0 -1
- package/release-manager/dist/update/install-layout.js.map +0 -1
- package/release-manager/dist/update/product-identity.d.ts +0 -70
- package/release-manager/dist/update/product-identity.d.ts.map +0 -1
- package/release-manager/dist/update/product-identity.js.map +0 -1
- package/release-manager/dist/update/release-asset-manifest.d.ts +0 -31
- package/release-manager/dist/update/release-asset-manifest.d.ts.map +0 -1
- package/release-manager/dist/update/release-asset-manifest.js.map +0 -1
- package/release-manager/dist/update/release-identity.d.ts +0 -33
- package/release-manager/dist/update/release-identity.d.ts.map +0 -1
- package/release-manager/dist/update/release-identity.js.map +0 -1
- package/release-manager/dist/update/release-manager-authority.d.ts +0 -39
- package/release-manager/dist/update/release-manager-authority.d.ts.map +0 -1
- package/release-manager/dist/update/release-manager-authority.js.map +0 -1
- package/release-manager/dist/update/release-manager.d.ts +0 -156
- package/release-manager/dist/update/release-manager.d.ts.map +0 -1
- package/release-manager/dist/update/release-manager.js.map +0 -1
- package/release-manager/dist/update/release-metadata-publisher.d.ts +0 -199
- package/release-manager/dist/update/release-metadata-publisher.d.ts.map +0 -1
- package/release-manager/dist/update/release-metadata-publisher.js +0 -478
- package/release-manager/dist/update/release-metadata-publisher.js.map +0 -1
- package/release-manager/dist/update/release-metadata-source.d.ts +0 -85
- package/release-manager/dist/update/release-metadata-source.d.ts.map +0 -1
- package/release-manager/dist/update/release-metadata-source.js.map +0 -1
- package/release-manager/dist/update/release-metadata.d.ts +0 -60
- package/release-manager/dist/update/release-metadata.d.ts.map +0 -1
- package/release-manager/dist/update/release-metadata.js.map +0 -1
- package/release-manager/dist/update/release-policy.d.ts +0 -51
- package/release-manager/dist/update/release-policy.d.ts.map +0 -1
- package/release-manager/dist/update/release-policy.js.map +0 -1
- package/release-manager/dist/update/rollback-policy.d.ts +0 -56
- package/release-manager/dist/update/rollback-policy.d.ts.map +0 -1
- package/release-manager/dist/update/rollback-policy.js +0 -61
- package/release-manager/dist/update/rollback-policy.js.map +0 -1
- package/release-manager/dist/update/transaction-journal.d.ts +0 -212
- package/release-manager/dist/update/transaction-journal.d.ts.map +0 -1
- package/release-manager/dist/update/transaction-journal.js.map +0 -1
- package/release-manager/dist/update/trust-metadata.d.ts +0 -55
- package/release-manager/dist/update/trust-metadata.d.ts.map +0 -1
- package/release-manager/dist/update/trust-metadata.js.map +0 -1
- package/release-manager/dist/update/trust-root-provisioning.d.ts +0 -99
- package/release-manager/dist/update/trust-root-provisioning.d.ts.map +0 -1
- package/release-manager/dist/update/trust-root-provisioning.js.map +0 -1
- package/release-manager/dist/update/update-history.d.ts +0 -46
- package/release-manager/dist/update/update-history.d.ts.map +0 -1
- package/release-manager/dist/update/update-history.js +0 -123
- package/release-manager/dist/update/update-history.js.map +0 -1
- package/release-manager/dist/utils/config-file-io.d.ts +0 -34
- package/release-manager/dist/utils/config-file-io.d.ts.map +0 -1
- package/release-manager/dist/utils/config-file-io.js.map +0 -1
- package/release-manager/dist/utils/env.d.ts +0 -94
- package/release-manager/dist/utils/env.d.ts.map +0 -1
- package/release-manager/dist/utils/env.js.map +0 -1
- package/release-manager/dist/utils/logger.d.ts +0 -15
- package/release-manager/dist/utils/logger.d.ts.map +0 -1
- package/release-manager/dist/utils/logger.js.map +0 -1
- package/release-manager/dist/utils/workspace-divergence.d.ts +0 -36
- package/release-manager/dist/utils/workspace-divergence.d.ts.map +0 -1
- package/release-manager/dist/utils/workspace-divergence.js.map +0 -1
|
@@ -1,421 +0,0 @@
|
|
|
1
|
-
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
/**
|
|
3
|
-
* FailedTasksPage — Pipeline observability page showing failed or
|
|
4
|
-
* needs_human_review tasks, grouped by taskKind.
|
|
5
|
-
*
|
|
6
|
-
* Part of the PD feedback-pipeline observability spec (Task 10).
|
|
7
|
-
* Gated by the `failed_tasks_observability` feature flag. When the flag is
|
|
8
|
-
* disabled, the backend returns 403 and this page shows a "disabled" state
|
|
9
|
-
* with guidance on how to enable the flag.
|
|
10
|
-
*
|
|
11
|
-
* Each row exposes a "Create Feedback Draft" button that deep-links into the
|
|
12
|
-
* Report Problem page with the task/pain context pre-filled.
|
|
13
|
-
*
|
|
14
|
-
* Governance Recovery Actions v1: when the `failed_task_recovery_console`
|
|
15
|
-
* feature flag is enabled, each row also exposes a "Resume Evolution" button.
|
|
16
|
-
* Recovery is an Owner governance action (never auto-triggered on load): it
|
|
17
|
-
* always goes through an explicit confirmation dialog before POSTing
|
|
18
|
-
* /api/v1/failed-tasks/:id/recover. The success state means "Recovery
|
|
19
|
-
* Accepted" — the task is pending again; execution is asynchronous.
|
|
20
|
-
*
|
|
21
|
-
* PRI-747 F22 / PRI-749: each row can be expanded into its detail — the full
|
|
22
|
-
* task record plus run history — fetched lazily from the pre-existing
|
|
23
|
-
* GET /api/v1/failed-tasks/:id endpoint (server route +
|
|
24
|
-
* SqliteTaskStore.getFailedTaskDetail already existed; the UI simply never
|
|
25
|
-
* consumed them).
|
|
26
|
-
*
|
|
27
|
-
* Runtime Contract (rc-*):
|
|
28
|
-
* - rc-1: API response is treated as `unknown` and validated before use
|
|
29
|
-
* - rc-2: no `as` type assertions — runtime type guards only
|
|
30
|
-
* - rc-5: `Object.hasOwn()` for untrusted object keys
|
|
31
|
-
* - rc-9: every error/disabled path surfaces a reason + next action
|
|
32
|
-
*/
|
|
33
|
-
import { useState, useEffect, useCallback, useRef, Fragment } from "react";
|
|
34
|
-
import { useNavigate, useSearchParams } from "react-router-dom";
|
|
35
|
-
import { useTranslation } from "react-i18next";
|
|
36
|
-
import { toast } from "sonner";
|
|
37
|
-
import { PageShell } from "../../components/layout/page-shell.js";
|
|
38
|
-
import { PageLoading } from "../../components/layout/page-loading.js";
|
|
39
|
-
import { Badge } from "../../components/ui/badge.js";
|
|
40
|
-
import { Card, CardContent, CardHeader, CardTitle } from "../../components/ui/card.js";
|
|
41
|
-
import { Button } from "../../components/ui/button.js";
|
|
42
|
-
import { AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogFooter, AlertDialogTitle, AlertDialogDescription, AlertDialogAction, AlertDialogCancel, } from "../../components/ui/alert-dialog.js";
|
|
43
|
-
import { ShinyText } from "../../components/ui/shiny-text.js";
|
|
44
|
-
import { formatDate } from "../../utils/format-date.js";
|
|
45
|
-
import i18n from "../../i18n/index.js";
|
|
46
|
-
import { recoverFailedTask, fetchConfigSummary, fetchFailedTaskDetail, request } from "../../api.js";
|
|
47
|
-
// Same component set the former utils/format.js rendered, now served by the
|
|
48
|
-
// defensive single-owner formatter: an invalid date string returns the raw
|
|
49
|
-
// input instead of Intl throwing RangeError mid-render (review round of
|
|
50
|
-
// PRI-747 F22 — utils/format.js was retired with this migration).
|
|
51
|
-
const DATETIME_FORMAT_OPTS = {
|
|
52
|
-
year: "numeric",
|
|
53
|
-
month: "2-digit",
|
|
54
|
-
day: "2-digit",
|
|
55
|
-
hour: "2-digit",
|
|
56
|
-
minute: "2-digit",
|
|
57
|
-
};
|
|
58
|
-
function formatDateTime(iso) {
|
|
59
|
-
return formatDate(iso, i18n.language, DATETIME_FORMAT_OPTS);
|
|
60
|
-
}
|
|
61
|
-
// ── Runtime validators (rc-1, rc-2, rc-5) ───────────────────────────────────
|
|
62
|
-
//
|
|
63
|
-
// The API response is `unknown` until these guards run. No `as` casts.
|
|
64
|
-
function isObject(v) {
|
|
65
|
-
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
66
|
-
}
|
|
67
|
-
function isString(v) {
|
|
68
|
-
return typeof v === "string";
|
|
69
|
-
}
|
|
70
|
-
function isNumber(v) {
|
|
71
|
-
return typeof v === "number" && !Number.isNaN(v);
|
|
72
|
-
}
|
|
73
|
-
function readString(v, key) {
|
|
74
|
-
if (!Object.hasOwn(v, key))
|
|
75
|
-
return null;
|
|
76
|
-
return isString(v[key]) ? v[key] : null;
|
|
77
|
-
}
|
|
78
|
-
function readNullableString(v, key) {
|
|
79
|
-
if (!Object.hasOwn(v, key))
|
|
80
|
-
return null;
|
|
81
|
-
const val = v[key];
|
|
82
|
-
if (val === null)
|
|
83
|
-
return null;
|
|
84
|
-
return isString(val) ? val : null;
|
|
85
|
-
}
|
|
86
|
-
function readNumber(v, key) {
|
|
87
|
-
if (!Object.hasOwn(v, key))
|
|
88
|
-
return null;
|
|
89
|
-
return isNumber(v[key]) ? v[key] : null;
|
|
90
|
-
}
|
|
91
|
-
function validateTask(v) {
|
|
92
|
-
if (!isObject(v))
|
|
93
|
-
return null;
|
|
94
|
-
const taskId = readString(v, "taskId");
|
|
95
|
-
const taskKind = readString(v, "taskKind");
|
|
96
|
-
const status = readString(v, "status");
|
|
97
|
-
const attemptCount = readNumber(v, "attemptCount");
|
|
98
|
-
const maxAttempts = readNumber(v, "maxAttempts");
|
|
99
|
-
const createdAt = readString(v, "createdAt");
|
|
100
|
-
// Required fields — fail loud when missing or wrong type (rc-3)
|
|
101
|
-
if (taskId === null ||
|
|
102
|
-
taskKind === null ||
|
|
103
|
-
status === null ||
|
|
104
|
-
attemptCount === null ||
|
|
105
|
-
maxAttempts === null ||
|
|
106
|
-
createdAt === null) {
|
|
107
|
-
return null;
|
|
108
|
-
}
|
|
109
|
-
const painId = readNullableString(v, "painId");
|
|
110
|
-
const lastError = readNullableString(v, "lastError");
|
|
111
|
-
const lastAttemptAt = readNullableString(v, "lastAttemptAt");
|
|
112
|
-
const ownerDecisionRequired = Object.hasOwn(v, "ownerDecisionRequired")
|
|
113
|
-
? v.ownerDecisionRequired === true
|
|
114
|
-
: undefined;
|
|
115
|
-
return {
|
|
116
|
-
taskId,
|
|
117
|
-
taskKind,
|
|
118
|
-
painId,
|
|
119
|
-
status,
|
|
120
|
-
lastError,
|
|
121
|
-
attemptCount,
|
|
122
|
-
maxAttempts,
|
|
123
|
-
createdAt,
|
|
124
|
-
lastAttemptAt,
|
|
125
|
-
...(ownerDecisionRequired !== undefined ? { ownerDecisionRequired } : {}),
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
function validateFailedTasksData(v) {
|
|
129
|
-
if (!isObject(v))
|
|
130
|
-
return null;
|
|
131
|
-
if (!Object.hasOwn(v, "tasks"))
|
|
132
|
-
return null;
|
|
133
|
-
const tasksRaw = v.tasks;
|
|
134
|
-
if (!Array.isArray(tasksRaw))
|
|
135
|
-
return null;
|
|
136
|
-
const tasks = [];
|
|
137
|
-
for (const item of tasksRaw) {
|
|
138
|
-
const task = validateTask(item);
|
|
139
|
-
if (task === null)
|
|
140
|
-
return null;
|
|
141
|
-
tasks.push(task);
|
|
142
|
-
}
|
|
143
|
-
const total = readNumber(v, "total");
|
|
144
|
-
if (total === null)
|
|
145
|
-
return null;
|
|
146
|
-
const nextAction = readNullableString(v, "nextAction");
|
|
147
|
-
// PRI-955 (F11): optional pipeline health block — an invalid block is
|
|
148
|
-
// omitted (never fails the whole payload), the page then renders nothing.
|
|
149
|
-
let pipeline;
|
|
150
|
-
if (Object.hasOwn(v, "pipeline") && isObject(v.pipeline)) {
|
|
151
|
-
const p = v.pipeline;
|
|
152
|
-
const status = readString(p, "status");
|
|
153
|
-
const note = readString(p, "note");
|
|
154
|
-
if (status === "healthy" || status === "backlogged" || status === "stalled" || status === "disabled" || status === "unknown") {
|
|
155
|
-
if (note !== null) {
|
|
156
|
-
pipeline = {
|
|
157
|
-
status,
|
|
158
|
-
pendingCount: readNumber(p, "pendingCount"),
|
|
159
|
-
oldestPendingTaskKind: readNullableString(p, "oldestPendingTaskKind"),
|
|
160
|
-
oldestPendingAgeMinutes: readNumber(p, "oldestPendingAgeMinutes"),
|
|
161
|
-
lastProgressAt: readNullableString(p, "lastProgressAt"),
|
|
162
|
-
note,
|
|
163
|
-
nextAction: readNullableString(p, "nextAction") ?? undefined,
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
return { tasks, total, nextAction: nextAction ?? undefined, ...(pipeline ? { pipeline } : {}) };
|
|
169
|
-
}
|
|
170
|
-
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
171
|
-
function truncateId(id) {
|
|
172
|
-
return id.length > 12 ? id.slice(0, 12) + "…" : id;
|
|
173
|
-
}
|
|
174
|
-
function statusToVariant(status) {
|
|
175
|
-
if (status === "failed")
|
|
176
|
-
return "destructive";
|
|
177
|
-
if (status === "needs_human_review")
|
|
178
|
-
return "amber";
|
|
179
|
-
return "default";
|
|
180
|
-
}
|
|
181
|
-
/** A failed task whose retry budget is spent — only a force recovery can requeue it. */
|
|
182
|
-
function isAttemptBudgetExhausted(task) {
|
|
183
|
-
return task.status === "failed" && task.attemptCount >= task.maxAttempts;
|
|
184
|
-
}
|
|
185
|
-
function groupTasksByKind(tasks) {
|
|
186
|
-
const grouped = new Map();
|
|
187
|
-
for (const task of tasks) {
|
|
188
|
-
const existing = grouped.get(task.taskKind) ?? [];
|
|
189
|
-
existing.push(task);
|
|
190
|
-
grouped.set(task.taskKind, existing);
|
|
191
|
-
}
|
|
192
|
-
return grouped;
|
|
193
|
-
}
|
|
194
|
-
// ── Component ────────────────────────────────────────────────────────────────
|
|
195
|
-
export function FailedTasksPage() {
|
|
196
|
-
const { t } = useTranslation();
|
|
197
|
-
const navigate = useNavigate();
|
|
198
|
-
const [searchParams] = useSearchParams();
|
|
199
|
-
// ?taskId= deep link (治理焦点「查看恢复详情」直达): initial value only —
|
|
200
|
-
// the row auto-expands once; the Owner can still collapse/switch freely.
|
|
201
|
-
const deepLinkTaskId = searchParams.get("taskId") ?? undefined;
|
|
202
|
-
const [state, setState] = useState({ status: "loading" });
|
|
203
|
-
// Governance Recovery Actions v1 — flag-gated recovery UI (fail-closed:
|
|
204
|
-
// absent/false → no recovery entry, Console stays read-only).
|
|
205
|
-
const [recoveryEnabled, setRecoveryEnabled] = useState(false);
|
|
206
|
-
const [recoverTarget, setRecoverTarget] = useState(null);
|
|
207
|
-
const [recoverLoading, setRecoverLoading] = useState(false);
|
|
208
|
-
// Exhausted targets recover with force: the dialog warns first and the
|
|
209
|
-
// action button becomes 强制恢复 (server refuses force=false for these).
|
|
210
|
-
const recoverExhausted = recoverTarget !== null && isAttemptBudgetExhausted(recoverTarget);
|
|
211
|
-
let recoverConfirmLabel = t("pages.failedTasks.recoverConfirmButton");
|
|
212
|
-
if (recoverLoading) {
|
|
213
|
-
recoverConfirmLabel = t("common.loading");
|
|
214
|
-
}
|
|
215
|
-
else if (recoverExhausted) {
|
|
216
|
-
recoverConfirmLabel = t("pages.failedTasks.recoverConfirmForceButton");
|
|
217
|
-
}
|
|
218
|
-
useEffect(() => {
|
|
219
|
-
let cancelled = false;
|
|
220
|
-
fetchConfigSummary()
|
|
221
|
-
.then((result) => {
|
|
222
|
-
if (cancelled)
|
|
223
|
-
return;
|
|
224
|
-
// Fail-closed: only an explicitly enabled flag shows the action
|
|
225
|
-
if (result.success && result.data) {
|
|
226
|
-
const flag = result.data.features.find((f) => f.id === "failed_task_recovery_console");
|
|
227
|
-
setRecoveryEnabled(flag?.enabled === true);
|
|
228
|
-
}
|
|
229
|
-
})
|
|
230
|
-
.catch(() => {
|
|
231
|
-
// rc-9: flag probe failure keeps recovery hidden (fail-closed)
|
|
232
|
-
});
|
|
233
|
-
return () => {
|
|
234
|
-
cancelled = true;
|
|
235
|
-
};
|
|
236
|
-
}, []);
|
|
237
|
-
const loadData = useCallback(async () => {
|
|
238
|
-
setState({ status: "loading" });
|
|
239
|
-
// Shared request(): same auth header and global 401 session-expiry
|
|
240
|
-
// handling as every other page (PRI-643 — a manual fetch here bypassed
|
|
241
|
-
// session-expiry routing entirely).
|
|
242
|
-
const result = await request("/api/v1/failed-tasks", undefined, validateFailedTasksData);
|
|
243
|
-
if (result.success) {
|
|
244
|
-
setState({ status: "loaded", data: result.data });
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
// 403 = feature flag disabled — show disabled state (rc-9: surface reason)
|
|
248
|
-
if (result.status === 403) {
|
|
249
|
-
setState({ status: "disabled" });
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
setState({ status: "error", message: result.error, nextAction: result.nextAction });
|
|
253
|
-
}, []);
|
|
254
|
-
useEffect(() => {
|
|
255
|
-
loadData();
|
|
256
|
-
}, [loadData]);
|
|
257
|
-
const handleCreateDraft = (task) => {
|
|
258
|
-
const params = new URLSearchParams({
|
|
259
|
-
source: "failed_tasks_page",
|
|
260
|
-
taskId: task.taskId,
|
|
261
|
-
});
|
|
262
|
-
if (task.painId) {
|
|
263
|
-
params.set("painId", task.painId);
|
|
264
|
-
}
|
|
265
|
-
navigate(`/report-problem?${params.toString()}`);
|
|
266
|
-
};
|
|
267
|
-
// Owner-confirmed recovery (SPEC §6.3: explicit confirmation only — this
|
|
268
|
-
// handler runs exclusively from the dialog's action button, never on load).
|
|
269
|
-
const handleRecoverConfirmed = async () => {
|
|
270
|
-
if (!recoverTarget || recoverLoading)
|
|
271
|
-
return;
|
|
272
|
-
setRecoverLoading(true);
|
|
273
|
-
try {
|
|
274
|
-
const result = await recoverFailedTask(recoverTarget.taskId, undefined, recoverExhausted);
|
|
275
|
-
if (result.success) {
|
|
276
|
-
toast.success(t("pages.failedTasks.recoverSuccess"));
|
|
277
|
-
setRecoverTarget(null);
|
|
278
|
-
await loadData();
|
|
279
|
-
}
|
|
280
|
-
else {
|
|
281
|
-
// rc-9: server-provided reason + next action accompany the failure
|
|
282
|
-
const detail = result.nextAction
|
|
283
|
-
? `${result.error} — ${result.nextAction}`
|
|
284
|
-
: result.error;
|
|
285
|
-
toast.error(`${t("pages.failedTasks.recoverFailed")}: ${detail}`);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
catch (err) {
|
|
289
|
-
toast.error(`${t("pages.failedTasks.recoverFailed")}: ${err instanceof Error ? err.message : "Network error"}`);
|
|
290
|
-
}
|
|
291
|
-
finally {
|
|
292
|
-
setRecoverLoading(false);
|
|
293
|
-
}
|
|
294
|
-
};
|
|
295
|
-
return (_jsxs(PageShell, { children: [_jsxs("div", { className: "mb-8", children: [_jsx("div", { className: "font-mono text-[11px] uppercase tracking-[0.14em] text-ink-3", children: t("pages.failedTasks.title") }), _jsx(ShinyText, { as: "h1", className: "text-[29px] font-semibold tracking-tight text-ink mt-3 mb-2", duration: 4.5, brightness: 0.5, disabled: state.status !== "loaded" || state.data.tasks.length === 0, children: t("pages.failedTasks.title") }), _jsx("p", { className: "text-ink-3 text-sm leading-relaxed max-w-[712px]", children: t("pages.failedTasks.subtitle") })] }), state.status === "loading" && (_jsx(PageLoading, { cardCount: 3, label: t("common.loading") })), state.status === "disabled" && (_jsxs("div", { className: "py-8", children: [_jsx("div", { className: "p-5 bg-panel border border-amber/20 rounded-[var(--radius-md)] text-amber text-[13px] leading-relaxed", children: t("pages.failedTasks.disabled") }), _jsx("div", { className: "mt-4", children: _jsx(Button, { variant: "outline", size: "sm", onClick: loadData, children: t("common.refresh") }) })] })), state.status === "error" && (_jsxs("div", { className: "py-8", children: [_jsxs("div", { className: "text-danger text-sm mb-2", children: [t("pages.failedTasks.error"), ": ", state.message] }), state.nextAction && (_jsx("div", { className: "text-ink-3 text-[13px] mb-3", children: state.nextAction })), _jsx(Button, { variant: "outline", size: "sm", onClick: loadData, children: t("common.refresh") })] })), state.status === "loaded" && (_jsx("div", { className: "animate-[pdFadeIn_400ms_ease-out]", children: _jsx(LoadedContent, { data: state.data, onCreateDraft: handleCreateDraft, onRecover: recoveryEnabled ? (task) => setRecoverTarget(task) : undefined, initialExpandedTaskId: deepLinkTaskId, t: t }) })), _jsx(AlertDialog, { open: recoverTarget !== null, onOpenChange: (open) => {
|
|
296
|
-
if (!open && !recoverLoading) {
|
|
297
|
-
setRecoverTarget(null);
|
|
298
|
-
}
|
|
299
|
-
}, children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: t("pages.failedTasks.recoverConfirmTitle") }), _jsx(AlertDialogDescription, { asChild: true, children: _jsxs("div", { className: "space-y-2 text-[13px] leading-relaxed", children: [_jsxs("div", { children: [_jsxs("span", { className: "font-medium", children: [t("pages.failedTasks.recoverConfirmStatus"), ":"] }), " ", _jsx("span", { className: "font-mono", children: recoverTarget?.status ?? "—" })] }), recoverExhausted && (_jsxs(_Fragment, { children: [_jsxs("div", { children: [_jsxs("span", { className: "font-medium", children: [t("pages.failedTasks.recoverConfirmAttempts"), ":"] }), " ", _jsxs("span", { className: "font-mono", children: [recoverTarget?.attemptCount, " / ", recoverTarget?.maxAttempts] })] }), _jsx("div", { className: "text-danger", children: t("pages.failedTasks.recoverExhaustedWarning", {
|
|
300
|
-
attempt: recoverTarget?.attemptCount ?? 0,
|
|
301
|
-
max: recoverTarget?.maxAttempts ?? 0,
|
|
302
|
-
}) })] })), _jsxs("div", { children: [_jsxs("span", { className: "font-medium", children: [t("pages.failedTasks.recoverConfirmActionLabel"), ":"] }), " ", t("pages.failedTasks.recoverConfirmActionDesc")] }), _jsxs("div", { children: [_jsxs("span", { className: "font-medium", children: [t("pages.failedTasks.recoverConfirmImpactLabel"), ":"] }), " ", recoverExhausted
|
|
303
|
-
? t("pages.failedTasks.recoverConfirmForceImpactDesc")
|
|
304
|
-
: t("pages.failedTasks.recoverConfirmImpactDesc")] })] }) })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { disabled: recoverLoading, children: t("common.cancel") }), _jsx(AlertDialogAction, { disabled: recoverLoading, onClick: (event) => {
|
|
305
|
-
// Prevent Radix from auto-closing before the async POST
|
|
306
|
-
// resolves; handleRecoverConfirmed closes on success.
|
|
307
|
-
event.preventDefault();
|
|
308
|
-
void handleRecoverConfirmed();
|
|
309
|
-
}, children: recoverConfirmLabel })] })] }) })] }));
|
|
310
|
-
}
|
|
311
|
-
/** PRI-955 (F11): derived learning-queue health block — never claims health
|
|
312
|
-
* from the failed-task count alone. Facts come from the server projection. */
|
|
313
|
-
function PipelineHealthCard({ pipeline, t }) {
|
|
314
|
-
const statusLabel = pipeline.status === "healthy" ? t("pages.failedTasks.pipeline.statusHealthy")
|
|
315
|
-
: pipeline.status === "backlogged" ? t("pages.failedTasks.pipeline.statusBacklogged")
|
|
316
|
-
: pipeline.status === "stalled" ? t("pages.failedTasks.pipeline.statusStalled")
|
|
317
|
-
: t("pages.failedTasks.pipeline.statusUnknown");
|
|
318
|
-
const variant = pipeline.status === "healthy" ? "default"
|
|
319
|
-
: pipeline.status === "stalled" ? "destructive"
|
|
320
|
-
: pipeline.status === "backlogged" ? "amber"
|
|
321
|
-
: "default";
|
|
322
|
-
return (_jsxs("div", { className: "p-5 bg-panel border border-line rounded-[var(--radius-md)] mb-4", children: [_jsxs("div", { className: "flex items-center gap-3 mb-2", children: [_jsx("span", { className: "text-[15px] font-semibold text-ink", children: t("pages.failedTasks.pipeline.title") }), _jsx("span", { className: `text-xs px-2 py-0.5 rounded-full border ${variant === "destructive" ? "border-red-300 text-red-700 bg-red-50"
|
|
323
|
-
: variant === "amber" ? "border-amber-300 text-amber-700 bg-amber-50"
|
|
324
|
-
: "border-line text-ink-3 bg-canvas"}`, children: statusLabel })] }), _jsx("p", { className: "text-ink-3 text-sm leading-relaxed", children: pipeline.note }), pipeline.nextAction && (_jsxs("p", { className: "text-ink-3 text-sm leading-relaxed mt-1", children: [t("pages.failedTasks.pipeline.nextActionPrefix"), pipeline.nextAction] }))] }));
|
|
325
|
-
}
|
|
326
|
-
function LoadedContent({ data, onCreateDraft, onRecover, initialExpandedTaskId, t }) {
|
|
327
|
-
const hasTasks = data.tasks.length > 0;
|
|
328
|
-
// Empty state — surface nextAction from the API (rc-9: no silent fallback).
|
|
329
|
-
// PRI-955: the empty copy no longer claims the pipeline is healthy; the
|
|
330
|
-
// derived queue-health block carries that judgment with facts.
|
|
331
|
-
if (!hasTasks) {
|
|
332
|
-
return (_jsxs("div", { className: "py-8", children: [_jsxs("div", { className: "p-5 bg-panel border border-line rounded-[var(--radius-md)]", children: [_jsx("h3", { className: "text-[17px] font-semibold text-ink mb-2", children: t("pages.failedTasks.empty") }), data.nextAction && (_jsx("p", { className: "text-ink-3 text-sm leading-relaxed", children: data.nextAction }))] }), data.pipeline && _jsx(PipelineHealthCard, { pipeline: data.pipeline, t: t })] }));
|
|
333
|
-
}
|
|
334
|
-
const grouped = groupTasksByKind(data.tasks);
|
|
335
|
-
const sortedKinds = Array.from(grouped.keys()).sort();
|
|
336
|
-
return (_jsxs(_Fragment, { children: [data.pipeline && _jsx(PipelineHealthCard, { pipeline: data.pipeline, t: t }), _jsx("div", { className: "space-y-6", children: sortedKinds.map((kind) => {
|
|
337
|
-
const tasks = grouped.get(kind);
|
|
338
|
-
if (!tasks || tasks.length === 0)
|
|
339
|
-
return null;
|
|
340
|
-
return (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Badge, { variant: "outline", children: t("pages.failedTasks.kind") }), _jsxs(CardTitle, { className: "text-[15px] font-mono", children: [kind, " (", tasks.length, ")"] })] }) }), _jsx(CardContent, { children: _jsx(TaskTable, { tasks: tasks, onCreateDraft: onCreateDraft, onRecover: onRecover, initialExpandedTaskId: initialExpandedTaskId, t: t }) })] }, kind));
|
|
341
|
-
}) }), _jsxs("div", { className: "mt-8 font-mono text-[11px] text-ink-4 space-y-1", children: [_jsxs("div", { children: [t("common.total"), ": ", data.total] }), data.total > data.tasks.length && (_jsx("div", { className: "text-amber/80 leading-relaxed", children: t("pages.failedTasks.partialResults")
|
|
342
|
-
.replace("{shown}", String(data.tasks.length))
|
|
343
|
-
.replace("{total}", String(data.total)) }))] })] }));
|
|
344
|
-
}
|
|
345
|
-
function TaskTable({ tasks, onCreateDraft, onRecover, initialExpandedTaskId, t }) {
|
|
346
|
-
// PRI-747 F22: one row at a time is expanded into its detail panel.
|
|
347
|
-
// ?taskId= deep link seeds the initial expansion (only when this table
|
|
348
|
-
// actually contains the task); after mount it behaves like a normal toggle.
|
|
349
|
-
const deepLinkTask = initialExpandedTaskId !== undefined
|
|
350
|
-
? tasks.find((task) => task.taskId === initialExpandedTaskId)
|
|
351
|
-
: undefined;
|
|
352
|
-
const [expandedId, setExpandedId] = useState(deepLinkTask?.taskId ?? null);
|
|
353
|
-
const [detail, setDetail] = useState({ status: "idle" });
|
|
354
|
-
// Drop responses from a superseded expand/collapse (rc-7: only the newest
|
|
355
|
-
// request's result may become current state).
|
|
356
|
-
const detailRequestIdRef = useRef(0);
|
|
357
|
-
const deepLinkFetchedRef = useRef(false);
|
|
358
|
-
// Fetch (or refetch) one task's detail; a stale response from a superseded
|
|
359
|
-
// request is dropped (rc-7: only the newest request's result may land).
|
|
360
|
-
const requestDetail = useCallback(async (taskId) => {
|
|
361
|
-
const requestId = ++detailRequestIdRef.current;
|
|
362
|
-
setDetail({ status: "loading", taskId });
|
|
363
|
-
// Lazy fetch on expand: the list view stays cheap; the run history is
|
|
364
|
-
// only loaded when the Owner actually asks for this task's detail.
|
|
365
|
-
const result = await fetchFailedTaskDetail(taskId);
|
|
366
|
-
if (requestId !== detailRequestIdRef.current)
|
|
367
|
-
return;
|
|
368
|
-
if (result.success) {
|
|
369
|
-
setDetail({ status: "loaded", taskId, data: result.data });
|
|
370
|
-
}
|
|
371
|
-
else {
|
|
372
|
-
// rc-9: surface the server-provided reason + next action (404 = the
|
|
373
|
-
// task recovered/changed state after the list was loaded).
|
|
374
|
-
setDetail({ status: "error", taskId, message: result.error, nextAction: result.nextAction });
|
|
375
|
-
}
|
|
376
|
-
}, []);
|
|
377
|
-
// Deep link (?taskId=): fetch the linked task's detail and scroll its row
|
|
378
|
-
// into view once. The table mounts only after the task list has loaded, so
|
|
379
|
-
// the row is present at first effect run. The URL param is untrusted
|
|
380
|
-
// (rc-1): CSS.escape (unquoted attribute selector) keeps a crafted id from
|
|
381
|
-
// breaking the selector.
|
|
382
|
-
useEffect(() => {
|
|
383
|
-
if (deepLinkTask === undefined || deepLinkFetchedRef.current)
|
|
384
|
-
return;
|
|
385
|
-
deepLinkFetchedRef.current = true;
|
|
386
|
-
void requestDetail(deepLinkTask.taskId);
|
|
387
|
-
const row = document.querySelector(`[data-testid=${CSS.escape(`detail-toggle-${deepLinkTask.taskId}`)}]`);
|
|
388
|
-
row?.scrollIntoView({ block: "center" });
|
|
389
|
-
}, [deepLinkTask, requestDetail]);
|
|
390
|
-
const toggleDetail = useCallback((taskId) => {
|
|
391
|
-
if (expandedId === taskId) {
|
|
392
|
-
// Invalidate any in-flight response, then collapse.
|
|
393
|
-
detailRequestIdRef.current += 1;
|
|
394
|
-
setExpandedId(null);
|
|
395
|
-
setDetail({ status: "idle" });
|
|
396
|
-
return;
|
|
397
|
-
}
|
|
398
|
-
setExpandedId(taskId);
|
|
399
|
-
void requestDetail(taskId);
|
|
400
|
-
}, [expandedId, requestDetail]);
|
|
401
|
-
return (_jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "grid grid-cols-[auto_minmax(0,1fr)_minmax(0,1fr)_auto_minmax(0,1fr)_auto_minmax(0,1fr)_auto_auto] gap-3 pb-2 border-b border-line font-mono text-[10px] uppercase tracking-[0.08em] text-ink-4", children: [_jsx("span", { "aria-hidden": "true" }), _jsx("span", { children: t("pages.failedTasks.taskId") }), _jsx("span", { children: t("pages.failedTasks.painId") }), _jsx("span", { children: t("pages.failedTasks.status") }), _jsx("span", { children: t("pages.failedTasks.lastError") }), _jsx("span", { children: t("pages.failedTasks.attempts") }), _jsx("span", { children: t("pages.failedTasks.lastAttempt") }), _jsx("span", { "aria-hidden": "true" }), _jsx("span", { "aria-hidden": "true" })] }), tasks.map((task) => {
|
|
402
|
-
const isExpanded = expandedId === task.taskId;
|
|
403
|
-
return (_jsxs(Fragment, { children: [_jsxs("div", { className: "grid grid-cols-[auto_minmax(0,1fr)_minmax(0,1fr)_auto_minmax(0,1fr)_auto_minmax(0,1fr)_auto_auto] gap-3 py-2 items-center text-[13px] border-b border-line/50 last:border-b-0", children: [_jsx(Button, { variant: "quiet", size: "icon", className: "h-6 w-6 shrink-0 text-[12px]", onClick: () => toggleDetail(task.taskId), "aria-label": t("pages.failedTasks.detailToggle"), "aria-expanded": isExpanded, "data-testid": `detail-toggle-${task.taskId}`, children: isExpanded ? "▾" : "▸" }), _jsx("span", { className: "font-mono text-ink-2 truncate", title: task.taskId, children: truncateId(task.taskId) }), _jsx("span", { className: "font-mono text-ink-3 truncate", title: task.painId ?? "", children: task.painId ? truncateId(task.painId) : t("pages.failedTasks.noPainId") }), _jsx(Badge, { variant: statusToVariant(task.status), children: task.status }), _jsx("span", { className: "text-ink-3 truncate", title: task.lastError ?? "", children: task.lastError ?? "—" }), _jsx("span", { className: "font-mono text-ink-2 text-center", children: task.attemptCount }), _jsx("span", { className: "font-mono text-ink-3 text-[12px]", children: task.lastAttemptAt ? formatDateTime(task.lastAttemptAt) : "—" }), task.ownerDecisionRequired === true ? (_jsxs("span", { className: "whitespace-nowrap text-[12px]", children: [_jsx("span", { className: "text-amber", children: t("pages.failedTasks.awaitingOwnerDecision") }), " ", _jsx("a", { href: "#/focus", className: "text-gov underline underline-offset-2 hover:text-gov/80", "data-testid": `go-focus-${task.taskId}`, children: t("pages.failedTasks.goGovernanceFocus") })] })) : onRecover ? (_jsx(Button, { variant: "default", size: "sm", onClick: () => onRecover(task), className: "whitespace-nowrap", children: t("pages.failedTasks.recover") })) : null, _jsx(Button, { variant: "outline", size: "sm", onClick: () => onCreateDraft(task), className: "whitespace-nowrap", children: t("pages.failedTasks.createDraft") })] }), isExpanded && (_jsx("div", { className: "border border-line/60 rounded-[var(--radius-md)] bg-panel/60 p-4 mb-2", "data-testid": `detail-panel-${task.taskId}`, children: _jsx(TaskDetailPanel, { state: detail.status === "idle" ? { status: "loading", taskId: task.taskId } : detail, taskId: task.taskId, onRetry: requestDetail, t: t }) }))] }, task.taskId));
|
|
404
|
-
})] }));
|
|
405
|
-
}
|
|
406
|
-
function TaskDetailPanel({ state, taskId, onRetry, t }) {
|
|
407
|
-
if (state.status === "loading") {
|
|
408
|
-
return (_jsx("div", { className: "text-ink-3 text-[13px]", "data-testid": "detail-loading", children: t("common.loading") }));
|
|
409
|
-
}
|
|
410
|
-
if (state.status === "error") {
|
|
411
|
-
// rc-9: reason + next action + an explicit retry affordance
|
|
412
|
-
return (_jsxs("div", { className: "text-[13px] space-y-2", "data-testid": "detail-error", children: [_jsxs("div", { className: "text-danger", children: [t("pages.failedTasks.detailError"), ": ", state.message] }), state.nextAction && _jsx("div", { className: "text-ink-3", children: state.nextAction }), _jsx("div", { children: _jsx(Button, { variant: "outline", size: "sm", onClick: () => onRetry(taskId), children: t("common.refresh") }) })] }));
|
|
413
|
-
}
|
|
414
|
-
// The idle state only occurs if the panel renders for a task whose fetch was
|
|
415
|
-
// superseded mid-flight; treat it as loading for the row just expanded.
|
|
416
|
-
if (state.status === "idle" || state.taskId !== taskId) {
|
|
417
|
-
return (_jsx("div", { className: "text-ink-3 text-[13px]", "data-testid": "detail-loading", children: t("common.loading") }));
|
|
418
|
-
}
|
|
419
|
-
const { data } = state;
|
|
420
|
-
return (_jsxs("div", { className: "space-y-4 text-[13px]", children: [_jsxs("div", { className: "grid grid-cols-[auto_minmax(0,1fr)] gap-x-4 gap-y-1 font-mono text-[12px]", children: [_jsx("span", { className: "text-ink-4 uppercase tracking-[0.08em] self-center", children: t("pages.failedTasks.taskId") }), _jsx("span", { className: "text-ink-2 break-all", children: data.taskId }), _jsx("span", { className: "text-ink-4 uppercase tracking-[0.08em] self-center", children: t("pages.failedTasks.kind") }), _jsx("span", { className: "text-ink-2", children: data.taskKind }), _jsx("span", { className: "text-ink-4 uppercase tracking-[0.08em] self-center", children: t("pages.failedTasks.status") }), _jsx("span", { className: "text-ink-2", children: data.status }), _jsx("span", { className: "text-ink-4 uppercase tracking-[0.08em] self-center", children: t("pages.failedTasks.createdAt") }), _jsx("span", { className: "text-ink-3", children: formatDateTime(data.createdAt) }), data.updatedAt && (_jsxs(_Fragment, { children: [_jsx("span", { className: "text-ink-4 uppercase tracking-[0.08em] self-center", children: t("pages.failedTasks.updatedAt") }), _jsx("span", { className: "text-ink-3", children: formatDateTime(data.updatedAt) })] })), _jsx("span", { className: "text-ink-4 uppercase tracking-[0.08em] self-center", children: t("pages.failedTasks.attempts") }), _jsxs("span", { className: "text-ink-2", children: [data.attemptCount, " / ", data.maxAttempts] }), _jsx("span", { className: "text-ink-4 uppercase tracking-[0.08em] self-center", children: t("pages.failedTasks.lastError") }), _jsx("span", { className: data.lastError ? "text-danger" : "text-ink-3", children: data.lastError ?? "—" })] }), _jsxs("div", { children: [_jsxs("div", { className: "font-mono text-[10px] uppercase tracking-[0.08em] text-ink-4 mb-2", children: [t("pages.failedTasks.runsTitle"), " (", data.runs.length, ")"] }), data.runs.length === 0 ? (_jsx("div", { className: "text-ink-3", children: t("pages.failedTasks.noRuns") })) : (_jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "grid grid-cols-[auto_auto_minmax(0,1fr)_minmax(0,1fr)] gap-3 pb-1 border-b border-line/50 font-mono text-[10px] uppercase tracking-[0.08em] text-ink-4", children: [_jsx("span", { children: t("pages.failedTasks.runAttempt") }), _jsx("span", { children: t("pages.failedTasks.status") }), _jsx("span", { children: t("pages.failedTasks.runStarted") }), _jsx("span", { children: t("pages.failedTasks.runReason") })] }), data.runs.map((run) => (_jsxs("div", { className: "grid grid-cols-[auto_auto_minmax(0,1fr)_minmax(0,1fr)] gap-3 py-1 text-[12px] border-b border-line/30 last:border-b-0", title: `${t("pages.failedTasks.runEnded")}: ${run.endedAt ? formatDateTime(run.endedAt) : "—"}${run.errorCategory ? ` · ${t("pages.failedTasks.runErrorCategory")}: ${run.errorCategory}` : ""}`, children: [_jsxs("span", { className: "font-mono text-ink-2", children: ["#", run.attemptNumber] }), _jsx("span", { className: "font-mono text-ink-3", children: run.executionStatus }), _jsx("span", { className: "font-mono text-ink-3", children: formatDateTime(run.startedAt) }), _jsx("span", { className: "text-ink-3 break-words", title: run.reason ?? undefined, children: run.reason ?? (run.errorCategory ?? "—") })] }, run.runId)))] }))] })] }));
|
|
421
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import type { ApprovalsGroupedData } from '../../api.js';
|
|
2
|
-
export declare function ApprovalSubjectReading({ principleId, approvalId, artifactId, channel, lockedReason, targetHost, onResolved, revisionReview, revisionReviewUnavailable, allowedActions, budgetNotice, promptInjection, sourceUnlinked, showPrincipleLink }: {
|
|
3
|
-
principleId: string;
|
|
4
|
-
approvalId: string;
|
|
5
|
-
artifactId: string;
|
|
6
|
-
channel: string;
|
|
7
|
-
lockedReason?: string;
|
|
8
|
-
targetHost?: 'openclaw' | 'codex' | 'workbuddy';
|
|
9
|
-
onResolved?: (message: string) => void;
|
|
10
|
-
revisionReview?: {
|
|
11
|
-
artifactId?: string;
|
|
12
|
-
artifactDigest?: string;
|
|
13
|
-
oldStatement: string;
|
|
14
|
-
newStatement: string;
|
|
15
|
-
intentFields: Record<string, string>;
|
|
16
|
-
};
|
|
17
|
-
revisionReviewUnavailable?: boolean;
|
|
18
|
-
/** Backend-assessed actions for THIS approval; absent means no per-action gate (the guarded submit still applies). */
|
|
19
|
-
allowedActions?: readonly ('approve' | 'reject' | 'edit_approval')[];
|
|
20
|
-
budgetNotice?: string;
|
|
21
|
-
promptInjection?: ApprovalsGroupedData['promptInjection'];
|
|
22
|
-
sourceUnlinked?: boolean;
|
|
23
|
-
showPrincipleLink?: boolean;
|
|
24
|
-
}): import("react").JSX.Element;
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
3
|
-
import { Link, useLocation } from 'react-router-dom';
|
|
4
|
-
import { useTranslation } from 'react-i18next';
|
|
5
|
-
import { fetchApprovalReview, fetchOwnerDecisionView, approveApproval, rejectApproval, editApproval, fetchAllActivations } from '../../api.js';
|
|
6
|
-
import { localizeApprovalWarning } from '../../utils/approval-warning-localization.js';
|
|
7
|
-
import { truncateByCodePoints } from '../../utils/reading-layout.js';
|
|
8
|
-
export function ApprovalSubjectReading({ principleId, approvalId, artifactId, channel, lockedReason, targetHost, onResolved, revisionReview, revisionReviewUnavailable, allowedActions, budgetNotice, promptInjection, sourceUnlinked, showPrincipleLink = true }) {
|
|
9
|
-
const { t } = useTranslation();
|
|
10
|
-
const location = useLocation();
|
|
11
|
-
const [review, setReview] = useState(null);
|
|
12
|
-
const [view, setView] = useState(null);
|
|
13
|
-
const [viewSettled, setViewSettled] = useState(false);
|
|
14
|
-
const [error, setError] = useState('');
|
|
15
|
-
const [resultText, setResultText] = useState('');
|
|
16
|
-
const [loading, setLoading] = useState(true);
|
|
17
|
-
const [busy, setBusy] = useState(false);
|
|
18
|
-
const [mode, setMode] = useState(null);
|
|
19
|
-
const [reason, setReason] = useState('');
|
|
20
|
-
const [newArtifact, setNewArtifact] = useState('');
|
|
21
|
-
const [intentReviewed, setIntentReviewed] = useState(false);
|
|
22
|
-
const [approveNote, setApproveNote] = useState('');
|
|
23
|
-
const epoch = useRef(0);
|
|
24
|
-
const read = useCallback(async () => {
|
|
25
|
-
const current = ++epoch.current;
|
|
26
|
-
setReview(null);
|
|
27
|
-
setLoading(true);
|
|
28
|
-
setIntentReviewed(false);
|
|
29
|
-
setError('');
|
|
30
|
-
const result = await fetchApprovalReview(approvalId);
|
|
31
|
-
if (current !== epoch.current)
|
|
32
|
-
return null;
|
|
33
|
-
setLoading(false);
|
|
34
|
-
if (result.success) {
|
|
35
|
-
setReview(result.data);
|
|
36
|
-
return result.data;
|
|
37
|
-
}
|
|
38
|
-
setError([result.error, result.nextAction].filter(Boolean).join(' '));
|
|
39
|
-
return null;
|
|
40
|
-
}, [approvalId]);
|
|
41
|
-
useEffect(() => {
|
|
42
|
-
setError('');
|
|
43
|
-
setResultText('');
|
|
44
|
-
setView(null);
|
|
45
|
-
setViewSettled(false);
|
|
46
|
-
setMode(null);
|
|
47
|
-
setReason('');
|
|
48
|
-
setNewArtifact('');
|
|
49
|
-
setApproveNote('');
|
|
50
|
-
let current = true;
|
|
51
|
-
void read();
|
|
52
|
-
void fetchOwnerDecisionView(principleId).then(result => { if (current) {
|
|
53
|
-
setView(result.success ? result.data : null);
|
|
54
|
-
setViewSettled(true);
|
|
55
|
-
} });
|
|
56
|
-
return () => { current = false; epoch.current++; };
|
|
57
|
-
}, [principleId, approvalId, artifactId, channel, read]);
|
|
58
|
-
const narrative = (field) => field.status === 'known' ? field.value.map(item => item.text).join('\n\n') : field.reason.ownerText;
|
|
59
|
-
const subject = view?.decisionSubjects.find(item => item.key === approvalId && item.channel === channel
|
|
60
|
-
&& item.revisionArtifactId.status === 'known' && item.revisionArtifactId.value === review?.expectedReview.artifactId);
|
|
61
|
-
const materialBlocks = subject?.materialGates.find(gate => gate.actionSemantic === 'approve')?.requiredItems.filter(item => item.status !== 'satisfied') ?? [];
|
|
62
|
-
const revisionMatches = !revisionReview || (review?.expectedReview.artifactState === 'present' && review.expectedReview.artifactId === artifactId
|
|
63
|
-
&& revisionReview.artifactId === review.expectedReview.artifactId && revisionReview.artifactDigest === review.expectedReview.artifactDigest);
|
|
64
|
-
const approveReady = review?.approval.status === 'pending' && subject !== undefined && review.expectedReview.artifactState === 'present' && review.material.exactBody !== null && review.material.availability === 'available' && revisionMatches && !revisionReviewUnavailable && materialBlocks.length === 0;
|
|
65
|
-
const may = (semantic) => allowedActions === undefined || allowedActions.includes(semantic);
|
|
66
|
-
async function submit(action) {
|
|
67
|
-
if (!review || busy || lockedReason || review.approval.status !== 'pending')
|
|
68
|
-
return;
|
|
69
|
-
if (!may(action === 'approve' ? 'approve' : action === 'reject' ? 'reject' : 'edit_approval'))
|
|
70
|
-
return;
|
|
71
|
-
if (action === 'approve' && (!approveReady || (revisionReview && !intentReviewed)))
|
|
72
|
-
return;
|
|
73
|
-
if (action !== 'approve' && !reason.trim())
|
|
74
|
-
return;
|
|
75
|
-
if (action === 'edit' && !newArtifact.trim())
|
|
76
|
-
return;
|
|
77
|
-
const snapshot = review;
|
|
78
|
-
const current = epoch.current;
|
|
79
|
-
setBusy(true);
|
|
80
|
-
setError('');
|
|
81
|
-
setResultText('');
|
|
82
|
-
const result = action === 'approve' ? await approveApproval(approvalId, approveNote.trim() || undefined, { expectedReview: snapshot.expectedReview, targetHost,
|
|
83
|
-
...(revisionReview ? { intentReviewed, reviewedArtifactId: snapshot.expectedReview.artifactId } : {}) })
|
|
84
|
-
: action === 'reject' ? await rejectApproval(approvalId, reason.trim(), snapshot.expectedReview)
|
|
85
|
-
: await editApproval(approvalId, newArtifact.trim(), { reason: reason.trim(), expectedReview: snapshot.expectedReview });
|
|
86
|
-
if (current !== epoch.current)
|
|
87
|
-
return;
|
|
88
|
-
const fresh = await read();
|
|
89
|
-
setBusy(false);
|
|
90
|
-
if (!fresh) {
|
|
91
|
-
setError(t('pages.focus.workbench.resultUnknown'));
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
if (fresh.approval.status === 'approved' || fresh.approval.status === 'rejected') {
|
|
95
|
-
const state = t(fresh.approval.status === 'approved' ? 'pages.focus.workbench.currentApproved' : 'pages.focus.workbench.currentRejected');
|
|
96
|
-
const ownOutcome = result.success && ((action === 'approve' && fresh.approval.status === 'approved') || (action === 'reject' && fresh.approval.status === 'rejected'));
|
|
97
|
-
const refused = !result.success && result.status !== undefined && result.status >= 400 && result.status < 500;
|
|
98
|
-
let message = ownOutcome ? t(fresh.approval.status === 'approved' ? 'pages.focus.workbench.approved' : 'pages.focus.workbench.rejected')
|
|
99
|
-
: refused ? t('pages.focus.workbench.refusedCurrentState', { state }) + ' ' + [result.error, result.nextAction].filter(Boolean).join(' ')
|
|
100
|
-
: t('pages.focus.workbench.currentStateConfirmed', { state });
|
|
101
|
-
if (fresh.approval.status === 'approved' && !result.success && !refused) {
|
|
102
|
-
const activations = await fetchAllActivations(targetHost);
|
|
103
|
-
if (epoch.current !== current + 1)
|
|
104
|
-
return;
|
|
105
|
-
const active = activations.success && activations.data.activations.some(item => item.artifactId === fresh.approval.artifactId && item.status === 'active');
|
|
106
|
-
message += ' ' + t(active ? 'pages.focus.workbench.runtimeCheckLink' : result.reason === 'activation_failed' ? 'pages.focus.workbench.activationFailed' : 'pages.focus.workbench.approvedRuntimeUnknown');
|
|
107
|
-
}
|
|
108
|
-
if (result.success && result.data.warning) {
|
|
109
|
-
const warning = localizeApprovalWarning(result.data.warning, t);
|
|
110
|
-
message += ` ${warning.title} ${warning.body ?? warning.detail}`;
|
|
111
|
-
}
|
|
112
|
-
setResultText(message);
|
|
113
|
-
onResolved?.(message);
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
if (!result.success)
|
|
117
|
-
setError(t(result.status === undefined ? 'pages.focus.workbench.resultUnknown' : 'pages.focus.workbench.notRecorded') + ' ' + [result.error, result.nextAction].filter(Boolean).join(' '));
|
|
118
|
-
else if (action === 'edit') {
|
|
119
|
-
setResultText(t('pages.focus.workbench.replaced'));
|
|
120
|
-
setMode(null);
|
|
121
|
-
setReason('');
|
|
122
|
-
setNewArtifact('');
|
|
123
|
-
setApproveNote('');
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
const body = review?.material.exactBody;
|
|
127
|
-
const bodyText = body?.text ?? review?.material.statement ?? '';
|
|
128
|
-
return _jsxs("div", { className: "space-y-7", children: [_jsxs("section", { children: [_jsx("h3", { className: "mb-3 text-[15px] font-medium text-ink", children: t('pages.focus.workbench.why') }), sourceUnlinked && _jsx("p", { role: "alert", className: "mb-3 text-sm leading-relaxed text-amber", children: t('pages.focus.workbench.sourceUnlinked') }), _jsx("p", { className: "whitespace-pre-wrap text-[14px] leading-relaxed text-ink-2", children: view ? narrative(view.incidentSummary) : t('pages.focus.workbench.materialUnavailable') })] }), _jsxs("section", { children: [_jsx("h3", { className: "mb-3 text-[15px] font-medium text-ink", children: t('pages.focus.workbench.proposal') }), loading ? _jsx("p", { role: "status", className: "text-sm text-ink-3", children: t('pages.focus.workbench.loading') }) : _jsxs(_Fragment, { children: [bodyText ? _jsxs(_Fragment, { children: [_jsx("p", { className: "mb-2 text-[12px] text-ink-4", children: t(body?.kind === 'rule_code' ? 'pages.focus.workbench.codePreview' : 'pages.focus.workbench.textPreview') }), _jsx("p", { className: "whitespace-pre-wrap break-words text-[15px] leading-[1.85] text-ink", children: truncateByCodePoints(bodyText, 240) }), _jsxs("details", { className: "mt-4 rounded-[4px] border border-line px-4 py-3 text-[13px]", children: [_jsx("summary", { className: "cursor-pointer text-gov", children: t(body?.kind === 'rule_code' ? 'pages.focus.workbench.fullCode' : 'pages.focus.workbench.fullText') }), _jsx("div", { className: "mt-4 whitespace-pre-wrap break-words text-[15px] leading-[1.85] text-ink-2", children: bodyText })] })] }) : _jsx("p", { className: "text-sm text-amber", children: t('pages.focus.workbench.exactMaterialMissing') }), review?.material.scope.length ? _jsxs("div", { className: "mt-4", children: [_jsx("h4", { className: "text-sm font-medium text-ink", children: t('pages.focus.workbench.scope') }), _jsx("ul", { className: "mt-2 list-disc space-y-1 pl-5 text-[13px] leading-relaxed text-ink-2", children: review.material.scope.map((scope, index) => _jsx("li", { children: scope }, index)) })] }) : null, review?.material.rationale && _jsxs("details", { className: "mt-4 text-[13px] text-ink-3", children: [_jsx("summary", { className: "cursor-pointer text-gov", children: t('pages.focus.workbench.reasonAndScope') }), _jsx("p", { className: "mt-3 whitespace-pre-wrap leading-relaxed", children: review.material.rationale })] }), revisionReview && _jsxs("details", { className: "mt-4 text-[13px] leading-relaxed text-ink-2", children: [_jsx("summary", { className: "cursor-pointer text-gov", children: t('pages.focus.workbench.compare') }), _jsx("p", { className: "mt-3 whitespace-pre-wrap", children: revisionReview.oldStatement }), _jsx("p", { className: "mt-3 whitespace-pre-wrap", children: revisionReview.newStatement }), _jsx("dl", { className: "mt-3", children: Object.entries(revisionReview.intentFields).map(([key, value]) => _jsxs("div", { children: [_jsx("dt", { className: "font-medium", children: t(`pages.focus.intentField.${key}`, { defaultValue: key }) }), _jsx("dd", { children: value })] }, key)) })] })] })] }), _jsxs("section", { className: "border-t border-line pt-5", "data-testid": "approval-review-limits", children: [_jsx("h3", { className: "mb-3 text-[15px] font-medium text-ink", children: t('pages.focus.workbench.beforeDecision') }), viewSettled && !subject && _jsx("p", { className: "text-sm leading-relaxed text-amber", children: t('pages.focus.workbench.subjectUnconfirmed') }), review && _jsx("div", { className: "mb-3 text-sm leading-relaxed text-ink-2", "data-testid": "approval-exact-risks", children: review.material.risks?.length ? _jsx("ul", { className: "list-disc space-y-1 pl-5", children: review.material.risks.map((risk, index) => _jsx("li", { children: risk }, index)) }) : _jsx("p", { children: t(review.material.risks === null ? 'pages.focus.workbench.risksUnknown' : 'pages.focus.workbench.risksEmpty') }) }), materialBlocks.map(item => _jsx("p", { className: "mb-2 text-sm leading-relaxed text-amber", children: item.ownerText }, item.key)), !revisionMatches && _jsx("p", { role: "alert", className: "mb-2 text-sm text-amber", children: t('pages.focus.workbench.revisionChanged') }), revisionReviewUnavailable && _jsx("p", { role: "alert", className: "mb-2 text-sm text-amber", children: t('pages.focus.revisionReviewUnavailable') }), budgetNotice && channel === 'prompt' && _jsx("p", { role: "alert", className: "mb-2 text-sm leading-relaxed text-amber", "data-testid": `approve-queue-badge-${principleId}`, children: budgetNotice }), promptInjection && channel === 'prompt' && _jsxs("details", { className: "mb-3 text-[12px] leading-relaxed text-ink-3", "data-testid": `approve-queue-badge-scope-${principleId}`, children: [_jsx("summary", { className: "cursor-pointer text-gov", children: t('pages.focus.workbench.budgetBasis') }), _jsxs("p", { className: "mt-2", children: [promptInjection.route === 'shared_render' ? t('pages.focus.promptScopeShared') : t('pages.focus.promptScopeList', { full: promptInjection.fullRenderChars !== undefined ? t('pages.focus.promptScopeFullRender', { count: promptInjection.fullRenderChars }) : '' }), promptInjection.hostKind ? ` · ${promptInjection.hostKind}` : ''] }), _jsx("p", { className: "mt-2", children: promptInjection.budgetCapacityStatus === 'confirmed' && promptInjection.budgetContextWindowTokens !== undefined
|
|
129
|
-
? promptInjection.budgetObservedAt !== undefined ? t('pages.focus.promptBudgetObservedNote', { source: t(promptInjection.budgetCapacitySource === 'pi_catalog_exact' ? 'pages.focus.budgetCapacitySourcePiCatalog' : promptInjection.budgetCapacitySource === 'operator_declared' ? 'pages.focus.budgetCapacitySourceOperatorDeclared' : 'pages.focus.budgetCapacitySourceHostWindow'), window: promptInjection.budgetContextWindowTokens, observedAt: promptInjection.budgetObservedAt })
|
|
130
|
-
: t('pages.focus.promptBudgetModelAwareNote', { window: promptInjection.budgetContextWindowTokens, sourceSuffix: promptInjection.budgetCapacitySource === 'operator_declared' ? t('pages.focus.budgetCapacitySourceOperatorDeclaredSuffix') : '' })
|
|
131
|
-
: promptInjection.budgetObservedAt !== undefined ? t('pages.focus.promptBudgetFallbackObservedNote', { reason: t(`pages.focus.budgetFallbackReason.${promptInjection.budgetFallbackReason}`, { defaultValue: promptInjection.budgetFallbackReason ?? 'unknown' }), observedAt: promptInjection.budgetObservedAt })
|
|
132
|
-
: t('pages.focus.promptBudgetFallbackNote', { reason: promptInjection.budgetFallbackReason ? t('pages.focus.promptBudgetFallbackReasonSuffix', { code: t(`pages.focus.budgetFallbackReason.${promptInjection.budgetFallbackReason}`, { defaultValue: promptInjection.budgetFallbackReason }) }) : '' }) })] }), review?.material.targetBehavior && _jsxs("div", { className: "mb-3 text-[14px] leading-relaxed text-ink-2", "data-testid": "approval-exact-target-behavior", children: [_jsx("h4", { className: "mb-1 font-medium", children: t('pages.focus.workbench.proposedBehavior') }), _jsx("p", { className: "whitespace-pre-wrap break-words", children: review.material.targetBehavior })] }), subject && _jsx("p", { className: "text-sm leading-relaxed text-ink-2", children: narrative(subject.decisionMaterial.consequence) }), _jsx("p", { className: "mt-2 text-[13px] leading-relaxed text-ink-3", children: t('pages.focus.workbench.scopeUnknown') }), view && _jsxs("details", { className: "mt-3 text-[13px] leading-relaxed text-ink-3", children: [_jsx("summary", { className: "cursor-pointer text-gov", children: t('pages.focus.workbench.principleLimits') }), _jsx("p", { className: "mt-3", children: view.nonApplicabilityOrUnknown.status === 'known' ? view.nonApplicabilityOrUnknown.value.map(item => item.text).join(' ') : view.nonApplicabilityOrUnknown.reason.ownerText }), _jsx("p", { className: "mt-2", children: view.risk.status === 'known' ? view.risk.value?.items.map(item => item.text).join(' ') || t('pages.principles.detail.ownerDecision.riskNotAssessed') : view.risk.reason?.ownerText }), _jsx("p", { className: "mt-2", children: view.uncertainty.status === 'known' ? view.uncertainty.value.map(item => item.text).join(' ') : view.uncertainty.reason.ownerText })] })] }), _jsxs("section", { className: "border-t border-line pt-5", children: [_jsx("h3", { className: "mb-3 text-[15px] font-medium text-ink", children: t('pages.focus.workbench.decision') }), lockedReason && _jsx("p", { role: "alert", className: "mb-3 text-[13px] leading-relaxed text-amber", children: lockedReason }), error && _jsx("p", { role: "alert", className: "mb-3 text-[13px] leading-relaxed text-amber", children: error }), resultText && _jsx("p", { role: "status", className: "mb-3 text-sm leading-relaxed text-ink", children: resultText }), !review && !loading && _jsx("button", { type: "button", className: "text-sm text-gov underline", onClick: () => { void read(); }, children: t('pages.focus.workbench.retry') }), review?.approval.status === 'pending' && _jsxs(_Fragment, { children: [_jsxs("details", { className: "mb-4 text-sm text-ink-3", children: [_jsx("summary", { className: "cursor-pointer text-gov", children: t('pages.focus.workbench.approvalNote') }), _jsxs("label", { className: "mt-3 block", children: [t('pages.focus.workbench.approvalNote'), _jsx("textarea", { value: approveNote, maxLength: 2000, onChange: event => setApproveNote(event.target.value), className: "mt-2 block w-full rounded border border-line bg-panel px-3 py-2" })] })] }), revisionReview && _jsxs("label", { className: "mb-4 block text-sm text-ink-2", children: [_jsx("input", { type: "checkbox", checked: intentReviewed, onChange: event => setIntentReviewed(event.target.checked), className: "mr-2" }), t('pages.focus.workbench.intentReviewed')] }), _jsx("p", { className: "mb-4 text-[12.5px] leading-relaxed text-ink-3", children: t(channel === 'code_tool_hook' ? 'pages.focus.workbench.ruleConsequence' : channel === 'defer_archive' ? 'pages.focus.workbench.archiveConsequence' : 'pages.focus.workbench.promptConsequence') }), _jsxs("div", { className: "flex flex-wrap gap-3", children: [_jsx("button", { type: "button", disabled: !approveReady || busy || Boolean(lockedReason) || !may('approve') || Boolean(revisionReview && !intentReviewed), "data-testid": `approve-btn-${principleId}`, onClick: () => { void submit('approve'); }, className: "rounded-[4px] border border-gov bg-gov px-4 py-2.5 text-[13px] text-paper disabled:opacity-50", children: t(channel === 'defer_archive' ? 'pages.focus.workbench.approveArchive' : 'pages.focus.workbench.approveOne') }), _jsx("button", { type: "button", disabled: busy || Boolean(lockedReason) || !may('reject'), onClick: () => setMode('reject'), className: "rounded-[4px] border border-line px-4 py-2.5 text-[13px] text-ink disabled:opacity-50", children: t('pages.focus.workbench.rejectOne') }), _jsx("button", { type: "button", "data-testid": `edit-btn-${principleId}`, disabled: busy || Boolean(lockedReason) || !may('edit_approval') || review.expectedReview.artifactState === 'missing', onClick: () => setMode('edit'), className: "text-[13px] text-gov underline disabled:opacity-50", children: t('pages.focus.workbench.replaceOne') })] }), mode && _jsxs("form", { className: "mt-4 space-y-3", onSubmit: event => { event.preventDefault(); void submit(mode); }, children: [mode === 'edit' && _jsxs("label", { className: "block text-sm text-ink-2", children: [t('pages.focus.workbench.replacementId'), _jsx("input", { value: newArtifact, onChange: event => setNewArtifact(event.target.value), className: "mt-2 block w-full rounded border border-line bg-panel px-3 py-2" })] }), _jsxs("label", { className: "block text-sm text-ink-2", children: [t('pages.focus.workbench.reason'), _jsx("textarea", { value: reason, maxLength: 2000, onChange: event => setReason(event.target.value), className: "mt-2 block w-full rounded border border-line bg-panel px-3 py-2" })] }), _jsx("button", { type: "submit", disabled: busy || !reason.trim() || (mode === 'edit' && !newArtifact.trim()), className: "rounded border border-line px-4 py-2 text-sm disabled:opacity-50", children: t('pages.focus.workbench.confirm') }), _jsx("button", { type: "button", onClick: () => setMode(null), className: "ml-3 text-sm text-ink-3", children: t('pages.focus.workbench.cancel') })] })] }), showPrincipleLink && _jsx(Link, { className: "mt-5 inline-block text-[13px] text-gov underline underline-offset-4", state: { returnTo: `/focus${location.search}` }, to: `/principles/${encodeURIComponent(principleId)}`, children: t('pages.focus.workbench.openPrinciple') }), _jsxs("details", { className: "mt-4 text-[12px] text-ink-4", children: [_jsx("summary", { className: "cursor-pointer", children: t('pages.focus.workbench.subjectDetails') }), _jsxs("dl", { className: "mt-2 break-all font-mono", children: [_jsxs("div", { children: ["approval: ", approvalId] }), _jsxs("div", { children: ["artifact: ", review?.expectedReview.artifactId ?? artifactId] }), _jsxs("div", { children: ["channel: ", review?.expectedReview.channel ?? channel] })] })] })] })] });
|
|
133
|
-
}
|