@ashlr/hub 3.10.0 → 3.11.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/CHANGELOG.md +156 -0
- package/README.md +343 -82
- package/dist/authority-surface.json +109 -29
- package/dist/build-identity.json +2 -2
- package/dist/cli/cloud.d.ts +39 -0
- package/dist/cli/cloud.js +448 -0
- package/dist/cli/cloud.js.map +1 -0
- package/dist/cli/completions.js +4 -0
- package/dist/cli/completions.js.map +1 -1
- package/dist/cli/help.js +2 -0
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +10 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/core/cloud/backlog.d.ts +17 -0
- package/dist/core/cloud/backlog.js +177 -0
- package/dist/core/cloud/backlog.js.map +1 -0
- package/dist/core/cloud/budget.d.ts +19 -0
- package/dist/core/cloud/budget.js +99 -0
- package/dist/core/cloud/budget.js.map +1 -0
- package/dist/core/cloud/checkout.d.ts +41 -0
- package/dist/core/cloud/checkout.js +168 -0
- package/dist/core/cloud/checkout.js.map +1 -0
- package/dist/core/cloud/cloud-api.d.ts +86 -0
- package/dist/core/cloud/cloud-api.js +599 -0
- package/dist/core/cloud/cloud-api.js.map +1 -0
- package/dist/core/cloud/delivery-contract.d.ts +25 -0
- package/dist/core/cloud/delivery-contract.js +138 -0
- package/dist/core/cloud/delivery-contract.js.map +1 -0
- package/dist/core/cloud/improvement-backlog.d.ts +11 -0
- package/dist/core/cloud/improvement-backlog.js +26 -0
- package/dist/core/cloud/improvement-backlog.js.map +1 -0
- package/dist/core/cloud/launcher.d.ts +68 -0
- package/dist/core/cloud/launcher.js +312 -0
- package/dist/core/cloud/launcher.js.map +1 -0
- package/dist/core/cloud/service.d.ts +51 -0
- package/dist/core/cloud/service.js +215 -0
- package/dist/core/cloud/service.js.map +1 -0
- package/dist/core/cloud/store.d.ts +41 -0
- package/dist/core/cloud/store.js +278 -0
- package/dist/core/cloud/store.js.map +1 -0
- package/dist/core/cloud/tracker.d.ts +30 -0
- package/dist/core/cloud/tracker.js +152 -0
- package/dist/core/cloud/tracker.js.map +1 -0
- package/dist/core/cloud/types.d.ts +233 -0
- package/dist/core/cloud/types.js +73 -0
- package/dist/core/cloud/types.js.map +1 -0
- package/dist/core/daemon/capacity-publisher.d.ts +19 -0
- package/dist/core/daemon/capacity-publisher.js +49 -2
- package/dist/core/daemon/capacity-publisher.js.map +1 -1
- package/dist/core/daemon/overnight-status.d.ts +13 -0
- package/dist/core/daemon/overnight-status.js +93 -8
- package/dist/core/daemon/overnight-status.js.map +1 -1
- package/dist/core/fleet/dispatch-router.js +26 -9
- package/dist/core/fleet/dispatch-router.js.map +1 -1
- package/dist/core/fleet/fleet-runtime-journal.js +12 -5
- package/dist/core/fleet/fleet-runtime-journal.js.map +1 -1
- package/dist/core/fleet/fleet-types.d.ts +13 -0
- package/dist/core/routing/budget-store.js +11 -5
- package/dist/core/routing/budget-store.js.map +1 -1
- package/dist/core/routing/capacity-history-api.d.ts +30 -0
- package/dist/core/routing/capacity-history-api.js +147 -0
- package/dist/core/routing/capacity-history-api.js.map +1 -0
- package/dist/core/routing/capacity-history-types.d.ts +63 -0
- package/dist/core/routing/capacity-history-types.js +18 -0
- package/dist/core/routing/capacity-history-types.js.map +1 -0
- package/dist/core/routing/capacity-history.d.ts +130 -0
- package/dist/core/routing/capacity-history.js +654 -0
- package/dist/core/routing/capacity-history.js.map +1 -0
- package/dist/core/routing/headroom.d.ts +5 -1
- package/dist/core/routing/headroom.js +82 -41
- package/dist/core/routing/headroom.js.map +1 -1
- package/dist/core/routing/router.d.ts +8 -1
- package/dist/core/routing/router.js +49 -16
- package/dist/core/routing/router.js.map +1 -1
- package/dist/core/routing/seat-reasons.d.ts +35 -0
- package/dist/core/routing/seat-reasons.js +48 -0
- package/dist/core/routing/seat-reasons.js.map +1 -0
- package/dist/core/routing/types.d.ts +37 -0
- package/dist/core/routing/types.js.map +1 -1
- package/dist/core/verse/activity-api.d.ts +3 -1
- package/dist/core/verse/activity-api.js +16 -1
- package/dist/core/verse/activity-api.js.map +1 -1
- package/dist/core/verse/activity.d.ts +7 -0
- package/dist/core/verse/activity.js +23 -0
- package/dist/core/verse/activity.js.map +1 -1
- package/dist/core/verse/fleet-history-types.d.ts +12 -4
- package/dist/core/verse/fleet-history-types.js +4 -2
- package/dist/core/verse/fleet-history-types.js.map +1 -1
- package/dist/core/verse/fleet-live-api.d.ts +14 -1
- package/dist/core/verse/fleet-live-api.js +31 -11
- package/dist/core/verse/fleet-live-api.js.map +1 -1
- package/dist/core/verse/preferences.d.ts +3 -1
- package/dist/core/verse/preferences.js +5 -2
- package/dist/core/verse/preferences.js.map +1 -1
- package/dist/core/verse/types.d.ts +5 -1
- package/dist/core/verse/types.js.map +1 -1
- package/dist/core/verse/verse-api.d.ts +1 -1
- package/dist/core/verse/verse-api.js +4 -1
- package/dist/core/verse/verse-api.js.map +1 -1
- package/dist/core/vision/leader-cloud.d.ts +63 -0
- package/dist/core/vision/leader-cloud.js +134 -0
- package/dist/core/vision/leader-cloud.js.map +1 -0
- package/dist/core/vision/leader.d.ts +6 -0
- package/dist/core/vision/leader.js +12 -1
- package/dist/core/vision/leader.js.map +1 -1
- package/dist/core/web/public/next/assets/{ActionMenu-DhbNne-M.js → ActionMenu-CQvNJvHY.js} +2 -2
- package/dist/core/web/public/next/assets/{ActionMenu-DhbNne-M.js.map → ActionMenu-CQvNJvHY.js.map} +1 -1
- package/dist/core/web/public/next/assets/Advanced-Bywv_iYR.css +1 -0
- package/dist/core/web/public/next/assets/Advanced-DEM93sKI.js +2 -0
- package/dist/core/web/public/next/assets/Advanced-DEM93sKI.js.map +1 -0
- package/dist/core/web/public/next/assets/{App-DyBg1v_q.js → App-CimXmK0L.js} +5 -5
- package/dist/core/web/public/next/assets/{App-DyBg1v_q.js.map → App-CimXmK0L.js.map} +1 -1
- package/dist/core/web/public/next/assets/AppsSection-D6U-dSj6.js +2 -0
- package/dist/core/web/public/next/assets/AppsSection-D6U-dSj6.js.map +1 -0
- package/dist/core/web/public/next/assets/AreaTrend-LD9lCPkT.js +2 -0
- package/dist/core/web/public/next/assets/AreaTrend-LD9lCPkT.js.map +1 -0
- package/dist/core/web/public/next/assets/AutonomySection-3V9pUyxm.js +2 -0
- package/dist/core/web/public/next/assets/{AutonomySection-CoMngwNB.js.map → AutonomySection-3V9pUyxm.js.map} +1 -1
- package/dist/core/web/public/next/assets/BranchBar-BZNQ50YE.css +1 -0
- package/dist/core/web/public/next/assets/BranchBar-U8UdpfEB.js +2 -0
- package/dist/core/web/public/next/assets/BranchBar-U8UdpfEB.js.map +1 -0
- package/dist/core/web/public/next/assets/BudgetControl-DOTlMSmW.js +2 -0
- package/dist/core/web/public/next/assets/BudgetControl-DOTlMSmW.js.map +1 -0
- package/dist/core/web/public/next/assets/BurnDown-x4PWH9JF.js +2 -0
- package/dist/core/web/public/next/assets/BurnDown-x4PWH9JF.js.map +1 -0
- package/dist/core/web/public/next/assets/{Button-0-nNbv1h.js → Button-DYv_KKxr.js} +2 -2
- package/dist/core/web/public/next/assets/{Button-0-nNbv1h.js.map → Button-DYv_KKxr.js.map} +1 -1
- package/dist/core/web/public/next/assets/CapacityStrip-C667yQe8.js +2 -0
- package/dist/core/web/public/next/assets/CapacityStrip-C667yQe8.js.map +1 -0
- package/dist/core/web/public/next/assets/CapacityStrip-Cc28CyWk.css +1 -0
- package/dist/core/web/public/next/assets/ChatSection-CCcIPpOq.js +3 -0
- package/dist/core/web/public/next/assets/{ChatSection-B9cdRKUh.js.map → ChatSection-CCcIPpOq.js.map} +1 -1
- package/dist/core/web/public/next/assets/CloudBudgetForm-DJeJvBEu.js +2 -0
- package/dist/core/web/public/next/assets/CloudBudgetForm-DJeJvBEu.js.map +1 -0
- package/dist/core/web/public/next/assets/{CommandPalette-GpeCSrPy.js → CommandPalette-FiD4N8XD.js} +2 -2
- package/dist/core/web/public/next/assets/{CommandPalette-GpeCSrPy.js.map → CommandPalette-FiD4N8XD.js.map} +1 -1
- package/dist/core/web/public/next/assets/CommandSection-DN8KwH-X.js +2 -0
- package/dist/core/web/public/next/assets/CommandSection-DN8KwH-X.js.map +1 -0
- package/dist/core/web/public/next/assets/Composer-ByzxOJK6.css +1 -0
- package/dist/core/web/public/next/assets/Composer.module-C8U73KdQ.js +2 -0
- package/dist/core/web/public/next/assets/{Composer.module-DJMdrqNt.js.map → Composer.module-C8U73KdQ.js.map} +1 -1
- package/dist/core/web/public/next/assets/DiffPane-BAvvbBsf.css +1 -0
- package/dist/core/web/public/next/assets/DiffPane-DLJ3gGGK.js +2 -0
- package/dist/core/web/public/next/assets/DiffPane-DLJ3gGGK.js.map +1 -0
- package/dist/core/web/public/next/assets/{DiffViewer-tOGWk0CA.js → DiffViewer-B7NWPjIO.js} +2 -2
- package/dist/core/web/public/next/assets/{DiffViewer-tOGWk0CA.js.map → DiffViewer-B7NWPjIO.js.map} +1 -1
- package/dist/core/web/public/next/assets/DockHost-B4RaYOET.css +1 -0
- package/dist/core/web/public/next/assets/DockHost-DvR-hPhZ.js +2 -0
- package/dist/core/web/public/next/assets/DockHost-DvR-hPhZ.js.map +1 -0
- package/dist/core/web/public/next/assets/{EmptyState-DdJ4CxtO.js → EmptyState-DhNgDDrF.js} +2 -2
- package/dist/core/web/public/next/assets/{EmptyState-DdJ4CxtO.js.map → EmptyState-DhNgDDrF.js.map} +1 -1
- package/dist/core/web/public/next/assets/{Epistemic-BLjM5y_y.js → Epistemic-uARPnx9x.js} +2 -2
- package/dist/core/web/public/next/assets/{Epistemic-BLjM5y_y.js.map → Epistemic-uARPnx9x.js.map} +1 -1
- package/dist/core/web/public/next/assets/FleetSection-CFiWeOEs.css +1 -0
- package/dist/core/web/public/next/assets/FleetSection-D_zRBCub.js +3 -0
- package/dist/core/web/public/next/assets/FleetSection-D_zRBCub.js.map +1 -0
- package/dist/core/web/public/next/assets/Funnel-CL8uHONq.js +2 -0
- package/dist/core/web/public/next/assets/Funnel-CL8uHONq.js.map +1 -0
- package/dist/core/web/public/next/assets/GearTray-C7k4l_BW.js +2 -0
- package/dist/core/web/public/next/assets/GearTray-C7k4l_BW.js.map +1 -0
- package/dist/core/web/public/next/assets/GrowthSection-DwdNSGj8.js +2 -0
- package/dist/core/web/public/next/assets/GrowthSection-DwdNSGj8.js.map +1 -0
- package/dist/core/web/public/next/assets/HandoffDialog-FPVOqOMN.js +2 -0
- package/dist/core/web/public/next/assets/HandoffDialog-FPVOqOMN.js.map +1 -0
- package/dist/core/web/public/next/assets/{Input-CTu0CycC.js → Input-B9-VLcHS.js} +2 -2
- package/dist/core/web/public/next/assets/{Input-CTu0CycC.js.map → Input-B9-VLcHS.js.map} +1 -1
- package/dist/core/web/public/next/assets/{LeaderCard-BuSDeATv.js → LeaderCard-4R0BhDzT.js} +2 -2
- package/dist/core/web/public/next/assets/{LeaderCard-BuSDeATv.js.map → LeaderCard-4R0BhDzT.js.map} +1 -1
- package/dist/core/web/public/next/assets/{MatrixHeatmap-9dKgJ_pT.css → MatrixHeatmap-Bmnvo1l1.css} +1 -1
- package/dist/core/web/public/next/assets/MatrixHeatmap-DiuushcS.js +2 -0
- package/dist/core/web/public/next/assets/MatrixHeatmap-DiuushcS.js.map +1 -0
- package/dist/core/web/public/next/assets/McpSection-CJ__OJKl.js +2 -0
- package/dist/core/web/public/next/assets/{McpSection-1UncWBpW.js.map → McpSection-CJ__OJKl.js.map} +1 -1
- package/dist/core/web/public/next/assets/MessageMarkdown-Cgh5f1-Q.css +1 -0
- package/dist/core/web/public/next/assets/MessageMarkdown-qWKyJvAQ.js +3 -0
- package/dist/core/web/public/next/assets/{MessageMarkdown-C4pl9LI5.js.map → MessageMarkdown-qWKyJvAQ.js.map} +1 -1
- package/dist/core/web/public/next/assets/{MessageMarkdownRenderer-CZsF77ZM.js → MessageMarkdownRenderer-x05TrsrW.js} +2 -2
- package/dist/core/web/public/next/assets/{MessageMarkdownRenderer-CZsF77ZM.js.map → MessageMarkdownRenderer-x05TrsrW.js.map} +1 -1
- package/dist/core/web/public/next/assets/{Meter-C18ciKz4.js → Meter-D-hncpkO.js} +2 -2
- package/dist/core/web/public/next/assets/{Meter-C18ciKz4.js.map → Meter-D-hncpkO.js.map} +1 -1
- package/dist/core/web/public/next/assets/MindSection-BjXHkQPS.js +2 -0
- package/dist/core/web/public/next/assets/MindSection-BjXHkQPS.js.map +1 -0
- package/dist/core/web/public/next/assets/MutationTokenDialog-SkRxqV_I.js +2 -0
- package/dist/core/web/public/next/assets/MutationTokenDialog-SkRxqV_I.js.map +1 -0
- package/dist/core/web/public/next/assets/NeedsYouDrawer-BpvidnM2.css +1 -0
- package/dist/core/web/public/next/assets/NeedsYouDrawer-rSd1ZHAR.js +2 -0
- package/dist/core/web/public/next/assets/NeedsYouDrawer-rSd1ZHAR.js.map +1 -0
- package/dist/core/web/public/next/assets/NeedsYouList-CG-nB1tr.js +2 -0
- package/dist/core/web/public/next/assets/NeedsYouList-CG-nB1tr.js.map +1 -0
- package/dist/core/web/public/next/assets/NeedsYouList-CruOt4JT.css +1 -0
- package/dist/core/web/public/next/assets/NewChatDialog-BeDQ1eW3.js +3 -0
- package/dist/core/web/public/next/assets/{NewChatDialog-B_BsS9pr.js.map → NewChatDialog-BeDQ1eW3.js.map} +1 -1
- package/dist/core/web/public/next/assets/OnboardingFlow-CkTZtt-m.js +2 -0
- package/dist/core/web/public/next/assets/{OnboardingFlow-CtCPpgoX.js.map → OnboardingFlow-CkTZtt-m.js.map} +1 -1
- package/dist/core/web/public/next/assets/PreviewPane-DoMsJSG3.css +1 -0
- package/dist/core/web/public/next/assets/PreviewPane-kzbbSK4u.js +2 -0
- package/dist/core/web/public/next/assets/PreviewPane-kzbbSK4u.js.map +1 -0
- package/dist/core/web/public/next/assets/RailStatus-D-t2gPae.js +2 -0
- package/dist/core/web/public/next/assets/RailStatus-D-t2gPae.js.map +1 -0
- package/dist/core/web/public/next/assets/{RefreshIndicator-CnhEovMZ.js → RefreshIndicator-BLnr9IOM.js} +2 -2
- package/dist/core/web/public/next/assets/{RefreshIndicator-CnhEovMZ.js.map → RefreshIndicator-BLnr9IOM.js.map} +1 -1
- package/dist/core/web/public/next/assets/{ResourcePoolConsoleApp-28gLwmwV.js → ResourcePoolConsoleApp-BeG0gBif.js} +4 -4
- package/dist/core/web/public/next/assets/{ResourcePoolConsoleApp-28gLwmwV.js.map → ResourcePoolConsoleApp-BeG0gBif.js.map} +1 -1
- package/dist/core/web/public/next/assets/ResourcesChrome-B3Z3O98t.css +1 -0
- package/dist/core/web/public/next/assets/ResourcesChrome-BzV6bkIa.js +3 -0
- package/dist/core/web/public/next/assets/ResourcesChrome-BzV6bkIa.js.map +1 -0
- package/dist/core/web/public/next/assets/ResourcesDrawer-CSKIO_wZ.css +1 -0
- package/dist/core/web/public/next/assets/ResourcesDrawer-o4ILHdep.js +2 -0
- package/dist/core/web/public/next/assets/ResourcesDrawer-o4ILHdep.js.map +1 -0
- package/dist/core/web/public/next/assets/{RouteErrorBoundary-AyUPCIb3.js → RouteErrorBoundary-C5SxA2Dg.js} +2 -2
- package/dist/core/web/public/next/assets/{RouteErrorBoundary-AyUPCIb3.js.map → RouteErrorBoundary-C5SxA2Dg.js.map} +1 -1
- package/dist/core/web/public/next/assets/RunInCloudAction-C3LBp7C4.js +2 -0
- package/dist/core/web/public/next/assets/RunInCloudAction-C3LBp7C4.js.map +1 -0
- package/dist/core/web/public/next/assets/SeatSelector-DpvvbjNm.js +3 -0
- package/dist/core/web/public/next/assets/{SeatSelector-DaUO-zoK.js.map → SeatSelector-DpvvbjNm.js.map} +1 -1
- package/dist/core/web/public/next/assets/{Segmented-B_8c_83S.js → Segmented-BL40udoO.js} +2 -2
- package/dist/core/web/public/next/assets/{Segmented-B_8c_83S.js.map → Segmented-BL40udoO.js.map} +1 -1
- package/dist/core/web/public/next/assets/{Select-Bl5g5Dt_.js → Select-FWLnAw_j.js} +2 -2
- package/dist/core/web/public/next/assets/{Select-Bl5g5Dt_.js.map → Select-FWLnAw_j.js.map} +1 -1
- package/dist/core/web/public/next/assets/{SessionInsightChip-w0I21q0c.js → SessionInsightChip-CWye87Ju.js} +2 -2
- package/dist/core/web/public/next/assets/{SessionInsightChip-w0I21q0c.js.map → SessionInsightChip-CWye87Ju.js.map} +1 -1
- package/dist/core/web/public/next/assets/SessionSearch-YvCHFUuN.js +2 -0
- package/dist/core/web/public/next/assets/{SessionSearch-B5aQ0BCM.js.map → SessionSearch-YvCHFUuN.js.map} +1 -1
- package/dist/core/web/public/next/assets/SettingsSection-Dr18upeE.js +2 -0
- package/dist/core/web/public/next/assets/SettingsSection-Dr18upeE.js.map +1 -0
- package/dist/core/web/public/next/assets/{Sheet-CVjk-cLI.js → Sheet-DZKcQXQg.js} +2 -2
- package/dist/core/web/public/next/assets/{Sheet-CVjk-cLI.js.map → Sheet-DZKcQXQg.js.map} +1 -1
- package/dist/core/web/public/next/assets/{ShortcutsOverlay-BiBjnBcc.js → ShortcutsOverlay-BpDimdPq.js} +2 -2
- package/dist/core/web/public/next/assets/{ShortcutsOverlay-BiBjnBcc.js.map → ShortcutsOverlay-BpDimdPq.js.map} +1 -1
- package/dist/core/web/public/next/assets/Sidebar-BRc1vGDF.css +1 -0
- package/dist/core/web/public/next/assets/Sidebar-BoBzBEFX.js +3 -0
- package/dist/core/web/public/next/assets/Sidebar-BoBzBEFX.js.map +1 -0
- package/dist/core/web/public/next/assets/{Slider-BS9MU5ZV.js → Slider-DUMo9HDD.js} +2 -2
- package/dist/core/web/public/next/assets/{Slider-BS9MU5ZV.js.map → Slider-DUMo9HDD.js.map} +1 -1
- package/dist/core/web/public/next/assets/{StatusBadge-DAf-ArKE.js → StatusBadge-BwnBNFQr.js} +2 -2
- package/dist/core/web/public/next/assets/{StatusBadge-DAf-ArKE.js.map → StatusBadge-BwnBNFQr.js.map} +1 -1
- package/dist/core/web/public/next/assets/StepBand-PDXMIlRk.js +2 -0
- package/dist/core/web/public/next/assets/StepBand-PDXMIlRk.js.map +1 -0
- package/dist/core/web/public/next/assets/{Surface-CiYOdNKQ.js → Surface-B3TZVoER.js} +2 -2
- package/dist/core/web/public/next/assets/{Surface-CiYOdNKQ.js.map → Surface-B3TZVoER.js.map} +1 -1
- package/dist/core/web/public/next/assets/Swimlane-DR4OgON_.js +2 -0
- package/dist/core/web/public/next/assets/Swimlane-DR4OgON_.js.map +1 -0
- package/dist/core/web/public/next/assets/{Switch-BzRXtNPt.js → Switch-CcR5Llk5.js} +2 -2
- package/dist/core/web/public/next/assets/{Switch-BzRXtNPt.js.map → Switch-CcR5Llk5.js.map} +1 -1
- package/dist/core/web/public/next/assets/{Tag-DEEbDz7T.js → Tag-_-K1h7Hk.js} +2 -2
- package/dist/core/web/public/next/assets/{Tag-DEEbDz7T.js.map → Tag-_-K1h7Hk.js.map} +1 -1
- package/dist/core/web/public/next/assets/TerminalPane-BwfMbnJz.css +1 -0
- package/dist/core/web/public/next/assets/TerminalPane-_1aH4ndN.js +7 -0
- package/dist/core/web/public/next/assets/TerminalPane-_1aH4ndN.js.map +1 -0
- package/dist/core/web/public/next/assets/UniverseConsoleApp-BUsq-inZ.js +2 -0
- package/dist/core/web/public/next/assets/{UniverseConsoleApp-DKqAf-eD.js.map → UniverseConsoleApp-BUsq-inZ.js.map} +1 -1
- package/dist/core/web/public/next/assets/{UniverseView-D6081LQL.js → UniverseView-Dogy_dsB.js} +2 -2
- package/dist/core/web/public/next/assets/{UniverseView-D6081LQL.js.map → UniverseView-Dogy_dsB.js.map} +1 -1
- package/dist/core/web/public/next/assets/UsageSection-DiX_dTyo.js +2 -0
- package/dist/core/web/public/next/assets/UsageSection-DiX_dTyo.js.map +1 -0
- package/dist/core/web/public/next/assets/UsageSection-DsB-Jqyl.css +1 -0
- package/dist/core/web/public/next/assets/VerseConsoleApp-Cxqr-3qc.css +1 -0
- package/dist/core/web/public/next/assets/VerseConsoleApp-WVeyJnW7.js +3 -0
- package/dist/core/web/public/next/assets/VerseConsoleApp-WVeyJnW7.js.map +1 -0
- package/dist/core/web/public/next/assets/Workspace-BYW9dm90.css +1 -0
- package/dist/core/web/public/next/assets/Workspace-BbxZHwQd.js +10 -0
- package/dist/core/web/public/next/assets/Workspace-BbxZHwQd.js.map +1 -0
- package/dist/core/web/public/next/assets/actions-Ddfg2iLl.css +1 -0
- package/dist/core/web/public/next/assets/actions-s2lfm_5k.js +2 -0
- package/dist/core/web/public/next/assets/actions-s2lfm_5k.js.map +1 -0
- package/dist/core/web/public/next/assets/apps-model-CDFvmsQV.js +2 -0
- package/dist/core/web/public/next/assets/apps-model-CDFvmsQV.js.map +1 -0
- package/dist/core/web/public/next/assets/{budget-queries-Dacv1VYP.js → budget-queries-wUshcovN.js} +2 -2
- package/dist/core/web/public/next/assets/{budget-queries-Dacv1VYP.js.map → budget-queries-wUshcovN.js.map} +1 -1
- package/dist/core/web/public/next/assets/calendar-day-BxBYHfX1.js +2 -0
- package/dist/core/web/public/next/assets/calendar-day-BxBYHfX1.js.map +1 -0
- package/dist/core/web/public/next/assets/capacity-strip-model-Bj_9ztzs.js +2 -0
- package/dist/core/web/public/next/assets/capacity-strip-model-Bj_9ztzs.js.map +1 -0
- package/dist/core/web/public/next/assets/charts-CMSgAUKn.js +2 -0
- package/dist/core/web/public/next/assets/charts-CMSgAUKn.js.map +1 -0
- package/dist/core/web/public/next/assets/cloud-C1KPNRHC.css +1 -0
- package/dist/core/web/public/next/assets/cloud-queries-CnV1yaNG.js +2 -0
- package/dist/core/web/public/next/assets/cloud-queries-CnV1yaNG.js.map +1 -0
- package/dist/core/web/public/next/assets/cloud.module-CiwatLj3.js +2 -0
- package/dist/core/web/public/next/assets/cloud.module-CiwatLj3.js.map +1 -0
- package/dist/core/web/public/next/assets/colors-BZ3a9wFf.js +2 -0
- package/dist/core/web/public/next/assets/colors-BZ3a9wFf.js.map +1 -0
- package/dist/core/web/public/next/assets/{command-bus-CpYuam1F.js → command-bus-DqimKCUY.js} +2 -2
- package/dist/core/web/public/next/assets/{command-bus-CpYuam1F.js.map → command-bus-DqimKCUY.js.map} +1 -1
- package/dist/core/web/public/next/assets/command-catalog-BdtDH93o.js +2 -0
- package/dist/core/web/public/next/assets/command-catalog-BdtDH93o.js.map +1 -0
- package/dist/core/web/public/next/assets/{composer-keys-CYz4U4Bg.js → composer-keys-DBRymxQn.js} +2 -2
- package/dist/core/web/public/next/assets/{composer-keys-CYz4U4Bg.js.map → composer-keys-DBRymxQn.js.map} +1 -1
- package/dist/core/web/public/next/assets/context-model-Dwd49-dp.js +2 -0
- package/dist/core/web/public/next/assets/context-model-Dwd49-dp.js.map +1 -0
- package/dist/core/web/public/next/assets/context-model-MT17V86_.js +2 -0
- package/dist/core/web/public/next/assets/context-model-MT17V86_.js.map +1 -0
- package/dist/core/web/public/next/assets/context-queries-xVKIeynP.js +2 -0
- package/dist/core/web/public/next/assets/context-queries-xVKIeynP.js.map +1 -0
- package/dist/core/web/public/next/assets/context.module-BijAfoJw.js +2 -0
- package/dist/core/web/public/next/assets/context.module-BijAfoJw.js.map +1 -0
- package/dist/core/web/public/next/assets/{control-queries-BGjarMm9.js → control-queries-DOZR-D8G.js} +2 -2
- package/dist/core/web/public/next/assets/{control-queries-BGjarMm9.js.map → control-queries-DOZR-D8G.js.map} +1 -1
- package/dist/core/web/public/next/assets/dark-since-DOMvtB_G.js +2 -0
- package/dist/core/web/public/next/assets/dark-since-DOMvtB_G.js.map +1 -0
- package/dist/core/web/public/next/assets/dock-icons-8EtARwUm.js +2 -0
- package/dist/core/web/public/next/assets/dock-icons-8EtARwUm.js.map +1 -0
- package/dist/core/web/public/next/assets/dock-panes-UKS-mXhr.js +2 -0
- package/dist/core/web/public/next/assets/{dock-panes-BNTLvX7G.js.map → dock-panes-UKS-mXhr.js.map} +1 -1
- package/dist/core/web/public/next/assets/{dock-store-_E5h6TEq.js → dock-store-BA1cHOYA.js} +2 -2
- package/dist/core/web/public/next/assets/{dock-store-_E5h6TEq.js.map → dock-store-BA1cHOYA.js.map} +1 -1
- package/dist/core/web/public/next/assets/fleet-model-CNe8Wh-s.js +2 -0
- package/dist/core/web/public/next/assets/fleet-model-CNe8Wh-s.js.map +1 -0
- package/dist/core/web/public/next/assets/fleet-queries-BSdRlUwJ.js +2 -0
- package/dist/core/web/public/next/assets/fleet-queries-BSdRlUwJ.js.map +1 -0
- package/dist/core/web/public/next/assets/format-B8DAVcn2.js +2 -0
- package/dist/core/web/public/next/assets/format-B8DAVcn2.js.map +1 -0
- package/dist/core/web/public/next/assets/format-DKrNjdwD.js +2 -0
- package/dist/core/web/public/next/assets/format-DKrNjdwD.js.map +1 -0
- package/dist/core/web/public/next/assets/{git-model-5v5xGobf.js → git-model-CivuBY_D.js} +2 -2
- package/dist/core/web/public/next/assets/{git-model-5v5xGobf.js.map → git-model-CivuBY_D.js.map} +1 -1
- package/dist/core/web/public/next/assets/global-Di2PUgj4.js +2 -0
- package/dist/core/web/public/next/assets/global-Di2PUgj4.js.map +1 -0
- package/dist/core/web/public/next/assets/{guarded-action-vOPc8v75.js → guarded-action-CvOHcqyH.js} +2 -2
- package/dist/core/web/public/next/assets/{guarded-action-vOPc8v75.js.map → guarded-action-CvOHcqyH.js.map} +1 -1
- package/dist/core/web/public/next/assets/{icons-DWb24OA6.js → icons-B_yuAgdN.js} +2 -2
- package/dist/core/web/public/next/assets/{icons-DWb24OA6.js.map → icons-B_yuAgdN.js.map} +1 -1
- package/dist/core/web/public/next/assets/{index-afDA8cwW.js → index-oAAq8XJa.js} +3 -3
- package/dist/core/web/public/next/assets/{index-afDA8cwW.js.map → index-oAAq8XJa.js.map} +1 -1
- package/dist/core/web/public/next/assets/live-model-DCw_IFRI.js +2 -0
- package/dist/core/web/public/next/assets/live-model-DCw_IFRI.js.map +1 -0
- package/dist/core/web/public/next/assets/local-model-DFyjKX0b.js +2 -0
- package/dist/core/web/public/next/assets/local-model-DFyjKX0b.js.map +1 -0
- package/dist/core/web/public/next/assets/mind-B9PzLcl0.css +1 -0
- package/dist/core/web/public/next/assets/mind.module-vnuJcwvD.js +2 -0
- package/dist/core/web/public/next/assets/mind.module-vnuJcwvD.js.map +1 -0
- package/dist/core/web/public/next/assets/mutations-Dpsql5t7.js +2 -0
- package/dist/core/web/public/next/assets/{mutations-C6E10SPX.js.map → mutations-Dpsql5t7.js.map} +1 -1
- package/dist/core/web/public/next/assets/{overnight-queries-CCGfiS-g.js → overnight-queries-X-44Gsw5.js} +2 -2
- package/dist/core/web/public/next/assets/{overnight-queries-CCGfiS-g.js.map → overnight-queries-X-44Gsw5.js.map} +1 -1
- package/dist/core/web/public/next/assets/pane-chrome-8JmEhHVD.css +1 -0
- package/dist/core/web/public/next/assets/pane-chrome.module-D7o-fVd0.js +2 -0
- package/dist/core/web/public/next/assets/pane-chrome.module-D7o-fVd0.js.map +1 -0
- package/dist/core/web/public/next/assets/primitives-BDIcUvob.js +1 -0
- package/dist/core/web/public/next/assets/{queries-BuYIEHst.js → queries-BA4rNGD-.js} +2 -2
- package/dist/core/web/public/next/assets/{queries-BuYIEHst.js.map → queries-BA4rNGD-.js.map} +1 -1
- package/dist/core/web/public/next/assets/{reasoning-pref-BVzGczV7.js → reasoning-pref-B9-DJUXP.js} +2 -2
- package/dist/core/web/public/next/assets/{reasoning-pref-BVzGczV7.js.map → reasoning-pref-B9-DJUXP.js.map} +1 -1
- package/dist/core/web/public/next/assets/seat-subscription-CH6fW4fJ.js +2 -0
- package/dist/core/web/public/next/assets/seat-subscription-CH6fW4fJ.js.map +1 -0
- package/dist/core/web/public/next/assets/{section-visibility-CSrsTcwK.js → section-visibility-qFqc_d4Z.js} +2 -2
- package/dist/core/web/public/next/assets/{section-visibility-CSrsTcwK.js.map → section-visibility-qFqc_d4Z.js.map} +1 -1
- package/dist/core/web/public/next/assets/{sidebar-model-C8fyqSQY.js → sidebar-model-M1GaQU5l.js} +2 -2
- package/dist/core/web/public/next/assets/{sidebar-model-C8fyqSQY.js.map → sidebar-model-M1GaQU5l.js.map} +1 -1
- package/dist/core/web/public/next/assets/slots-Ds9ZIqUL.js +3 -0
- package/dist/core/web/public/next/assets/{slots-W8gkmdfE.js.map → slots-Ds9ZIqUL.js.map} +1 -1
- package/dist/core/web/public/next/assets/surface-data-J52iIGMD.js +2 -0
- package/dist/core/web/public/next/assets/surface-data-J52iIGMD.js.map +1 -0
- package/dist/core/web/public/next/assets/surface-prefetch-CgZ-LEJK.js +2 -0
- package/dist/core/web/public/next/assets/surface-prefetch-CgZ-LEJK.js.map +1 -0
- package/dist/core/web/public/next/assets/tasks-model-ent8f0jZ.js +2 -0
- package/dist/core/web/public/next/assets/{tasks-model-CnYQzgxk.js.map → tasks-model-ent8f0jZ.js.map} +1 -1
- package/dist/core/web/public/next/assets/{terminal-icons-CxUYaWX9.js → terminal-icons-DJTK7QpR.js} +2 -2
- package/dist/core/web/public/next/assets/{terminal-icons-CxUYaWX9.js.map → terminal-icons-DJTK7QpR.js.map} +1 -1
- package/dist/core/web/public/next/assets/{tool-semantics-CTRuLF5j.js → tool-semantics-GX8ScuSH.js} +2 -2
- package/dist/core/web/public/next/assets/{tool-semantics-CTRuLF5j.js.map → tool-semantics-GX8ScuSH.js.map} +1 -1
- package/dist/core/web/public/next/assets/{turn-files-BvB5LLNw.js → turn-files-BPHQzYuH.js} +2 -2
- package/dist/core/web/public/next/assets/{turn-files-BvB5LLNw.js.map → turn-files-BPHQzYuH.js.map} +1 -1
- package/dist/core/web/public/next/assets/types-D5XOdyw8.js +2 -0
- package/dist/core/web/public/next/assets/types-D5XOdyw8.js.map +1 -0
- package/dist/core/web/public/next/assets/{usage-queries-CQOpeE1H.js → usage-queries-Bn8ccqyN.js} +2 -2
- package/dist/core/web/public/next/assets/{usage-queries-CQOpeE1H.js.map → usage-queries-Bn8ccqyN.js.map} +1 -1
- package/dist/core/web/public/next/assets/use-guarded-action-C2HlSzkz.js +2 -0
- package/dist/core/web/public/next/assets/use-guarded-action-C2HlSzkz.js.map +1 -0
- package/dist/core/web/public/next/assets/{use-ticker-hryPXnyp.js → use-ticker-BNc1H4ZC.js} +2 -2
- package/dist/core/web/public/next/assets/{use-ticker-hryPXnyp.js.map → use-ticker-BNc1H4ZC.js.map} +1 -1
- package/dist/core/web/public/next/assets/use-token-gate-B3IJL6bf.js +2 -0
- package/dist/core/web/public/next/assets/use-token-gate-B3IJL6bf.js.map +1 -0
- package/dist/core/web/public/next/assets/{useActivity-Cg_9-7nt.js → useActivity-Bk3XgXjL.js} +2 -2
- package/dist/core/web/public/next/assets/{useActivity-Cg_9-7nt.js.map → useActivity-Bk3XgXjL.js.map} +1 -1
- package/dist/core/web/public/next/assets/useChartWidth-BAtVgn2n.css +1 -0
- package/dist/core/web/public/next/assets/useChartWidth-lZ9PrQn0.js +2 -0
- package/dist/core/web/public/next/assets/useChartWidth-lZ9PrQn0.js.map +1 -0
- package/dist/core/web/public/next/assets/{useSeatsRefresh-4Ov9eT1F.js → useSeatsRefresh-CG4jczeL.js} +2 -2
- package/dist/core/web/public/next/assets/{useSeatsRefresh-4Ov9eT1F.js.map → useSeatsRefresh-CG4jczeL.js.map} +1 -1
- package/dist/core/web/public/next/assets/verse-model-D7NZQFAR.js +2 -0
- package/dist/core/web/public/next/assets/verse-model-D7NZQFAR.js.map +1 -0
- package/dist/core/web/public/next/assets/{verse-queries-BjQoOTwv.js → verse-queries-DvIJcn6Z.js} +2 -2
- package/dist/core/web/public/next/assets/{verse-queries-BjQoOTwv.js.map → verse-queries-DvIJcn6Z.js.map} +1 -1
- package/dist/core/web/public/next/assets/verse-store-DjBWj04q.js +3 -0
- package/dist/core/web/public/next/assets/verse-store-DjBWj04q.js.map +1 -0
- package/dist/core/web/public/next/assets/verse-ui-store-zafyzXKD.js +3 -0
- package/dist/core/web/public/next/assets/{verse-ui-store-KTTKfv-T.js.map → verse-ui-store-zafyzXKD.js.map} +1 -1
- package/dist/core/web/public/next/assets/{viewport-ZB86jdES.js → viewport-BEqGUoTA.js} +2 -2
- package/dist/core/web/public/next/assets/{viewport-ZB86jdES.js.map → viewport-BEqGUoTA.js.map} +1 -1
- package/dist/core/web/public/next/assets/warmup-BR6pxAKs.js +3 -0
- package/dist/core/web/public/next/assets/warmup-BR6pxAKs.js.map +1 -0
- package/dist/core/web/public/next/assets/{workspace-model-4hq_Mv3N.js → workspace-model-D_tWD5Wt.js} +2 -2
- package/dist/core/web/public/next/assets/{workspace-model-4hq_Mv3N.js.map → workspace-model-D_tWD5Wt.js.map} +1 -1
- package/dist/core/web/public/next/index.html +1 -1
- package/dist/release-dependency-inventory.json +1 -1
- package/package.json +1 -1
- package/dist/core/web/public/next/assets/Advanced-CHjxJt2z.css +0 -1
- package/dist/core/web/public/next/assets/Advanced-DM0DYibH.js +0 -2
- package/dist/core/web/public/next/assets/Advanced-DM0DYibH.js.map +0 -1
- package/dist/core/web/public/next/assets/AppsSection-CXxbfPJQ.js +0 -2
- package/dist/core/web/public/next/assets/AppsSection-CXxbfPJQ.js.map +0 -1
- package/dist/core/web/public/next/assets/AreaTrend-gUVXkfhf.js +0 -2
- package/dist/core/web/public/next/assets/AreaTrend-gUVXkfhf.js.map +0 -1
- package/dist/core/web/public/next/assets/AutonomySection-CoMngwNB.js +0 -2
- package/dist/core/web/public/next/assets/BranchBar-C-NovaXr.js +0 -2
- package/dist/core/web/public/next/assets/BranchBar-C-NovaXr.js.map +0 -1
- package/dist/core/web/public/next/assets/BranchBar-C9BpPVLr.css +0 -1
- package/dist/core/web/public/next/assets/BudgetControl-P5lMxHvQ.js +0 -2
- package/dist/core/web/public/next/assets/BudgetControl-P5lMxHvQ.js.map +0 -1
- package/dist/core/web/public/next/assets/BurnDown-4P1LDoCL.js +0 -2
- package/dist/core/web/public/next/assets/BurnDown-4P1LDoCL.js.map +0 -1
- package/dist/core/web/public/next/assets/CapacityStrip-D6Yyg4a1.js +0 -2
- package/dist/core/web/public/next/assets/CapacityStrip-D6Yyg4a1.js.map +0 -1
- package/dist/core/web/public/next/assets/CapacityStrip-wgFP9MUA.css +0 -1
- package/dist/core/web/public/next/assets/ChatSection-B9cdRKUh.js +0 -3
- package/dist/core/web/public/next/assets/CommandSection-BC0uXim_.js +0 -2
- package/dist/core/web/public/next/assets/CommandSection-BC0uXim_.js.map +0 -1
- package/dist/core/web/public/next/assets/Composer-CkcwbQhG.css +0 -1
- package/dist/core/web/public/next/assets/Composer.module-DJMdrqNt.js +0 -2
- package/dist/core/web/public/next/assets/DiffPane-5uLKa0au.js +0 -2
- package/dist/core/web/public/next/assets/DiffPane-5uLKa0au.js.map +0 -1
- package/dist/core/web/public/next/assets/DiffPane-Csboxnn0.css +0 -1
- package/dist/core/web/public/next/assets/DockHost-BGnTgdIb.css +0 -1
- package/dist/core/web/public/next/assets/DockHost-CBV_6Ehi.js +0 -2
- package/dist/core/web/public/next/assets/DockHost-CBV_6Ehi.js.map +0 -1
- package/dist/core/web/public/next/assets/FleetSection-DuJcQCGg.js +0 -3
- package/dist/core/web/public/next/assets/FleetSection-DuJcQCGg.js.map +0 -1
- package/dist/core/web/public/next/assets/FleetSection-w9AjqfTR.css +0 -1
- package/dist/core/web/public/next/assets/Funnel-qX3Hlbce.js +0 -2
- package/dist/core/web/public/next/assets/Funnel-qX3Hlbce.js.map +0 -1
- package/dist/core/web/public/next/assets/GearTray-kVGP1coG.js +0 -2
- package/dist/core/web/public/next/assets/GearTray-kVGP1coG.js.map +0 -1
- package/dist/core/web/public/next/assets/GrowthSection-CYxJ9MH5.js +0 -2
- package/dist/core/web/public/next/assets/GrowthSection-CYxJ9MH5.js.map +0 -1
- package/dist/core/web/public/next/assets/HandoffDialog-DHZo3Ev0.js +0 -2
- package/dist/core/web/public/next/assets/HandoffDialog-DHZo3Ev0.js.map +0 -1
- package/dist/core/web/public/next/assets/MatrixHeatmap-B_JWCjZx.js +0 -2
- package/dist/core/web/public/next/assets/MatrixHeatmap-B_JWCjZx.js.map +0 -1
- package/dist/core/web/public/next/assets/McpSection-1UncWBpW.js +0 -2
- package/dist/core/web/public/next/assets/MessageMarkdown-C4pl9LI5.js +0 -3
- package/dist/core/web/public/next/assets/MessageMarkdown-Czcl3L_Q.css +0 -1
- package/dist/core/web/public/next/assets/MindSection-C3fCw_fD.js +0 -2
- package/dist/core/web/public/next/assets/MindSection-C3fCw_fD.js.map +0 -1
- package/dist/core/web/public/next/assets/MutationTokenDialog-Dn0tzCrh.js +0 -2
- package/dist/core/web/public/next/assets/MutationTokenDialog-Dn0tzCrh.js.map +0 -1
- package/dist/core/web/public/next/assets/NeedsYouDrawer-CuNYJCJ0.js +0 -2
- package/dist/core/web/public/next/assets/NeedsYouDrawer-CuNYJCJ0.js.map +0 -1
- package/dist/core/web/public/next/assets/NeedsYouDrawer-SkhvvdLb.css +0 -1
- package/dist/core/web/public/next/assets/NewChatDialog-B_BsS9pr.js +0 -3
- package/dist/core/web/public/next/assets/OnboardingFlow-CtCPpgoX.js +0 -2
- package/dist/core/web/public/next/assets/PreviewPane-Cx6RRwzM.js +0 -2
- package/dist/core/web/public/next/assets/PreviewPane-Cx6RRwzM.js.map +0 -1
- package/dist/core/web/public/next/assets/PreviewPane-WQJFjSXe.css +0 -1
- package/dist/core/web/public/next/assets/RailStatus-BWs6xevj.js +0 -2
- package/dist/core/web/public/next/assets/RailStatus-BWs6xevj.js.map +0 -1
- package/dist/core/web/public/next/assets/SeatSelector-DaUO-zoK.js +0 -3
- package/dist/core/web/public/next/assets/SessionSearch-B5aQ0BCM.js +0 -2
- package/dist/core/web/public/next/assets/SettingsSection-CuESA30a.js +0 -2
- package/dist/core/web/public/next/assets/SettingsSection-CuESA30a.js.map +0 -1
- package/dist/core/web/public/next/assets/Sidebar-DfD8MNZt.css +0 -1
- package/dist/core/web/public/next/assets/Sidebar-q11dtYZf.js +0 -3
- package/dist/core/web/public/next/assets/Sidebar-q11dtYZf.js.map +0 -1
- package/dist/core/web/public/next/assets/StepBand-B1W7rU70.js +0 -2
- package/dist/core/web/public/next/assets/StepBand-B1W7rU70.js.map +0 -1
- package/dist/core/web/public/next/assets/Swimlane-BR40WVcj.js +0 -2
- package/dist/core/web/public/next/assets/Swimlane-BR40WVcj.js.map +0 -1
- package/dist/core/web/public/next/assets/TerminalPane-CPKUTD9K.js +0 -7
- package/dist/core/web/public/next/assets/TerminalPane-CPKUTD9K.js.map +0 -1
- package/dist/core/web/public/next/assets/TerminalPane-CmE2QL-Q.css +0 -1
- package/dist/core/web/public/next/assets/UniverseConsoleApp-DKqAf-eD.js +0 -2
- package/dist/core/web/public/next/assets/UsageSection-B6fR3cru.js +0 -2
- package/dist/core/web/public/next/assets/UsageSection-B6fR3cru.js.map +0 -1
- package/dist/core/web/public/next/assets/UsageSection-DwgNOYvi.css +0 -1
- package/dist/core/web/public/next/assets/VerseConsoleApp-C0V221WL.css +0 -1
- package/dist/core/web/public/next/assets/VerseConsoleApp-bjARLPwN.js +0 -3
- package/dist/core/web/public/next/assets/VerseConsoleApp-bjARLPwN.js.map +0 -1
- package/dist/core/web/public/next/assets/Workspace-DQXMVDf7.css +0 -1
- package/dist/core/web/public/next/assets/Workspace-DtJb5tiM.js +0 -10
- package/dist/core/web/public/next/assets/Workspace-DtJb5tiM.js.map +0 -1
- package/dist/core/web/public/next/assets/actions-Bszsdpms.js +0 -2
- package/dist/core/web/public/next/assets/actions-Bszsdpms.js.map +0 -1
- package/dist/core/web/public/next/assets/actions-Dug8YIf0.css +0 -1
- package/dist/core/web/public/next/assets/capacity-strip-model-BR539Ci8.js +0 -2
- package/dist/core/web/public/next/assets/capacity-strip-model-BR539Ci8.js.map +0 -1
- package/dist/core/web/public/next/assets/charts-D4NaLaXA.js +0 -2
- package/dist/core/web/public/next/assets/charts-D4NaLaXA.js.map +0 -1
- package/dist/core/web/public/next/assets/colors-B-vHsX1S.js +0 -2
- package/dist/core/web/public/next/assets/colors-B-vHsX1S.js.map +0 -1
- package/dist/core/web/public/next/assets/command-catalog-s3W6Z96D.js +0 -2
- package/dist/core/web/public/next/assets/command-catalog-s3W6Z96D.js.map +0 -1
- package/dist/core/web/public/next/assets/context-model-Bto3LZn2.js +0 -2
- package/dist/core/web/public/next/assets/context-model-Bto3LZn2.js.map +0 -1
- package/dist/core/web/public/next/assets/context-queries-DVdSLrYy.js +0 -2
- package/dist/core/web/public/next/assets/context-queries-DVdSLrYy.js.map +0 -1
- package/dist/core/web/public/next/assets/context.module-B_k02HNU.js +0 -2
- package/dist/core/web/public/next/assets/context.module-B_k02HNU.js.map +0 -1
- package/dist/core/web/public/next/assets/dock-icons-oG8d7mNZ.js +0 -2
- package/dist/core/web/public/next/assets/dock-icons-oG8d7mNZ.js.map +0 -1
- package/dist/core/web/public/next/assets/dock-panes-BNTLvX7G.js +0 -2
- package/dist/core/web/public/next/assets/fleet-model-ClBbQ0Uj.js +0 -2
- package/dist/core/web/public/next/assets/fleet-model-ClBbQ0Uj.js.map +0 -1
- package/dist/core/web/public/next/assets/format-BNYWGJat.js +0 -2
- package/dist/core/web/public/next/assets/format-BNYWGJat.js.map +0 -1
- package/dist/core/web/public/next/assets/global-DVgMguLr.js +0 -2
- package/dist/core/web/public/next/assets/global-DVgMguLr.js.map +0 -1
- package/dist/core/web/public/next/assets/live-model-Dm6ITHvX.js +0 -2
- package/dist/core/web/public/next/assets/live-model-Dm6ITHvX.js.map +0 -1
- package/dist/core/web/public/next/assets/mind-B-lYfSUJ.css +0 -1
- package/dist/core/web/public/next/assets/mind.module-BzhAlg3e.js +0 -2
- package/dist/core/web/public/next/assets/mind.module-BzhAlg3e.js.map +0 -1
- package/dist/core/web/public/next/assets/mutations-C6E10SPX.js +0 -2
- package/dist/core/web/public/next/assets/primitives-DQvYikjm.js +0 -1
- package/dist/core/web/public/next/assets/seat-subscription-CrWOV4Qc.js +0 -2
- package/dist/core/web/public/next/assets/seat-subscription-CrWOV4Qc.js.map +0 -1
- package/dist/core/web/public/next/assets/slots-W8gkmdfE.js +0 -3
- package/dist/core/web/public/next/assets/surface-data-ZtCegYMQ.js +0 -2
- package/dist/core/web/public/next/assets/surface-data-ZtCegYMQ.js.map +0 -1
- package/dist/core/web/public/next/assets/tasks-model-CnYQzgxk.js +0 -2
- package/dist/core/web/public/next/assets/types-Dypu22dp.js +0 -3
- package/dist/core/web/public/next/assets/types-Dypu22dp.js.map +0 -1
- package/dist/core/web/public/next/assets/use-guarded-action-CO_aAsed.js +0 -2
- package/dist/core/web/public/next/assets/use-guarded-action-CO_aAsed.js.map +0 -1
- package/dist/core/web/public/next/assets/use-token-gate-DYA2-ALC.js +0 -2
- package/dist/core/web/public/next/assets/use-token-gate-DYA2-ALC.js.map +0 -1
- package/dist/core/web/public/next/assets/useChartWidth-B8E5mJgM.css +0 -1
- package/dist/core/web/public/next/assets/useChartWidth-MEXvDxDL.js +0 -2
- package/dist/core/web/public/next/assets/useChartWidth-MEXvDxDL.js.map +0 -1
- package/dist/core/web/public/next/assets/verse-model-E98kXngO.js +0 -2
- package/dist/core/web/public/next/assets/verse-model-E98kXngO.js.map +0 -1
- package/dist/core/web/public/next/assets/verse-store-BgKx_YGs.js +0 -2
- package/dist/core/web/public/next/assets/verse-store-BgKx_YGs.js.map +0 -1
- package/dist/core/web/public/next/assets/verse-ui-store-KTTKfv-T.js +0 -3
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"fleet-model-ClBbQ0Uj.js","names":[],"sources":["../../../../../../src/web-ui/routes/verse/autonomy/fleet-model.ts"],"sourcesContent":["/**\n * routes/verse/autonomy/fleet-model.ts — every derivation the local-fleet\n * surfaces make, as pure functions. No React, no I/O, so the rules that decide\n * what the operator is told are unit-testable without a DOM.\n *\n * The one rule this file exists to enforce:\n *\n * **A concurrency number the runtime will not honour is a lie, and the UI\n * must print the truth instead of the configuration.**\n *\n * That is not a hypothetical. Measured on this machine (docs/LOCAL-FLEET.md),\n * four concurrent Qwen3.8 requests to Ollama returned at 3.7 / 7.6 / 11.4 /\n * 15.2s — a four-second stagger, which is a queue. The same weights on\n * llama-server returned at 8.6 / 8.9 / 9.0 / 9.0s: continuous batching across\n * four slots sharing one 27 GB copy. Ollama's log calls it a refusal\n * (`model architecture does not currently support parallel requests`) and\n * `OLLAMA_NUM_PARALLEL` does not move it.\n *\n * So `runtimeCapacity()` never reports `slotsTotal` as capacity unless the\n * runtime says it batches. When a runtime that serializes is configured with\n * four slots, the capacity is ONE and `overstated` is true, and the panel says\n * so in words. A queue that looks like slowness is the exact failure this\n * surface is here to prevent.\n *\n * The bodies these functions narrow come from routes that are landing in\n * parallel (see fleet-contract.ts), so everything is projected STRUCTURALLY\n * rather than cast: a field-name drift degrades to an honest \"unknown\" at\n * render time instead of throwing.\n */\nimport { UNKNOWN, formatDuration } from './format.js';\nimport type {\n FleetAgent,\n FleetAgentState,\n FleetSnapshot,\n LocalOnlyPolicy,\n LocalOnlyRefusal,\n LocalOnlySource,\n ServingRuntimeKind,\n ServingRuntimeParallelism,\n ServingRuntimeSnapshot,\n ServingRuntimeState,\n} from './fleet-contract.js';\n\n// ---------------------------------------------------------------------------\n// Structural narrowing\n// ---------------------------------------------------------------------------\n\nfunction record(value: unknown): Record<string, unknown> | null {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : null;\n}\n\nfunction str(source: Record<string, unknown>, key: string): string | null {\n const v = source[key];\n return typeof v === 'string' && v.length > 0 ? v : null;\n}\n\nfunction num(source: Record<string, unknown>, key: string): number | null {\n const v = source[key];\n return typeof v === 'number' && Number.isFinite(v) ? v : null;\n}\n\n/** A count that is meaningful at zero — so 0 stays 0 and absent stays null. */\nfunction count(source: Record<string, unknown>, key: string): number | null {\n const v = num(source, key);\n return v === null ? null : Math.max(0, Math.round(v));\n}\n\nfunction bool(source: Record<string, unknown>, key: string): boolean | null {\n const v = source[key];\n return typeof v === 'boolean' ? v : null;\n}\n\nfunction strings(source: Record<string, unknown>, key: string): string[] {\n const v = source[key];\n return Array.isArray(v) ? v.filter((entry): entry is string => typeof entry === 'string') : [];\n}\n\nconst RUNTIME_KINDS: readonly ServingRuntimeKind[] = ['llama-server', 'ollama', 'vllm', 'unknown'];\nconst RUNTIME_STATES: readonly ServingRuntimeState[] = [\n 'running',\n 'starting',\n 'stopping',\n 'stopped',\n 'unknown',\n];\nconst AGENT_STATES: readonly FleetAgentState[] = ['queued', 'running', 'finishing'];\nconst LOCAL_ONLY_SOURCES: readonly LocalOnlySource[] = [\n 'off',\n 'config',\n 'env',\n 'config+env',\n 'latch',\n 'unknown',\n];\n\nfunction oneOf<T extends string>(\n source: Record<string, unknown>,\n key: string,\n allowed: readonly T[],\n fallback: T,\n): T {\n const v = source[key];\n return typeof v === 'string' && (allowed as readonly string[]).includes(v) ? (v as T) : fallback;\n}\n\nfunction projectParallelism(raw: unknown): ServingRuntimeParallelism {\n const r = record(raw);\n if (!r) return { capable: null, refusal: null, slots: null };\n return { capable: bool(r, 'capable'), refusal: str(r, 'refusal'), slots: count(r, 'slots') };\n}\n\n/**\n * Owner R's `LlamaRuntimeSnapshot` (`src/core/local-runtime/llama/types.ts`)\n * spells the same facts differently, and the route that serves it is not\n * written yet — so rather than betting on one envelope, this reads both.\n *\n * The mapping, with the two judgement calls stated:\n *\n * - `state: 'up' | 'down' | 'loading' | 'unknown'` → running / stopped /\n * starting / unknown.\n * - `contextPerSlot` wins over `contextTotal`. `-c 65536 --parallel 4` gives\n * each slot 16k, and the number an agent's turn actually runs in is the\n * per-slot one. Quoting the total would overstate every agent's context\n * fourfold — the same class of error as quoting slots as concurrency.\n * - `managed` → `supervised`. R is explicit that an unmanaged server may be\n * adopted but is \"never killed on a guess\", which is exactly the condition\n * under which this panel must not offer a stop button.\n */\nconst LLAMA_STATE: Readonly<Record<string, ServingRuntimeState>> = {\n up: 'running',\n down: 'stopped',\n loading: 'starting',\n unknown: 'unknown',\n};\n\nfunction projectLlamaShape(r: Record<string, unknown>): ServingRuntimeSnapshot | null {\n const rawState = r['state'];\n if (typeof rawState !== 'string' || !(rawState in LLAMA_STATE)) return null;\n const slots = record(r['slots']);\n const host = str(r, 'host');\n const port = count(r, 'port');\n const configured = slots ? count(slots, 'configured') : null;\n const busy = slots ? count(slots, 'busy') : null;\n // `slots.source` is R's own trust marker: 'unknown' means \"do not trust a\n // number\", so a slot count carrying it is dropped rather than quoted.\n const slotSource = slots ? str(slots, 'source') : null;\n const trusted = slotSource !== null && slotSource !== 'unknown';\n const total = trusted ? configured : null;\n\n const refusal = str(r, 'parallelRefusal');\n const identity = str(r, 'runtimeKind');\n const contextPerSlot = count(r, 'contextPerSlot');\n const contextTotal = count(r, 'contextTotal');\n\n return {\n // IDENTIFIED where the producer says so. A base URL pointed at Ollama's\n // /v1 answers /v1/models — enough to keep the engine routable — while\n // 404ing /health and /props, and asserting 'llama-server' for it would\n // hide the one fact that matters: that runtime serialises this model.\n kind: identity === 'ollama' ? 'ollama' : identity === 'unknown' ? 'unknown' : 'llama-server',\n state: LLAMA_STATE[rawState] ?? 'unknown',\n endpoint: host !== null && port !== null ? `${host}:${port}` : (str(r, 'origin') ?? null),\n // A digest is not a name. `modelName` falls back to `model_path`'s basename\n // at the producer, which for a blob out of Ollama's content-addressed store\n // is `sha256-…`: it identifies the machine's storage layout and names\n // nothing. Null renders as \"unknown\", which is the honest answer.\n model: humaneModelName(str(r, 'modelName')) ?? humaneModelName(str(r, 'model')),\n slotsTotal: total,\n slotsBusy: trusted ? busy : null,\n // PER-SLOT, or nothing. `-c 65536 --parallel 4` gives each agent 16k, so\n // the `-c` total is usable only divided by a slot count we trust.\n contextTokens:\n contextPerSlot ??\n (total !== null && total > 0 && contextTotal !== null && contextTotal > 0\n ? Math.floor(contextTotal / total)\n : null),\n startedAt: str(r, 'startedAt'),\n parallel: {\n // llama-server's slots ARE its continuous-batching slots: a count read\n // back off the live server (`/props.total_slots`, `/slots`) is a count\n // of requests it will run at the same time, which is what the\n // measurement in docs/LOCAL-FLEET.md showed. An untrusted or absent\n // reading claims nothing — `null`, not `true`.\n capable: refusal !== null ? false : trusted && total !== null ? true : null,\n // The runtime's own words when it refuses, carried verbatim as evidence.\n refusal,\n slots: total,\n },\n reason: str(r, 'lastError'),\n supervised: bool(r, 'managed') ?? false,\n sampledAt: str(r, 'checkedAt'),\n };\n}\n\n/** A displayable model name, or null for an absent or digest-shaped one. */\nfunction humaneModelName(raw: string | null): string | null {\n if (raw === null || raw.length === 0) return null;\n const base = raw.split('/').pop() ?? raw;\n if (base.length === 0) return null;\n if (/^sha256[-:][0-9a-f]{16,}$/i.test(base)) return null;\n return base;\n}\n\nexport function projectServingRuntime(raw: unknown): ServingRuntimeSnapshot | null {\n const r = record(raw);\n if (!r) return null;\n // A body already in this surface's own shape wins; otherwise try owner R's.\n if (!('kind' in r) && 'slots' in r && 'state' in r) {\n const llama = projectLlamaShape(r);\n if (llama !== null) return llama;\n }\n return {\n kind: oneOf(r, 'kind', RUNTIME_KINDS, 'unknown'),\n state: oneOf(r, 'state', RUNTIME_STATES, 'unknown'),\n endpoint: str(r, 'endpoint'),\n model: str(r, 'model'),\n slotsTotal: count(r, 'slotsTotal'),\n slotsBusy: count(r, 'slotsBusy'),\n contextTokens: count(r, 'contextTokens'),\n startedAt: str(r, 'startedAt'),\n parallel: projectParallelism(r['parallel']),\n reason: str(r, 'reason'),\n // A runtime this server cannot supervise must not be offered buttons, so\n // an unreported flag reads as NOT supervised. The conservative default is\n // \"no controls\" rather than controls that 404 on the first click.\n supervised: bool(r, 'supervised') ?? false,\n sampledAt: str(r, 'sampledAt'),\n };\n}\n\nfunction projectAgent(raw: unknown): FleetAgent | null {\n const r = record(raw);\n if (!r) return null;\n const id = str(r, 'id');\n if (id === null) return null;\n return {\n id,\n task: str(r, 'task'),\n repo: str(r, 'repo'),\n engine: str(r, 'engine'),\n model: str(r, 'model'),\n state: oneOf(r, 'state', AGENT_STATES, 'running'),\n startedAt: str(r, 'startedAt'),\n slot: count(r, 'slot'),\n };\n}\n\nexport function projectFleet(raw: unknown): FleetSnapshot | null {\n const r = record(raw);\n if (!r) return null;\n const rawAgents = Array.isArray(r['agents']) ? (r['agents'] as unknown[]) : [];\n return {\n agents: rawAgents.map(projectAgent).filter((a): a is FleetAgent => a !== null),\n queueDepth: count(r, 'queueDepth'),\n slotsTotal: count(r, 'slotsTotal'),\n slotsBusy: count(r, 'slotsBusy'),\n notes: strings(r, 'notes'),\n sampledAt: str(r, 'sampledAt'),\n };\n}\n\nfunction projectRefusal(raw: unknown): LocalOnlyRefusal | null {\n const r = record(raw);\n if (!r) return null;\n const engine = str(r, 'engine');\n if (engine === null) return null;\n return { engine, reason: str(r, 'reason') ?? 'The dispatcher refuses this engine while local-only is on.' };\n}\n\n/**\n * `source` is carried through as the resolver's own value — it is no longer\n * flattened onto a smaller union here.\n *\n * The flattening this replaced mapped `'latch'` onto `'config'`, which tells\n * an operator the mode is stored in the config and editable when in fact the\n * process has pinned it for its own lifetime. Only the separate `mutable`\n * flag kept that from reaching the screen as a working switch that silently\n * does nothing. An unrecognised value lands on `'unknown'`, which makes no\n * claim at all.\n */\n/** Sources that pin the mode to this server process, whatever the config says. */\nconst PINNED_SOURCES: ReadonlySet<LocalOnlySource> = new Set<LocalOnlySource>([\n 'env',\n 'config+env',\n 'latch',\n 'unknown',\n]);\n\nexport function projectLocalOnly(raw: unknown): LocalOnlyPolicy | null {\n const r = record(raw);\n if (!r) return null;\n const rawRefusals = Array.isArray(r['refuses']) ? (r['refuses'] as unknown[]) : [];\n const source = oneOf(r, 'source', LOCAL_ONLY_SOURCES, 'unknown');\n return {\n // An unreported `enabled` is NOT \"on\": claiming cloud is unreachable when\n // the server never said so is the one error this policy cannot afford.\n enabled: bool(r, 'enabled') ?? false,\n source,\n refuses: rawRefusals.map(projectRefusal).filter((x): x is LocalOnlyRefusal => x !== null),\n // A policy pinned by the server's own process — an env var, or a latch\n // this process already took — is never editable from here, whatever the\n // body claims: a write would be silently overridden on the next read.\n // `'unknown'` is treated as pinned too, because offering a switch for a\n // policy this client cannot identify is the more expensive mistake.\n mutable: PINNED_SOURCES.has(source) ? false : (bool(r, 'mutable') ?? false),\n detail: str(r, 'detail'),\n sampledAt: str(r, 'sampledAt') ?? str(r, 'checkedAt'),\n };\n}\n\n// ---------------------------------------------------------------------------\n// The capacity verdict — the point of this whole file\n// ---------------------------------------------------------------------------\n\nexport type CapacityVerdict = 'batched' | 'serialized' | 'unknown' | 'offline';\n\nexport interface RuntimeCapacity {\n verdict: CapacityVerdict;\n /**\n * How many agents can genuinely be in flight at once. `null` means it\n * cannot be known from this reading — which is NOT the same as 0 and must\n * never render as a number.\n */\n effectiveConcurrency: number | null;\n /** What the runtime was configured with, honoured or not. */\n configuredSlots: number | null;\n /**\n * True when the configured slot count is larger than what the runtime will\n * actually run in parallel. While this is true, the configured number must\n * not be presented as capacity anywhere on the screen.\n */\n overstated: boolean;\n headline: string;\n detail: string;\n tone: 'success' | 'warning' | 'danger' | 'neutral' | 'unknown';\n}\n\n/** Human name for a runtime, used in sentences. */\nexport function runtimeLabel(kind: ServingRuntimeKind): string {\n switch (kind) {\n case 'llama-server':\n return 'llama-server';\n case 'ollama':\n return 'Ollama';\n case 'vllm':\n return 'vLLM';\n default:\n return 'the serving runtime';\n }\n}\n\nexport function runtimeCapacity(runtime: ServingRuntimeSnapshot | null): RuntimeCapacity {\n if (runtime === null) {\n return {\n verdict: 'unknown',\n effectiveConcurrency: null,\n configuredSlots: null,\n overstated: false,\n headline: 'No serving runtime reported',\n detail:\n 'Nothing answered for the serving runtime, so how many agents can run at once is unknown. That is a missing reading, not an idle machine.',\n tone: 'unknown',\n };\n }\n\n const configured = runtime.slotsTotal ?? runtime.parallel.slots;\n const name = runtimeLabel(runtime.kind);\n\n if (runtime.state !== 'running') {\n return {\n verdict: 'offline',\n effectiveConcurrency: null,\n configuredSlots: configured,\n overstated: false,\n headline: runtime.state === 'stopped' ? 'Not serving' : `Serving runtime is ${runtime.state}`,\n detail:\n runtime.state === 'stopped'\n ? `${name} is not running, so no local agent can take a turn. Start it before dispatching a fleet.`\n : `${name} is ${runtime.state}; capacity is not settled until it reports running.`,\n tone: runtime.state === 'stopped' ? 'neutral' : 'warning',\n };\n }\n\n if (runtime.parallel.capable === false) {\n // The measured case. The configured number is real — it is simply not a\n // concurrency, and printing it as one is the failure mode this exists for.\n const overstated = configured !== null && configured > 1;\n return {\n verdict: 'serialized',\n effectiveConcurrency: 1,\n configuredSlots: configured,\n overstated,\n headline: `${name} serializes requests`,\n detail: overstated\n ? `${name} has ${configured} slots configured but runs one request at a time for this model, so the real capacity is one agent. The other ${configured - 1} would queue, and a queue looks like slowness rather than a wait.`\n : `${name} runs one request at a time for this model. A second agent waits for the first to finish.`,\n tone: 'danger',\n };\n }\n\n if (runtime.parallel.capable === null) {\n return {\n verdict: 'unknown',\n effectiveConcurrency: null,\n configuredSlots: configured,\n overstated: false,\n headline: 'Batching not confirmed',\n detail: `${name} is running${\n configured === null ? '' : ` with ${configured} slots configured`\n }, but it has not reported whether it batches concurrent requests for this model. Until it does, this panel will not quote a concurrency it cannot stand behind.`,\n tone: 'unknown',\n };\n }\n\n if (configured === null) {\n return {\n verdict: 'batched',\n effectiveConcurrency: null,\n configuredSlots: null,\n overstated: false,\n headline: `${name} batches concurrent requests`,\n detail: `${name} batches, but reported no slot count, so the ceiling on concurrent agents is unknown.`,\n tone: 'unknown',\n };\n }\n\n return {\n verdict: 'batched',\n effectiveConcurrency: configured,\n configuredSlots: configured,\n overstated: false,\n headline: `${configured} agent${configured === 1 ? '' : 's'} in parallel`,\n detail: `${name} batches across ${configured} slot${\n configured === 1 ? '' : 's'\n } sharing one copy of the weights, so ${configured} agent${\n configured === 1 ? '' : 's'\n } can be mid-turn at once.`,\n tone: 'success',\n };\n}\n\n/**\n * The measured comparison, kept as data so the panel renders it as evidence\n * rather than as an assertion in prose. Source: docs/LOCAL-FLEET.md, four\n * concurrent requests against the same Qwen3.8 GGUF on this machine.\n */\nexport interface ConcurrencyEvidenceRow {\n runtime: string;\n /** Per-request completion times, in seconds, in the order they returned. */\n perRequestSeconds: readonly number[];\n wallSeconds: number;\n verdict: string;\n}\n\nexport const CONCURRENCY_EVIDENCE: readonly ConcurrencyEvidenceRow[] = [\n {\n runtime: 'Ollama',\n perRequestSeconds: [3.7, 7.6, 11.4, 15.2],\n wallSeconds: 15.2,\n verdict: 'staggered — the requests queued',\n },\n {\n runtime: 'llama-server',\n perRequestSeconds: [8.6, 8.9, 9.0, 9.0],\n wallSeconds: 9.1,\n verdict: 'together — the requests batched',\n },\n] as const;\n\n// ---------------------------------------------------------------------------\n// Utilisation\n// ---------------------------------------------------------------------------\n\nexport interface SlotUtilisation {\n busy: number | null;\n total: number | null;\n /** 0–100, or null when either half is unknown. */\n percent: number | null;\n /** Every slot is taken: the next turn waits. */\n saturated: boolean;\n /** One line, safe to render on its own. */\n label: string;\n}\n\n/**\n * Slot occupancy.\n *\n * `total` is the EFFECTIVE concurrency, not the configured slot count — on a\n * runtime that serializes, one busy slot out of four configured is full, and\n * drawing it as 25% would tell the operator there is room when there is none.\n */\nexport function slotUtilisation(busy: number | null, effectiveTotal: number | null): SlotUtilisation {\n if (busy === null || effectiveTotal === null || effectiveTotal <= 0) {\n return {\n busy,\n total: effectiveTotal,\n percent: null,\n saturated: false,\n label:\n busy === null\n ? 'Slot occupancy is not reported.'\n : `${busy} in flight; the number of slots is not known, so occupancy cannot be drawn.`,\n };\n }\n const clamped = Math.min(busy, effectiveTotal);\n const percent = Math.round((clamped / effectiveTotal) * 100);\n const saturated = busy >= effectiveTotal;\n return {\n busy,\n total: effectiveTotal,\n percent,\n saturated,\n label: saturated\n ? `All ${effectiveTotal} slot${effectiveTotal === 1 ? '' : 's'} busy — new turns wait for one to free up.`\n : `${busy} of ${effectiveTotal} slot${effectiveTotal === 1 ? '' : 's'} busy.`,\n };\n}\n\n/**\n * What the operator is actually waiting on.\n *\n * Saturation alone is not bad news — a fleet at full occupancy is a fleet\n * working. Saturation WITH a queue is the thing to say plainly, because from\n * the outside it is indistinguishable from the machine being slow.\n */\nexport interface FleetPressure {\n state: 'idle' | 'working' | 'saturated' | 'queued' | 'unknown';\n headline: string;\n detail: string;\n tone: 'success' | 'running' | 'warning' | 'neutral' | 'unknown';\n}\n\nexport function fleetPressure(\n fleet: FleetSnapshot | null,\n utilisation: SlotUtilisation,\n capacity: RuntimeCapacity,\n): FleetPressure {\n if (fleet === null) {\n return {\n state: 'unknown',\n headline: 'Fleet not reported',\n detail:\n 'No fleet snapshot answered, so what is in flight cannot be listed. That is a missing reading, not an empty fleet.',\n tone: 'unknown',\n };\n }\n\n const running = fleet.agents.filter((a) => a.state !== 'queued').length;\n const queued = fleet.queueDepth ?? fleet.agents.filter((a) => a.state === 'queued').length;\n\n if (queued > 0) {\n return {\n state: 'queued',\n headline: `${queued} waiting for a slot`,\n detail:\n capacity.verdict === 'serialized'\n ? `${running} turn${running === 1 ? '' : 's'} in flight and ${queued} queued. This runtime serializes, so the queue drains one at a time — the wait is a wait, not slow inference.`\n : `${running} turn${running === 1 ? '' : 's'} in flight and ${queued} queued behind ${\n utilisation.total === null ? 'the available slots' : `${utilisation.total} slots`\n }. Queued turns have not started; their wall time is queueing, not work.`,\n tone: 'warning',\n };\n }\n\n if (running === 0) {\n return {\n state: 'idle',\n headline: 'Nothing in flight',\n detail: 'No agent is mid-turn. The fleet is idle, not stuck.',\n tone: 'neutral',\n };\n }\n\n if (utilisation.saturated) {\n return {\n state: 'saturated',\n headline: `All slots busy · ${running} in flight`,\n detail: `Every slot is working and nothing is queued behind them. The next turn will wait, but none is waiting yet.`,\n tone: 'running',\n };\n }\n\n return {\n state: 'working',\n headline: `${running} in flight`,\n detail: utilisation.label,\n tone: 'running',\n };\n}\n\n// ---------------------------------------------------------------------------\n// Local-only impact\n// ---------------------------------------------------------------------------\n\nexport interface SeatLike {\n id: string;\n engine: string;\n label: string;\n}\n\nexport interface LocalOnlyImpact {\n /** Seats that become unreachable while the policy is on. */\n blocked: SeatLike[];\n /** Seats unaffected — the local ones. */\n allowed: SeatLike[];\n /** The named refusal per blocked engine, from the server when it sent one. */\n refusalFor: (engine: string) => string;\n /** One line summarising the blast radius against the CURRENT seat roster. */\n summary: string;\n}\n\nconst DEFAULT_REFUSAL = 'Refused: local-only is on and this engine is not local.';\n\n/**\n * What local-only would block, measured against the seats that actually exist\n * on this machine right now.\n *\n * Deliberately computed from the live roster rather than from a fixed list of\n * cloud engines: the sentence \"this would block 3 of your 5 seats\" is only\n * trustworthy if the 5 is the operator's own number.\n */\nexport function localOnlyImpact(\n policy: LocalOnlyPolicy | null,\n seats: readonly SeatLike[],\n): LocalOnlyImpact {\n const blocked = seats.filter((s) => s.engine !== 'local');\n const allowed = seats.filter((s) => s.engine === 'local');\n const byEngine = new Map<string, string>();\n for (const refusal of policy?.refuses ?? []) byEngine.set(refusal.engine, refusal.reason);\n\n const summary =\n seats.length === 0\n ? 'No seats are configured, so there is nothing for this policy to block yet.'\n : blocked.length === 0\n ? `Every one of your ${seats.length} seat${seats.length === 1 ? '' : 's'} is local, so this policy would block nothing today.`\n : `${blocked.length} of ${seats.length} seat${seats.length === 1 ? '' : 's'} would become unreachable; ${allowed.length} local seat${allowed.length === 1 ? '' : 's'} would keep working.`;\n\n return {\n blocked,\n allowed,\n refusalFor: (engine: string) => byEngine.get(engine) ?? DEFAULT_REFUSAL,\n summary,\n };\n}\n\n/** Where the policy came from, as a sentence an operator can act on. */\nexport function localOnlySourceNote(policy: LocalOnlyPolicy | null): string | null {\n if (policy === null) return null;\n // The policy's own sentence, when it wrote one. It knows things this client\n // does not — e.g. that an env var tried and failed to clear a persisted\n // local-only — and a template would erase exactly that.\n if (policy.detail !== null) return policy.detail;\n switch (policy.source) {\n case 'env':\n case 'config+env':\n return 'This policy is pinned by an environment variable on the server process, so it cannot be changed from here — restart the server without it to take the switch back.';\n case 'latch':\n return 'This process already observed a persisted local-only and has pinned it for its own lifetime, so turning it off takes effect for new dispatches but not for the seams that latched. Restart the server to clear it.';\n case 'config':\n return 'Stored in the hub config and applied on the next dispatch.';\n case 'off':\n return 'Not configured; cloud engines are reachable.';\n default:\n return 'The server did not say where this setting came from.';\n }\n}\n\n// ---------------------------------------------------------------------------\n// Small formatters\n// ---------------------------------------------------------------------------\n\n/** \"4m 12s\" since an ISO instant, against a live clock. Absent stays absent. */\nexport function elapsedSince(iso: string | null, now: number): string {\n if (!iso) return UNKNOWN;\n const at = Date.parse(iso);\n if (Number.isNaN(at)) return UNKNOWN;\n return formatDuration(Math.max(0, now - at));\n}\n\n/** Uptime for the runtime header. Same rules, different words. */\nexport function formatUptime(startedAt: string | null, now: number): string {\n if (!startedAt) return UNKNOWN;\n const at = Date.parse(startedAt);\n if (Number.isNaN(at)) return UNKNOWN;\n return `up ${formatDuration(Math.max(0, now - at))}`;\n}\n\n/**\n * Context windows are quoted in powers of two everywhere the operator has\n * seen them (65536 is \"64k\"), so the divisor is 1024. Matching\n * `usage/local-model.ts`'s `formatContext` on purpose: two spellings of the\n * same number across two panels is how a UI starts to feel untrustworthy.\n */\nexport function formatContextTokens(tokens: number | null): string {\n if (tokens === null || !Number.isFinite(tokens)) return UNKNOWN;\n if (tokens >= 1024) return `${Math.round(tokens / 1024)}k`;\n return String(tokens);\n}\n\n/** Agent rows, ordered so the reader's first question is answered first. */\nexport function orderAgents(agents: readonly FleetAgent[]): FleetAgent[] {\n const rank: Record<FleetAgentState, number> = { running: 0, finishing: 1, queued: 2 };\n return [...agents].sort((a, b) => {\n const byState = rank[a.state] - rank[b.state];\n if (byState !== 0) return byState;\n // Longest-running first: the one most likely to be stuck is the one an\n // operator scanning this table is looking for.\n const at = a.startedAt ? Date.parse(a.startedAt) : Number.NaN;\n const bt = b.startedAt ? Date.parse(b.startedAt) : Number.NaN;\n if (Number.isNaN(at) && Number.isNaN(bt)) return a.id.localeCompare(b.id);\n if (Number.isNaN(at)) return 1;\n if (Number.isNaN(bt)) return -1;\n return at - bt;\n });\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
function e(e){return typeof e!=`number`||!Number.isFinite(e)?`—`:`$${e.toFixed(2)}`}function t(e){return typeof e!=`number`||!Number.isFinite(e)?`—`:String(e)}function n(e){let t=Math.max(0,Math.round(e/1e3));if(t<60)return`${t}s`;let n=Math.floor(t/60),r=t%60;return n<60?`${n}m ${String(r).padStart(2,`0`)}s`:`${Math.floor(n/60)}h ${String(n%60).padStart(2,`0`)}m`}function r(e){if(!e)return`—`;let t=Date.parse(e);if(Number.isNaN(t))return`—`;let n=Math.max(0,Math.round((Date.now()-t)/1e3));if(n<60)return`${n}s`;let r=Math.round(n/60);if(r<60)return`${r}m`;let i=Math.round(r/60);return i<24?`${i}h`:`${Math.round(i/24)}d`}function i(e){let t=r(e);return t===`—`?`—`:`${t} ago`}function a(e){if(!e)return`—`;let t=Date.parse(e);return Number.isNaN(t)?`—`:new Date(t).toLocaleTimeString(void 0,{hour:`2-digit`,minute:`2-digit`,second:`2-digit`})}function o(e=new Date){return`${e.getFullYear()}-${String(e.getMonth()+1).padStart(2,`0`)}-${String(e.getDate()).padStart(2,`0`)}`}function s(e,t=new Date){return e===o(t)||e===t.toISOString().slice(0,10)}function c(e,t=new Date){if(!e)return`—`;let n=Date.parse(e);if(Number.isNaN(n))return`—`;let r=new Date(n),i=r.toLocaleTimeString(void 0,{hour:`2-digit`,minute:`2-digit`,second:`2-digit`});return o(r)===o(t)?i:`${r.toLocaleDateString(void 0,{month:`short`,day:`numeric`})} ${i}`}function l(e){return typeof e!=`number`||!Number.isFinite(e)||e<=0?`—`:`every ${n(e).replace(/ 00[ms]$/,``)}`}function u(t,n,r,i=new Date){let a=typeof t==`number`&&Number.isFinite(t)?Math.max(0,t):null,o=typeof n==`number`&&Number.isFinite(n)?n:null,c=typeof r==`string`&&!s(r,i);if(o===0)return{state:`stopped`,percent:100,label:`${e(a)} spent today · budget $0.00`,note:`Daily budget is 0 — the loop is stopped, not unlimited.`};if(c)return{state:`unknown`,percent:null,label:`— spent today · budget ${e(o)}`,note:`Nothing has been recorded today — the last ledger day is ${r}.`};if(o===null||a===null)return{state:`unknown`,percent:null,label:o===null?`${e(a)} spent today · budget —`:`— spent today · budget ${e(o)}`,note:o===null?`Daily budget is not configured.`:`Today's spend is not observable right now.`};let l=a/o*100,u=Math.min(100,Math.round(l)),d=l>=100?`over`:l>=70?`warn`:`ok`;return{state:d,percent:u,label:`${e(a)} of ${e(o)} today · ${u}%`,note:d===`over`?`Daily budget reached — the loop idles until the date rolls over.`:d===`warn`?`${e(o-a)} left before the loop idles today.`:`${e(o-a)} left today.`}}function d(e,t){if(!e)return null;let n=Date.parse(e);return Number.isNaN(n)||typeof t!=`number`||!Number.isFinite(t)||t<=0?null:n+t}function f(e,t){if(e===null)return`—`;let r=e-t;return r>1e3?`in ${n(r)}`:r>-1e3?`due now`:`overdue by ${n(-r)}`}function p(e){if(!e)return{label:`no tick recorded`,tone:`unknown`};switch(e){case`ok`:return{label:`ok`,tone:`success`};case`kill-switch`:return{label:`refused — emergency stop engaged`,tone:`danger`};case`budget-exhausted`:return{label:`stopped — daily budget spent`,tone:`warning`};case`no-enrolled-repos`:return{label:`nothing to do — no repos enrolled`,tone:`warning`};case`no-backlog`:return{label:`nothing to do — backlog empty`,tone:`neutral`};case`dry-run`:return{label:`dry run`,tone:`neutral`};default:return{label:e,tone:`neutral`}}}export{a,l as c,e as d,s as f,r as i,i as l,f as n,t as o,d as p,p as r,n as s,u as t,c as u};
|
|
2
|
-
//# sourceMappingURL=format-BNYWGJat.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"format-BNYWGJat.js","names":[],"sources":["../../../../../../src/web-ui/routes/verse/autonomy/format.ts"],"sourcesContent":["/**\n * routes/verse/autonomy/format.ts — the small pure functions the cockpit's\n * numerals go through. Kept framework-free so they unit-test without a DOM,\n * and kept in ONE place so \"unknown\" never renders as 0 in one panel and as\n * an em-dash in the next.\n *\n * The rule every function here obeys: absent is not zero. A null spend is\n * \"—\", a null budget is \"not configured\", and a budget of exactly 0 is\n * \"stopped\" — which is the single most misread number in the whole product.\n */\n\n/** What we render when a value genuinely is not known. */\nexport const UNKNOWN = '—';\n\nexport function formatUsd(value: number | null | undefined): string {\n if (typeof value !== 'number' || !Number.isFinite(value)) return UNKNOWN;\n return `$${value.toFixed(2)}`;\n}\n\nexport function formatCount(value: number | null | undefined): string {\n if (typeof value !== 'number' || !Number.isFinite(value)) return UNKNOWN;\n return String(value);\n}\n\n/** \"4m 12s\" / \"1h 06m\" / \"12s\" — for durations and countdowns alike. */\nexport function formatDuration(ms: number): string {\n const total = Math.max(0, Math.round(ms / 1000));\n if (total < 60) return `${total}s`;\n const minutes = Math.floor(total / 60);\n const seconds = total % 60;\n if (minutes < 60) return `${minutes}m ${String(seconds).padStart(2, '0')}s`;\n const hours = Math.floor(minutes / 60);\n return `${hours}h ${String(minutes % 60).padStart(2, '0')}m`;\n}\n\n/** Compact age for dense table cells: \"42s\", \"7m\", \"3h\", \"5d\". */\nexport function formatAge(iso: string | null | undefined): string {\n if (!iso) return UNKNOWN;\n const ms = Date.parse(iso);\n if (Number.isNaN(ms)) return UNKNOWN;\n const seconds = Math.max(0, Math.round((Date.now() - ms) / 1000));\n if (seconds < 60) return `${seconds}s`;\n const minutes = Math.round(seconds / 60);\n if (minutes < 60) return `${minutes}m`;\n const hours = Math.round(minutes / 60);\n if (hours < 24) return `${hours}h`;\n return `${Math.round(hours / 24)}d`;\n}\n\nexport function formatRelative(iso: string | null | undefined): string {\n const age = formatAge(iso);\n return age === UNKNOWN ? UNKNOWN : `${age} ago`;\n}\n\n/** Local wall-clock time, for the audit table's second column. */\nexport function formatClock(iso: string | null | undefined): string {\n if (!iso) return UNKNOWN;\n const ms = Date.parse(iso);\n if (Number.isNaN(ms)) return UNKNOWN;\n return new Date(ms).toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', second: '2-digit' });\n}\n\n/** The viewer's local calendar day as `YYYY-MM-DD` — NOT the UTC slice. */\nexport function localDateKey(at: Date = new Date()): string {\n const y = at.getFullYear();\n const m = String(at.getMonth() + 1).padStart(2, '0');\n const d = String(at.getDate()).padStart(2, '0');\n return `${y}-${m}-${d}`;\n}\n\n/**\n * Is `todayDate` still the current ledger day?\n *\n * The daemon stamps it in UTC (`resetDayIfNeeded` in core/daemon/state.ts is\n * `new Date().toISOString().slice(0, 10)`) but the operator reads the screen\n * in local time, and the two disagree for part of every day at any offset but\n * zero. Comparing against the local day alone would announce \"nothing has been\n * recorded today\" every evening west of UTC — a false alarm on the meter whose\n * whole purpose is to be trustworthy.\n *\n * So either spelling counts as current. That is deliberately conservative: it\n * can call a day current for a few hours after the UTC rollover, and it will\n * never call a genuinely current day stale. The case this exists for is a\n * ledger day weeks old, which neither spelling matches.\n */\nexport function isCurrentLedgerDay(todayDate: string, now: Date = new Date()): boolean {\n return todayDate === localDateKey(now) || todayDate === now.toISOString().slice(0, 10);\n}\n\n/**\n * Time-of-day for a row from today; date + time for anything older.\n *\n * `formatClock` alone is only honest inside a single-day window, and the\n * tables that use it are not one: the audit trail defaults to 200 rows and\n * offers 500, which on a real machine spans days. Two rows reading \"03:12:44\"\n * could be last night and last week, and the only date was in a hover title\n * that a touch viewport never fires.\n */\nexport function formatStamp(iso: string | null | undefined, now: Date = new Date()): string {\n if (!iso) return UNKNOWN;\n const ms = Date.parse(iso);\n if (Number.isNaN(ms)) return UNKNOWN;\n const at = new Date(ms);\n const clock = at.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', second: '2-digit' });\n if (localDateKey(at) === localDateKey(now)) return clock;\n const day = at.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });\n return `${day} ${clock}`;\n}\n\n/** Human tick interval, e.g. \"every 15m\". */\nexport function formatInterval(intervalMs: number | null | undefined): string {\n if (typeof intervalMs !== 'number' || !Number.isFinite(intervalMs) || intervalMs <= 0) return UNKNOWN;\n // A configured interval is a round number far more often than not; \"every\n // 15m\" reads better than \"every 15m 00s\" and loses nothing when it isn't.\n return `every ${formatDuration(intervalMs).replace(/ 00[ms]$/, '')}`;\n}\n\nexport type BudgetState = 'stopped' | 'unknown' | 'ok' | 'warn' | 'over';\n\nexport interface BudgetMeter {\n state: BudgetState;\n /** 0-100, clamped. Null when there is nothing honest to draw. */\n percent: number | null;\n /** One line: \"$4.50 of $25.00 today · 18%\". */\n label: string;\n /** The plain-language verdict shown next to the meter. */\n note: string;\n}\n\nconst BUDGET_WARN_PERCENT = 70;\n\n/**\n * Today's spend against the configured daily cap, as ONE line.\n *\n * A cap of exactly 0 is the contract's guard rail: it means the loop is\n * stopped, and it must never render as an empty (i.e. \"plenty left\") meter.\n *\n * `todayDate` is the ledger day `spend.todayUsd` was counted for. It is NOT\n * optional decoration: the daemon writes the figure once per day and leaves it\n * there, so a machine that last ticked three weeks ago answers\n * `{todayUsd: 0, todayDate: \"2026-09-01\"}`. Rendering that as \"$0.00 of $50.00\n * today\" is a fabricated statement about today on the one number an operator\n * checks before walking away from an autonomous loop. Pass `null` only when\n * the source genuinely has no date to offer (the legacy daemon observation).\n */\nexport function budgetMeter(\n spentUsd: number | null | undefined,\n capUsd: number | null | undefined,\n todayDate?: string | null,\n now: Date = new Date(),\n): BudgetMeter {\n const spend = typeof spentUsd === 'number' && Number.isFinite(spentUsd) ? Math.max(0, spentUsd) : null;\n const cap = typeof capUsd === 'number' && Number.isFinite(capUsd) ? capUsd : null;\n // A cap of 0 is a statement about configuration, not about the ledger, so\n // it still reads true on a stale day and is checked first.\n const stale = typeof todayDate === 'string' && !isCurrentLedgerDay(todayDate, now);\n\n if (cap === 0) {\n return {\n state: 'stopped',\n percent: 100,\n label: `${formatUsd(spend)} spent today · budget $0.00`,\n note: 'Daily budget is 0 — the loop is stopped, not unlimited.',\n };\n }\n if (stale) {\n return {\n state: 'unknown',\n percent: null,\n label: `${UNKNOWN} spent today · budget ${formatUsd(cap)}`,\n note: `Nothing has been recorded today — the last ledger day is ${todayDate}.`,\n };\n }\n if (cap === null || spend === null) {\n return {\n state: 'unknown',\n percent: null,\n label: cap === null ? `${formatUsd(spend)} spent today · budget ${UNKNOWN}` : `${UNKNOWN} spent today · budget ${formatUsd(cap)}`,\n note: cap === null ? 'Daily budget is not configured.' : \"Today's spend is not observable right now.\",\n };\n }\n\n const raw = (spend / cap) * 100;\n const percent = Math.min(100, Math.round(raw));\n const state: BudgetState = raw >= 100 ? 'over' : raw >= BUDGET_WARN_PERCENT ? 'warn' : 'ok';\n return {\n state,\n percent,\n label: `${formatUsd(spend)} of ${formatUsd(cap)} today · ${percent}%`,\n note:\n state === 'over'\n ? 'Daily budget reached — the loop idles until the date rolls over.'\n : state === 'warn'\n ? `${formatUsd(cap - spend)} left before the loop idles today.`\n : `${formatUsd(cap - spend)} left today.`,\n };\n}\n\n/** Epoch ms of the next expected tick, or null when it cannot be derived. */\nexport function nextTickAt(lastTickAt: string | null | undefined, intervalMs: number | null | undefined): number | null {\n if (!lastTickAt) return null;\n const last = Date.parse(lastTickAt);\n if (Number.isNaN(last)) return null;\n if (typeof intervalMs !== 'number' || !Number.isFinite(intervalMs) || intervalMs <= 0) return null;\n return last + intervalMs;\n}\n\n/** \"in 4m 12s\" / \"due now\" / \"overdue by 2m 03s\". */\nexport function countdownLabel(nextAt: number | null, now: number): string {\n if (nextAt === null) return UNKNOWN;\n const delta = nextAt - now;\n if (delta > 1000) return `in ${formatDuration(delta)}`;\n if (delta > -1000) return 'due now';\n return `overdue by ${formatDuration(-delta)}`;\n}\n\n/** Last tick outcome, phrased the way a person would say it out loud. */\nexport function describeTickOutcome(reason: string | null | undefined): { label: string; tone: 'success' | 'warning' | 'danger' | 'neutral' | 'unknown' } {\n if (!reason) return { label: 'no tick recorded', tone: 'unknown' };\n switch (reason) {\n case 'ok':\n return { label: 'ok', tone: 'success' };\n case 'kill-switch':\n return { label: 'refused — emergency stop engaged', tone: 'danger' };\n case 'budget-exhausted':\n return { label: 'stopped — daily budget spent', tone: 'warning' };\n case 'no-enrolled-repos':\n return { label: 'nothing to do — no repos enrolled', tone: 'warning' };\n case 'no-backlog':\n return { label: 'nothing to do — backlog empty', tone: 'neutral' };\n case 'dry-run':\n return { label: 'dry run', tone: 'neutral' };\n default:\n return { label: reason, tone: 'neutral' };\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{a as e,n as t,r as n,s as r}from"./index-afDA8cwW.js";var i=r(e(),1),a={data:void 0,error:void 0,status:`idle`,updatedAt:null},o=new Map,s=0,c=[],l=0,u=0;function d(){--s;let e=c.shift();e&&e()}function ee(e){let t=()=>{let t=u;s+=1,s>l&&(l=s);let n=()=>{t===u&&d()},r;try{r=e()}catch(e){return n(),Promise.reject(e instanceof Error?e:Error(String(e)))}return r.then(e=>(n(),e),e=>{throw n(),e})};return s<4?t():new Promise((e,n)=>{c.push(()=>{t().then(e,n)})})}function te(){u+=1,s=0,l=0;for(let e of c.splice(0))e()}function f(e){let t=o.get(e);return t||(t={snapshot:a,inFlight:null,fetcher:null,subscribers:new Set,issued:0,issuedAt:0,applied:0},o.set(e,t)),t}function p(e,t){e.snapshot={...e.snapshot,...t}}function m(e){let t=o.get(e);if(t)for(let e of t.subscribers)e()}function h(e,t){if(Object.is(e,t))return!0;if(typeof e!=typeof t||e===null||t===null||typeof e!=`object`||Array.isArray(e)!==Array.isArray(t))return!1;if(Array.isArray(e)&&Array.isArray(t))return e.length===t.length&&e.every((e,n)=>h(e,t[n]));let n=e,r=t,i=Object.keys(n);return i.length===Object.keys(r).length&&i.every(e=>Object.prototype.hasOwnProperty.call(r,e)&&h(n[e],r[e]))}function g(e,t){let n=f(e);return n.subscribers.add(t),()=>{n.subscribers.delete(t)}}function _(e){return f(e).snapshot}function ne(e,t){return e.snapshot.updatedAt===null||e.snapshot.status===`error`?!1:Date.now()-e.snapshot.updatedAt<t}function re(e,t){return e.inFlight===null?!1:t.joinWindowMs===void 0||Date.now()-e.issuedAt<t.joinWindowMs}function ie(e,t,n){let r=f(e);if(r.fetcher=t,!n.force){if(re(r,n))return r.inFlight;if(n.maxAgeMs!==void 0&&ne(r,n.maxAgeMs))return Promise.resolve()}let i=r.issued+=1;r.issuedAt=Date.now(),p(r,{status:r.snapshot.data===void 0?`loading`:`refreshing`}),m(e);let a=ee(t).then(e=>{if(i<r.applied)return;r.applied=i;let t=r.snapshot.data!==void 0&&h(r.snapshot.data,e);p(r,{data:t?r.snapshot.data:e,error:void 0,status:`success`,updatedAt:Date.now()})}).catch(e=>{i<r.applied||(r.applied=i,p(r,{error:e instanceof Error?e:Error(String(e)),status:`error`}))}).finally(()=>{r.issued===i&&(r.inFlight=null),m(e)});return r.inFlight=a,a}function ae(e,t){return ie(e,t,{})}function oe(e,t,n){return ie(e,t,{maxAgeMs:n})}function se(e,t,n=!1){return ie(e,t,n?{force:!0}:{joinWindowMs:250})}function ce(e){let t=o.get(e);t&&t.fetcher&&ae(e,t.fetcher)}function v(e){for(let t of o.keys())t.startsWith(e)&&ce(t)}function le(e,t){t.snapshot=a,t.fetcher=null,t.inFlight=null,t.issued+=1,t.applied=t.issued,m(e)}function ue(){te();for(let[e,t]of[...o]){if(t.subscribers.size===0){o.delete(e);continue}le(e,t)}}var de=4e5,fe=2e4,pe=13e3,me=.9,he=36e5,ge={claude:25e3,codex:15e3,grok:2e4,local:15e3},_e=25e3;function ve(e){return e?ge[e]??25e3:_e}ge.local+8e3+fe+pe;function y(e){return typeof e==`number`&&Number.isFinite(e)&&e>0?Math.floor(e):null}function ye(e,t,n=null){let r=Math.min(e,y(n)??e),i=Math.min(y(t)??2e4,fe);return Math.max(0,r-i-pe)}function be(e){return Math.floor(e*me)}var xe={"claude-opus-5.5":`claude-opus-5-5`};function Se(e){return xe[e]??e}function b(e,t){if(!e)return null;if(t===`expansive`)return e.expansive??null;let n=y(e.contextWindow);return n===null?null:{contextWindow:n,autoCompactAt:y(e.autoCompactAt??null)}}function x(e){let t=b(e,`standard`),n=b(e,`expansive`);return n?!t||(n.autoCompactAt??n.contextWindow)>(t.autoCompactAt??t.contextWindow):!1}function Ce(e,t){if(t===`expansive`)return null;let n=y(e?.contextWindow??null);return n!==null&&n>4e5&&x(e)?de:null}function we(e){let t=y(e.runtimeWindow);if(t===null)return e.budget?.autoCompactAt??null;if(e.budget&&e.budget.contextWindow===t)return e.budget.autoCompactAt;switch(e.engine){case`claude`:case`local`:return ye(t,e.maxOutputTokens??null,e.autocompactWindow??null);case`codex`:return be(Math.round(t*100/95));default:{let n=e.budget;return!n||n.autoCompactAt===null||n.contextWindow<=0?null:Math.floor(t*(n.autoCompactAt/n.contextWindow))}}}var Te=.8,Ee=.95;function De(e){let t=Math.max(0,Math.floor(e.contextTokens||0)),n=y(e.contextWindow),r=y(e.autoCompactAt??null),i=n===null?null:t/n,a=r===null?null:t/r,o=a??i,s=e.contextTokensExact!==!1,c=`unknown`;return!s&&o!==null&&o>=1?c=`unknown`:n!==null&&t>n?c=`over`:o!==null&&(c=o>=.95?`danger`:o>=.8?`warn`:`ok`),{tokens:t,window:n,autoCompactAt:r,ofWindow:i,ofCompaction:a,untilCompaction:r===null?null:Math.max(0,r-t),tone:c,exact:s}}function Oe(e){let t=e.inputTokens+e.cacheReadTokens+e.cacheCreationTokens;return t>0?e.cacheReadTokens/t:null}function ke(e){return Math.ceil(Math.max(0,e)/4)}function Ae(e,t,n=_e){let r=b(t,`standard`);if(!r)return null;let i=Math.max(0,e)+Math.max(0,n),a=r.autoCompactAt??r.contextWindow;if(i<=a*.6)return`fits`;if(i<=a)return`tight`;let o=b(t,`expansive`);return o&&i<=(o.autoCompactAt??o.contextWindow)?`expansive`:`split`}function S(e){return e>=1e6?`${(e/1e6).toFixed(e%1e6==0?0:1)}M`:e>=1e3?`${Math.round(e/1e3)}k`:String(e)}function je(e){let t=De(e.usage),n=[],r=`none`,i=e=>{(e===`urge`||e===`suggest`&&r===`none`)&&(r=e)};t.exact&&(t.tone===`over`?(i(`urge`),n.push(`Context (${S(t.tokens)}) is past the ${S(t.window??0)} window.`)):t.ofCompaction!==null&&t.ofCompaction>=.8&&(i(`suggest`),n.push(`About ${S(t.untilCompaction??0)} tokens left before the CLI auto-compacts.`)));let a=e.compactionCount??0;if(a>=2&&(i(`suggest`),n.push(`Compacted ${a} times — early turns now survive only as summaries.`)),e.lastActivityAt){let r=e.now-Date.parse(e.lastActivityAt);t.exact&&Number.isFinite(r)&&r>=36e5&&t.tokens>=1e5&&(i(`suggest`),n.push(`Idle over an hour: the prompt cache has likely expired, so the next turn re-reads ~${S(t.tokens)} tokens at full cost.`))}return{level:r,reasons:n}}function Me(e){if((e.session.contextMode??`standard`)!==`standard`||!x(e.option))return{suggest:!1,reason:null};let t=e.session.compactionCount??0;return t>=2?{suggest:!0,reason:`This session has compacted ${t} times; expansive mode keeps more of it in view.`}:typeof e.workingSetTokens==`number`&&Ae(e.workingSetTokens,e.option)===`expansive`?{suggest:!0,reason:`The reachable code (~${S(e.workingSetTokens)} tokens) only fits the expansive budget.`}:{suggest:!1,reason:null}}function Ne(e){return e===`claude`||e===`codex`}var C=`ashlr.verse.drafts.v1`,w=`ashlr.verse.sent.v1`,Pe=40,T=65536;function E(e,t){let n={};try{let r=localStorage.getItem(e),i=r?JSON.parse(r):null;if(i&&typeof i==`object`&&!Array.isArray(i))for(let[e,r]of Object.entries(i)){let i=t(r);i!==null&&(n[e]=i)}}catch{}return n}function Fe(e,t){let n=Object.keys(t);for(let e of n.slice(0,Math.max(0,n.length-Pe)))delete t[e];try{return localStorage.setItem(e,JSON.stringify(t)),!0}catch{return!1}}var Ie=e=>typeof e==`string`&&e.length>0?e.slice(0,T):null,Le=e=>{if(!Array.isArray(e))return null;let t=e.filter(e=>typeof e==`string`&&e.length>0).slice(-25).map(e=>e.slice(0,T));return t.length>0?t:null},D=new Map;function Re(e){if(!e)return``;let t=D.get(e);return t===void 0?E(C,Ie)[e]??``:t}function ze(e,t){if(!e)return;let n=t.trim().length>0?t.slice(0,T):``,r=E(C,Ie);if(delete r[e],n&&(r[e]=n),D.delete(e),!Fe(C,r)){D.set(e,n);for(let e of[...D.keys()].slice(0,Math.max(0,D.size-Pe)))D.delete(e)}}function Be(e){return e?E(w,Le)[e]??[]:[]}function Ve(e,t){if(!e||t.trim().length===0)return;let n=E(w,Le),r=n[e]??[];delete n[e],n[e]=(r[r.length-1]===t?r:[...r,t.slice(0,T)]).slice(-25),Fe(w,n)}function He(e){if(e){D.delete(e);for(let[t,n]of[[C,Ie],[w,Le]]){let r=E(t,n);e in r&&(delete r[e],Fe(t,r))}}}function Ue(){D.clear();for(let e of[C,w])try{localStorage.removeItem(e)}catch{}}function We(e){let t=e.trim();return t.length===0?0:Math.max(1,Math.ceil(t.length/4))}function Ge(e,t){let n=We(e);if(n===0)return null;let r=typeof t.contextWindow==`number`&&t.contextWindow>0?t.contextWindow:null;if(r===null)return null;let i=De({contextTokens:(typeof t.contextTokens==`number`&&Number.isFinite(t.contextTokens)?Math.max(0,t.contextTokens):0)+n,contextWindow:r,autoCompactAt:t.autoCompactAt??null,contextTokensExact:t.exact});return i.tone===`ok`||i.tone===`unknown`?null:{draftTokens:n,projectedTokens:i.tokens,projectedPercent:Math.min(999,Math.round((i.ofWindow??0)*100)),autoCompactAt:i.autoCompactAt,pastCompaction:i.ofCompaction!==null&&i.ofCompaction>=1,tone:i.tone,exact:i.exact}}var Ke=`ashlr.readClientProof.v1`,qe=/^[a-f0-9]{64}$/,Je=12e5;function Ye(){let e=new Uint8Array(32);return crypto.getRandomValues(e),Array.from(e,e=>e.toString(16).padStart(2,`0`)).join(``)}function Xe(){try{let e=sessionStorage.getItem(Ke);if(e&&qe.test(e))return e}catch{}let e=Ye();try{sessionStorage.setItem(Ke,e)}catch{}return e}var Ze=Xe(),O=null,k=null,Qe=!1,A=null,j=null,M=null,N={phase:`checking`,checked:!1,mutationTokenHeldUntil:null},$e=new Set;function et(){for(let e of $e)e()}function P(e){N={...N,...e},et()}function F(e){return $e.add(e),()=>$e.delete(e)}function I(){return N}function tt(){return Ze}async function L(e){let t=e.trim();if(!/^[a-f0-9]{64}$/.test(t))throw Error(`That does not look like a read token — expected 64 hex characters.`);let n=await fetch(`/api/session`,{method:`POST`,credentials:`same-origin`,headers:{"x-ashlr-token":t,"x-ashlr-read-client":Ze}});if(n.status===204){A=t,P({phase:`authenticated`,checked:!0});return}throw n.status===401?Error(`Server rejected that token. Double-check the read token printed by this server at startup.`):Error(`Unexpected response establishing session (HTTP ${n.status}).`)}async function nt(){try{await fetch(`/api/session`,{method:`DELETE`,credentials:`same-origin`,headers:{"x-ashlr-read-client":Ze}})}catch{}A=null,M=null,R()}function R(){B(),ue(),Ue(),P({phase:`unauthenticated`,checked:!0})}function z(){if(N.phase===`authenticated`){if(A){rt();return}R()}}function rt(){if(j)return j;let e=A;return e?(j=(async()=>{try{return await L(e),v(``),!0}catch{return A=null,M=null,R(),!1}finally{j=null}})(),j):Promise.resolve(!1)}function it(e){P({phase:e?`authenticated`:`unauthenticated`,checked:!0})}function at(){let e=window.__ASHLR_TOKENS__;if(e&&typeof e==`object`){try{delete window.__ASHLR_TOKENS__}catch{window.__ASHLR_TOKENS__=void 0}typeof e.readToken==`string`&&(M={readToken:e.readToken,...typeof e.token==`string`?{token:e.token}:{}})}return M?{...M}:null}async function ot(){let e=at();if(!e)return!1;try{await L(e.readToken)}catch{return M=null,!1}return e.token&&/^[a-f0-9]{64}$/.test(e.token.trim())&&ut(e.token.trim(),{host:!0}),!0}function st(){return O}function ct(){return O!==null}function lt(){k&&clearTimeout(k),k=setTimeout(()=>{Qe&&O!==null?dt():B()},Je)}function ut(e,t={}){O=e,Qe=t.host===!0,P({mutationTokenHeldUntil:Date.now()+Je}),lt()}function dt(){O!==null&&(P({mutationTokenHeldUntil:Date.now()+Je}),lt())}function B(){O=null,Qe=!1,k&&=(clearTimeout(k),null),P({mutationTokenHeldUntil:null})}var ft=[`runs`,`swarms`,`inbox`,`daemon`,`daemon-observation`,`fleet-activity-ping`,`fleet-activity-observation`,`snapshot`,`session-expired`],pt={runs:[`runs`],swarms:[`swarms`],inbox:[`inbox`],daemon:[`daemon`,`control-snapshot`,`verse-control`,`verse-usage-control`,`verse-caps`],"daemon-observation":[`daemon`,`control-snapshot`,`verse-control`,`verse-usage-control`,`verse-caps`],"fleet-activity-ping":[`fleet-activity`,`fleet`],"fleet-activity-observation":[`fleet-activity`,`fleet`],snapshot:[`dashboard-snapshot`,`agent-os-snapshot`],"session-expired":[]},mt=new Map,V=null,H=null,U=0;function ht(){return Math.min(1e3*2**U,15e3)}function gt(){V&&=(V.close(),null),H&&=(clearTimeout(H),null)}function _t(){H||=setTimeout(()=>{H=null,U+=1,vt()},ht())}function vt(){if(n()){gt();return}if(I().phase!==`authenticated`)return;gt();let e=`/api/events?client=${encodeURIComponent(tt())}`,t=new EventSource(e,{withCredentials:!0});V=t,t.onopen=()=>{U=0};for(let e of ft)t.addEventListener(e,t=>{let n=t,r;try{r=JSON.parse(n.data)}catch{r=void 0}for(let t of pt[e])ce(t);e===`inbox`&&(v(`inbox-list:`),v(`proposal-detail-`)),e===`session-expired`&&z();let i=mt.get(e);if(i)for(let e of i)e(r)});t.onerror=()=>{t.close(),V===t&&(V=null),I().phase===`authenticated`&&_t()}}F(()=>{I().phase===`authenticated`?(U=0,vt()):gt()});var yt=`ashlr.theme.v1`;function bt(e){let t=document.documentElement;e===`system`?t.removeAttribute(`data-theme`):t.setAttribute(`data-theme`,e)}function xt(){try{let e=localStorage.getItem(yt);if(e===`light`||e===`dark`||e===`system`)return e}catch{}return`system`}var W=xt();bt(W);var St=new Set;function Ct(){for(let e of St)e()}function wt(e){return St.add(e),()=>St.delete(e)}function G(){return W}function K(e){W=e,bt(W);try{localStorage.setItem(yt,W)}catch{}Ct()}function Tt(){K(W===`system`?`light`:W===`light`?`dark`:`system`)}var Et=`ashlr.verse.appearance.v1`,Dt=30,Ot=78,q={h:245,s:72,l:58},kt=[{id:`indigo`,label:`Indigo`,h:245,s:72,l:58},{id:`blue`,label:`Blue`,h:212,s:80,l:50},{id:`cyan`,label:`Cyan`,h:190,s:80,l:40},{id:`teal`,label:`Teal`,h:168,s:70,l:38},{id:`green`,label:`Green`,h:145,s:60,l:38},{id:`amber`,label:`Amber`,h:38,s:80,l:42},{id:`rose`,label:`Rose`,h:348,s:72,l:50},{id:`violet`,label:`Violet`,h:278,s:66,l:55}];function At(){try{return typeof window<`u`&&typeof window.matchMedia==`function`&&window.matchMedia(`(prefers-reduced-motion: reduce)`).matches}catch{return!1}}function jt(){return{theme:`system`,accentH:q.h,accentS:q.s,accentL:q.l,density:`comfortable`,uiScale:`default`,displayFont:`grotesk`,radius:`default`,motion:`system`,reduceMotion:At()}}function Mt(e){return e===`system`||e===`reduce`||e===`full`}function Nt(e){return e===`reduce`||e!==`full`&&At()}var Pt=(e,t,n)=>Math.min(n,Math.max(t,e));function Ft(e){return e===`comfortable`||e===`compact`}function It(e){return e==="default"||e===`large`||e===`xlarge`}function Lt(e){return e===`grotesk`||e===`ui`||e===`mono`}function Rt(e){return e===`sharp`||e==="default"||e===`soft`}function zt(e,t){let n={...e,...t},r=t.reduceMotion,i=Mt(t.motion)?t.motion:typeof r==`boolean`?r?`reduce`:`full`:Mt(e.motion)?e.motion:`system`;return{theme:n.theme===`light`||n.theme===`dark`?n.theme:`system`,accentH:Number.isFinite(n.accentH)?(Math.round(n.accentH)%360+360)%360:q.h,accentS:Number.isFinite(n.accentS)?Pt(Math.round(n.accentS),0,100):q.s,accentL:Number.isFinite(n.accentL)?Pt(Math.round(n.accentL),Dt,Ot):q.l,density:Ft(n.density)?n.density:`comfortable`,uiScale:It(n.uiScale)?n.uiScale:`default`,displayFont:Lt(n.displayFont)?n.displayFont:`grotesk`,radius:Rt(n.radius)?n.radius:`default`,motion:i,reduceMotion:Nt(i)}}function Bt(){let e=jt(),t={};try{let e=localStorage.getItem(Et);if(e){let n=JSON.parse(e);n&&typeof n==`object`&&(t=n)}}catch{}let{reduceMotion:n,...r}=t;return zt(e,{...r,theme:G()})}function Vt(e){try{let{theme:t,reduceMotion:n,...r}=e;localStorage.setItem(Et,JSON.stringify(r))}catch{}}var Ht={grotesk:null,ui:`var(--font-ui)`,mono:`var(--font-mono)`};function Ut(e){if(typeof document>`u`)return;let t=document.documentElement;t.style.setProperty(`--accent-h`,String(e.accentH)),t.style.setProperty(`--accent-s`,`${e.accentS}%`),t.style.setProperty(`--accent-l`,`${e.accentL}%`);let n=Ht[e.displayFont];n===null?t.style.removeProperty(`--font-display`):t.style.setProperty(`--font-display`,n),t.setAttribute(`data-density`,e.density),t.setAttribute(`data-ui-scale`,e.uiScale),t.setAttribute(`data-radius`,e.radius),e.motion===`system`?t.removeAttribute(`data-motion`):t.setAttribute(`data-motion`,e.motion)}var J=Bt();Ut(J);var Y=new Set;function Wt(){for(let e of Y)e()}wt(()=>{J.theme!==G()&&(J={...J,theme:G()},Wt())});function Gt(e){return Y.add(e),()=>Y.delete(e)}function Kt(){return J}function qt(e){let t=zt(J,e),n=t.theme!==J.theme;J=t,Ut(J),Vt(J),n&&K(J.theme),Wt()}function Jt(){try{localStorage.removeItem(Et)}catch{}J=jt(),Ut(J),K(`system`),Wt()}function Yt(e=J){let t=jt();return e.theme===t.theme&&e.accentH===t.accentH&&e.accentS===t.accentS&&e.accentL===t.accentL&&e.density===t.density&&e.uiScale===t.uiScale&&e.displayFont===t.displayFont&&e.radius===t.radius&&e.motion===t.motion}function Xt(e=J){return kt.find(t=>t.h===e.accentH&&t.s===e.accentS&&t.l===e.accentL)??null}var Zt={min:Dt,max:Ot};function Qt(e,t){let n=t?.freshMs??1e4,r=(0,i.useSyncExternalStore)((0,i.useCallback)(t=>g(e.key,t),[e.key]),()=>_(e.key),()=>_(e.key));return(0,i.useEffect)(()=>{oe(e.key,()=>e.fetch(),n)},[e.key,n]),r}function $t(e){return(0,i.useCallback)(()=>{se(e.key,()=>e.fetch())},[e])}function en(e){return(0,i.useCallback)(()=>{se(e.key,()=>e.fetch(),!0)},[e])}function tn(){return(0,i.useSyncExternalStore)(F,()=>I().phase,()=>I().phase)}function nn(){let e=(0,i.useSyncExternalStore)(F,()=>I().mutationTokenHeldUntil,()=>I().mutationTokenHeldUntil);return{hasHold:ct(),token:st(),heldUntil:e,setToken:ut,touch:dt,clear:B}}function rn(){return{theme:(0,i.useSyncExternalStore)(wt,G,G),set:K,cycle:Tt}}function an(){return{appearance:(0,i.useSyncExternalStore)(Gt,Kt,Kt),set:qt,reset:Jt}}var X=class extends Error{status;path;detail;code;constructor(e,t,n,r=null,i=null){super(e),this.status=t,this.path=n,this.detail=r,this.code=i,this.name=`ApiError`}},on=class extends X{constructor(e){super(`This server was started without --allow-dispatch, so mutations are disabled.`,404,e),this.name=`DispatchDisabledError`}};async function sn(e,t){let n=await fetch(e,{method:`GET`,credentials:`same-origin`,headers:{"x-ashlr-read-client":tt()},signal:t});if(n.status===401)throw z(),new X(`Read session expired.`,401,e);if(!n.ok)throw new X(`GET ${e} failed (HTTP ${n.status}).`,n.status,e);return await n.json()}async function cn(e,t,n,r){let i=await fetch(e,{method:`POST`,credentials:`same-origin`,headers:{"Content-Type":`application/json`,"x-ashlr-token":n},body:JSON.stringify(t??{}),signal:r});if(i.status===401)throw new X(`Mutation token was rejected.`,401,e);if(!i.ok){let{detail:t,code:n}=await ln(i);throw i.status===404&&n===null?new on(e):new X(`POST ${e} failed (HTTP ${i.status})${t?`: ${t}`:``}.`,i.status,e,t||null,n)}if(i.status!==204)return await i.json()}async function ln(e){try{let t=await e.json();return{detail:typeof t.error==`string`&&t.error?t.error:typeof t.note==`string`?t.note:``,code:typeof t.code==`string`&&t.code?t.code:null}}catch{return{detail:``,code:null}}}var Z={grid:`_grid_x7p2y_1`,card:`_card_x7p2y_10`,title:`_title_x7p2y_19`,stat:`_stat_x7p2y_25`,caption:`_caption_x7p2y_31`,line:`_line_x7p2y_37`,row:`_row_x7p2y_42`,cell:`_cell_x7p2y_51`},Q=t();function un({width:e=`100%`}){return(0,Q.jsx)(`div`,{className:`skeleton ${Z.line}`,style:{width:e}})}function dn(){return(0,Q.jsxs)(`div`,{className:Z.card,children:[(0,Q.jsx)(`div`,{className:`skeleton ${Z.title}`}),(0,Q.jsx)(`div`,{className:`skeleton ${Z.stat}`}),(0,Q.jsx)(`div`,{className:`skeleton ${Z.caption}`})]})}function fn(){return(0,Q.jsxs)(`div`,{className:Z.row,children:[(0,Q.jsx)(`div`,{className:`skeleton ${Z.cell}`,style:{width:`18%`}}),(0,Q.jsx)(`div`,{className:`skeleton ${Z.cell}`,style:{width:`38%`}}),(0,Q.jsx)(`div`,{className:`skeleton ${Z.cell}`,style:{width:`14%`}}),(0,Q.jsx)(`div`,{className:`skeleton ${Z.cell}`,style:{width:`10%`}})]})}function pn({count:e=4}){return(0,Q.jsx)(`div`,{className:Z.grid,children:Array.from({length:e},(e,t)=>(0,Q.jsx)(dn,{},t))})}var mn={skipLink:`_skipLink_1uply_1`};function hn(){return(0,Q.jsx)(`button`,{type:`button`,className:mn.skipLink,onClick:()=>{let e=document.getElementById(`main-content`);e?.focus(),e?.scrollIntoView({block:`start`})},children:`Skip to content`})}var $={wrap:`_wrap_14qw9_1`,card:`_card_14qw9_10`,badge:`_badge_14qw9_19`,heading:`_heading_14qw9_24`,body:`_body_14qw9_31`,code:`_code_14qw9_38`,example:`_example_14qw9_49`,form:`_form_14qw9_56`,label:`_label_14qw9_63`,input:`_input_14qw9_68`,error:`_error_14qw9_82`,submit:`_submit_14qw9_87`,optional:`_optional_14qw9_107`},gn=/^[a-f0-9]{64}$/;function _n({onAuthenticated:e,heading:t=`Connect to ashlr serve`,command:n=`ashlr serve`,subject:r=`This dashboard`,mutationField:a=!1}){let[o,s]=(0,i.useState)(``),[c,l]=(0,i.useState)(``),[u,d]=(0,i.useState)(null),[ee,te]=(0,i.useState)(!1),f=(0,i.useRef)(null),p=(0,i.useId)(),m=(0,i.useId)(),h=(0,i.useId)(),g=(0,i.useRef)(e);g.current=e,(0,i.useEffect)(()=>{let e=!1;return ot().then(t=>{t&&!e&&g.current?.()}),()=>{e=!0}},[]);async function _(t){t.preventDefault(),d(null);let n=c.trim();if(a&&n&&!gn.test(n)){d(`That does not look like a mutation token — expected 64 hex characters (leave it empty to unlock later).`);return}te(!0);try{await L(o),s(``),a&&n&&ut(n),l(``),e?.()}catch(e){d(e instanceof Error?e.message:`Could not establish a session.`),f.current?.select()}finally{te(!1)}}return(0,Q.jsx)(`div`,{className:$.wrap,children:(0,Q.jsxs)(`div`,{className:$.card,children:[(0,Q.jsx)(`div`,{className:$.badge,"aria-hidden":`true`,children:`⛺`}),(0,Q.jsx)(`h1`,{className:$.heading,children:t}),(0,Q.jsxs)(`p`,{className:$.body,children:[r,` only ever talks to the ashlr server running on this machine (`,(0,Q.jsx)(`code`,{children:`127.0.0.1`}),`, never a remote host). To read data, paste the`,` `,(0,Q.jsx)(`strong`,{children:`read token`}),` printed in the terminal where you ran:`]}),(0,Q.jsx)(`pre`,{className:$.code,children:n}),(0,Q.jsxs)(`p`,{className:$.body,children:[`It looks like a 64-character hex string, printed once at startup — something like`,` `,(0,Q.jsx)(`code`,{className:$.example,children:`a1b2c3…`}),`. This dialog never stores it: it is exchanged once for a short-lived session cookie, then discarded.`]}),(0,Q.jsxs)(`form`,{onSubmit:_,className:$.form,noValidate:!0,children:[(0,Q.jsx)(`label`,{htmlFor:p,className:$.label,children:`Read token`}),(0,Q.jsx)(`input`,{ref:f,id:p,name:`read-token`,type:`password`,autoComplete:`off`,autoCorrect:`off`,spellCheck:!1,inputMode:`text`,className:$.input,value:o,onChange:e=>s(e.target.value),"aria-invalid":u?!0:void 0,"aria-describedby":u?h:void 0,placeholder:`64 hex characters`,autoFocus:!0}),a?(0,Q.jsxs)(Q.Fragment,{children:[(0,Q.jsxs)(`label`,{htmlFor:m,className:$.label,children:[`Mutation token `,(0,Q.jsx)(`span`,{className:$.optional,children:`optional — paste it now to enable sending`})]}),(0,Q.jsx)(`input`,{id:m,name:`mutation-token`,type:`password`,autoComplete:`off`,autoCorrect:`off`,spellCheck:!1,inputMode:`text`,className:$.input,value:c,onChange:e=>l(e.target.value),placeholder:`64 hex characters, printed under “Dispatch enabled”`})]}):null,u?(0,Q.jsx)(`p`,{id:h,className:$.error,role:`alert`,children:u}):null,(0,Q.jsx)(`button`,{type:`submit`,className:$.submit,disabled:ee||o.trim().length===0,children:ee?`Connecting…`:`Connect`})]})]})})}export{x as $,it as A,ze as B,ot as C,st as D,I as E,Ge as F,Oe as G,Ee as H,He as I,Ne as J,Se as K,Re as L,z as M,F as N,tt as O,dt as P,je as Q,Be as R,Tt as S,nt as T,Te as U,he as V,b as W,Me as X,ke as Y,Ae as Z,rn as _,un as a,v as at,Yt as b,on as c,g as ct,an as d,De as et,tn as f,en as g,$t as h,pn as i,ce as it,rt as j,ct as k,sn as l,Qt as m,hn as n,ve as nt,fn as o,se as ot,nn as p,Ce as q,dn as r,_ as rt,X as s,ae as st,_n as t,we as tt,cn as u,Zt as v,B as w,Xt as x,kt as y,Ve as z};
|
|
2
|
-
//# sourceMappingURL=global-DVgMguLr.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"global-DVgMguLr.js","names":[],"sources":["../../../../../../src/web-ui/data/cache.ts","../../../../../../src/core/verse/context-math.ts","../../../../../../src/web-ui/routes/verse/chat/composer-state.ts","../../../../../../src/web-ui/data/auth-store.ts","../../../../../../src/web-ui/data/sse.ts","../../../../../../src/web-ui/data/theme-store.ts","../../../../../../src/web-ui/data/appearance-store.ts","../../../../../../src/web-ui/data/hooks.ts","../../../../../../src/web-ui/data/client.ts","../../../../../../src/web-ui/components/primitives/Skeleton.module.css","../../../../../../src/web-ui/components/primitives/Skeleton.tsx","../../../../../../src/web-ui/app/SkipToContent.module.css","../../../../../../src/web-ui/app/SkipToContent.tsx","../../../../../../src/web-ui/components/auth/SessionGate.module.css","../../../../../../src/web-ui/components/auth/SessionGate.tsx"],"sourcesContent":["/**\n * data/cache.ts — the resource cache every view reads through. This is what\n * makes tab switches instant instead of blanking to \"Loading…\": a resource\n * fetched once stays in the cache and renders immediately (possibly stale)\n * on the next mount, with a background refresh kicked off silently behind\n * it. No framework dependency — a plain keyed store consumed via\n * useSyncExternalStore (see hooks.ts), matching the pattern already used\n * for auth-store.ts.\n *\n * Design choices, spelled out because a future agent will extend this:\n * - In-flight de-dupe: two components requesting the same key at once\n * share one fetch, not two.\n * - Never clears `data` on refetch. `status` distinguishes 'loading'\n * (no data yet — show a skeleton) from 'refreshing' (stale data still\n * shown — show a subtle indicator) from 'success'/'error'.\n * - `invalidate(key)` is how the SSE layer (sse.ts) tells the cache \"the\n * server just told us this changed\" without either module importing\n * React.\n * - useSyncExternalStore requires getSnapshot() to return a NEW object\n * reference whenever the observable state actually changes (it detects\n * updates via Object.is, not deep equality) — mutating one long-lived\n * entry object in place and handing back the same reference would fire\n * subscribers that never actually re-render. Every state transition\n * below therefore replaces `bookkeeping.snapshot` wholesale instead of\n * patching fields on it.\n *\n * ---------------------------------------------------------------------------\n * V2.1 — the request burst, and the three defects that lived in this file\n * ---------------------------------------------------------------------------\n *\n * Mounting the Usage section fires SEVEN reads at once (accounts, control,\n * bootstrap, usage-series, local-models, /api/control, /api/usage), several\n * of which spawn short-lived probe processes server-side. That burst starved\n * the 2s local-runtime probe timeout and made a healthy Ollama report as\n * unreachable roughly one burst in ten. The probe timeouts were hardened\n * server-side; the burst itself was still this file's problem. Three changes:\n *\n * 1. CONCURRENCY GATE. Every fetch this cache issues passes through a\n * process-wide gate capped at QUERY_CONCURRENCY_LIMIT. The same seven\n * reads still happen, but at most four are outstanding at once, so the\n * server is never asked to run seven probe-spawning handlers in\n * parallel. Nothing is dropped, nothing is delayed by a timer — a\n * queued task starts the microtask a slot frees. The gate is what makes\n * the probe timeout survivable, and it costs the caller nothing: the\n * browser already caps at ~6 sockets per origin, so this is a cap the\n * requests were going to hit anyway, applied where we can order it.\n *\n * 2. FRESHNESS. `ensureQuery(key, fetcher, maxAgeMs)` is the MOUNT path: it\n * skips the request entirely when the cached value is younger than\n * `maxAgeMs`. Re-entering a section you were just in is now zero\n * requests instead of seven. `runQuery` keeps its old always-fetch\n * semantics because the SSE layer and the prefetchers depend on it.\n *\n * 3. REFRESH THAT ACTUALLY RE-READS. `runQuery`'s in-flight de-dupe is\n * right for two components mounting at once and WRONG for a person\n * pressing Refresh: a click landing while a read is in flight joined\n * the running promise and returned data fetched before the click — a\n * silent no-op, and with local-models taking seconds under load the\n * window was real. `refetchQuery` fixes it in two steps, because the\n * same hook backs both a person's button and a 3s poll loop:\n * - by default it joins an in-flight read ONLY while that read is\n * younger than REFETCH_JOIN_WINDOW_MS. A poll tick landing on a\n * read that started milliseconds ago still coalesces (which is the\n * documented contract UniverseView's poll relies on); a click\n * landing on a read that started seconds ago issues a new request.\n * - with `force` it never joins at all. That is the strict guarantee\n * for an explicit user-initiated refresh (hooks.ts `useRefresh`).\n * Either way a newer request SUPERSEDES an older one by sequence\n * number, so the last read ISSUED is the one whose result is kept even\n * if an earlier one lands later.\n *\n * And the latent one: `evictAll()` used to call `store.clear()`, which\n * dropped the Bookkeeping objects that mounted components had registered\n * their `useSyncExternalStore` listeners on. Those components stayed\n * subscribed to an orphan Set that nothing would ever notify again — they\n * would never re-render for the rest of their life. Evict now RESETS an\n * entry that still has subscribers (and deletes only the unobserved ones),\n * so a subscriber always survives an evict.\n */\n\nexport type QueryStatus = 'idle' | 'loading' | 'refreshing' | 'success' | 'error';\n\nexport interface QueryEntry<T> {\n data: T | undefined;\n error: Error | undefined;\n status: QueryStatus;\n updatedAt: number | null;\n}\n\ninterface Bookkeeping<T = unknown> {\n snapshot: QueryEntry<T>;\n inFlight: Promise<void> | null;\n fetcher: (() => Promise<T>) | null;\n subscribers: Set<() => void>;\n /** Sequence number of the newest request ISSUED for this key. */\n issued: number;\n /** `Date.now()` when that newest request was issued. */\n issuedAt: number;\n /** Highest sequence number whose result has been written to `snapshot`. */\n applied: number;\n}\n\nconst IDLE_SNAPSHOT: QueryEntry<unknown> = { data: undefined, error: undefined, status: 'idle', updatedAt: null };\n\nconst store = new Map<string, Bookkeeping>();\n\n// ---------------------------------------------------------------------------\n// Concurrency gate\n// ---------------------------------------------------------------------------\n\n/**\n * How many cache-issued fetches may be outstanding at once, process-wide.\n *\n * Four, not one: the point is to stop a seven-way burst from spawning seven\n * concurrent server-side probes, not to serialize the app. Four keeps a\n * section's first paint fast while leaving the server two of its six sockets\n * for the SSE stream and any non-cache request.\n */\nexport const QUERY_CONCURRENCY_LIMIT = 4;\n\nlet activeFetches = 0;\nconst gateQueue: (() => void)[] = [];\n/** Peak concurrency observed since the gate was last reset. */\nlet peakConcurrency = 0;\n/**\n * Bumped by `resetQueryGate`. A fetch issued under an older generation no\n * longer owns a slot, so its eventual settlement must not decrement the new\n * generation's count — otherwise a request that never settles (a hung\n * socket, or a test stubbing fetch with a promise that never resolves) would\n * permanently consume capacity and starve every later read.\n */\nlet gateGeneration = 0;\n\nfunction releaseSlot(): void {\n activeFetches -= 1;\n const next = gateQueue.shift();\n if (next) next();\n}\n\n/**\n * Run `task` when a slot is free. Purely promise-driven — no timers, so this\n * is safe under fake timers and adds no latency beyond one microtask.\n */\nfunction schedule<T>(task: () => Promise<T>): Promise<T> {\n const start = (): Promise<T> => {\n const generation = gateGeneration;\n activeFetches += 1;\n if (activeFetches > peakConcurrency) peakConcurrency = activeFetches;\n const done = (): void => {\n if (generation === gateGeneration) releaseSlot();\n };\n let running: Promise<T>;\n try {\n running = task();\n } catch (err) {\n // A fetcher that throws synchronously must still free its slot.\n done();\n return Promise.reject(err instanceof Error ? err : new Error(String(err)));\n }\n return running.then(\n (value) => {\n done();\n return value;\n },\n (err: unknown) => {\n done();\n throw err;\n },\n );\n };\n\n if (activeFetches < QUERY_CONCURRENCY_LIMIT) return start();\n return new Promise<T>((resolve, reject) => {\n gateQueue.push(() => {\n start().then(resolve, reject);\n });\n });\n}\n\n/**\n * Abandon the gate's accounting: forget how many fetches are outstanding and\n * let everything queued run now. Called from `evictAll()`, which is both the\n * logout path and every test's `beforeEach` — a read that outlives the state\n * it was issued for has no claim on the next one's capacity.\n *\n * Nothing is cancelled. Requests already in flight still settle and still\n * write their result unless the key was evicted underneath them.\n */\nexport function resetQueryGate(): void {\n gateGeneration += 1;\n activeFetches = 0;\n peakConcurrency = 0;\n for (const start of gateQueue.splice(0)) start();\n}\n\n/** Observability for the tests: what the gate is doing right now. */\nexport function queryGateStats(): { active: number; queued: number; peak: number } {\n return { active: activeFetches, queued: gateQueue.length, peak: peakConcurrency };\n}\n\n// ---------------------------------------------------------------------------\n// Entries\n// ---------------------------------------------------------------------------\n\nfunction entryFor<T>(key: string): Bookkeeping<T> {\n let e = store.get(key) as Bookkeeping<T> | undefined;\n if (!e) {\n e = {\n snapshot: IDLE_SNAPSHOT as QueryEntry<T>,\n inFlight: null,\n fetcher: null,\n subscribers: new Set(),\n issued: 0,\n issuedAt: 0,\n applied: 0,\n };\n store.set(key, e);\n }\n return e;\n}\n\nfunction setSnapshot<T>(e: Bookkeeping<T>, patch: Partial<QueryEntry<T>>): void {\n e.snapshot = { ...e.snapshot, ...patch };\n}\n\nfunction notify(key: string): void {\n const e = store.get(key);\n if (!e) return;\n for (const s of e.subscribers) s();\n}\n\n/**\n * Structural equality for API payloads, used to decide whether a refresh\n * actually changed anything.\n *\n * A Refresh that re-reads seven routes and finds six of them byte-identical\n * should re-render nothing for those six. Keeping the PREVIOUS `data`\n * reference when the new body is deep-equal does exactly that: the snapshot\n * object still changes (status and updatedAt did), so useSyncExternalStore\n * still fires, but every `useMemo`/`memo` keyed on `data` downstream skips —\n * and the Usage section's models are all keyed on `data`.\n *\n * Deliberately structural rather than JSON.stringify: key order is not\n * significant in these payloads, and stringify would also throw on the\n * (never-seen, but cheap to survive) cyclic body.\n */\nfunction sameData(a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return true;\n if (typeof a !== typeof b) return false;\n if (a === null || b === null || typeof a !== 'object') return false;\n if (Array.isArray(a) !== Array.isArray(b)) return false;\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n return a.every((item, i) => sameData(item, b[i]));\n }\n const left = a as Record<string, unknown>;\n const right = b as Record<string, unknown>;\n const leftKeys = Object.keys(left);\n if (leftKeys.length !== Object.keys(right).length) return false;\n return leftKeys.every((k) => Object.prototype.hasOwnProperty.call(right, k) && sameData(left[k], right[k]));\n}\n\nexport function subscribeQuery(key: string, listener: () => void): () => void {\n const e = entryFor(key);\n e.subscribers.add(listener);\n return () => {\n e.subscribers.delete(listener);\n };\n}\n\nexport function getQuerySnapshot<T>(key: string): QueryEntry<T> {\n return entryFor<T>(key).snapshot;\n}\n\n/**\n * How long an in-flight read is accepted as already satisfying a refetch.\n *\n * A poll tick (UniverseView refreshes every 3–15s and again on\n * visibilitychange) that lands on a read issued milliseconds ago should\n * coalesce with it — that is the behavior its own comment and tests pin\n * down. A person's Refresh landing on a read that has been running for\n * seconds should not: they are asking about the world as of the click.\n */\nexport const REFETCH_JOIN_WINDOW_MS = 250;\n\nexport interface QueryRunOptions {\n /**\n * Skip the request when the cached value is younger than this many ms.\n * Undefined (the default) always fetches.\n */\n maxAgeMs?: number;\n /**\n * Join an in-flight read only while it is younger than this. Undefined\n * joins any in-flight read (the original de-dupe).\n */\n joinWindowMs?: number;\n /**\n * User-initiated. Never joins an in-flight read and never honours\n * `maxAgeMs` — pressing Refresh must produce a request that was issued\n * after the click.\n */\n force?: boolean;\n}\n\nfunction isFresh(e: Bookkeeping, maxAgeMs: number): boolean {\n if (e.snapshot.updatedAt === null) return false;\n if (e.snapshot.status === 'error') return false;\n return Date.now() - e.snapshot.updatedAt < maxAgeMs;\n}\n\nfunction mayJoin(e: Bookkeeping, options: QueryRunOptions): boolean {\n if (e.inFlight === null) return false;\n if (options.joinWindowMs === undefined) return true;\n return Date.now() - e.issuedAt < options.joinWindowMs;\n}\n\nfunction execute<T>(key: string, fetcher: () => Promise<T>, options: QueryRunOptions): Promise<void> {\n const e = entryFor<T>(key);\n e.fetcher = fetcher;\n\n if (!options.force) {\n // Two components mounting at once share one request.\n if (mayJoin(e, options)) return e.inFlight!;\n // Already fresh enough for this caller: no request at all.\n if (options.maxAgeMs !== undefined && isFresh(e, options.maxAgeMs)) return Promise.resolve();\n }\n\n const seq = (e.issued += 1);\n e.issuedAt = Date.now();\n setSnapshot(e, { status: e.snapshot.data !== undefined ? 'refreshing' : 'loading' });\n notify(key);\n\n const run = schedule(fetcher)\n .then((data) => {\n // A superseded request (a forced refresh was issued after this one, or\n // the key was evicted) must never overwrite a newer answer.\n if (seq < e.applied) return;\n e.applied = seq;\n const unchanged = e.snapshot.data !== undefined && sameData(e.snapshot.data, data);\n setSnapshot(e, {\n data: unchanged ? e.snapshot.data : data,\n error: undefined,\n status: 'success',\n updatedAt: Date.now(),\n });\n })\n .catch((err: unknown) => {\n if (seq < e.applied) return;\n e.applied = seq;\n setSnapshot(e, { error: err instanceof Error ? err : new Error(String(err)), status: 'error' });\n })\n .finally(() => {\n // Only the newest request owns `inFlight`; an older, superseded one\n // finishing must not clear the newer one's slot.\n if (e.issued === seq) e.inFlight = null;\n notify(key);\n });\n\n e.inFlight = run;\n return run;\n}\n\n/**\n * Kick off (or join) a fetch for `key`. Safe to call redundantly — repeated\n * calls with an in-flight fetch are no-ops. Registers `fetcher` on the\n * entry so invalidate() can re-run it later without the caller re-supplying\n * it (e.g. from the SSE layer, which doesn't have per-view fetch closures).\n */\nexport function runQuery<T>(key: string, fetcher: () => Promise<T>): Promise<void> {\n return execute(key, fetcher, {});\n}\n\n/**\n * The MOUNT path (hooks.ts `useQuery`). Identical to `runQuery` except that a\n * value fetched less than `maxAgeMs` ago is accepted as-is and no request is\n * made. This is what turns \"switch to Usage and back\" from fourteen requests\n * into seven.\n */\nexport function ensureQuery<T>(key: string, fetcher: () => Promise<T>, maxAgeMs: number): Promise<void> {\n return execute(key, fetcher, { maxAgeMs });\n}\n\n/**\n * The REFRESH path (hooks.ts `useRefetch` / `useRefresh`).\n *\n * Default (`force: false`): joins an in-flight read only while that read is\n * younger than REFETCH_JOIN_WINDOW_MS, so a burst of poll ticks still\n * collapses into one request while a click on a long-running read gets a\n * real re-read.\n *\n * `force: true`: never joins. Always issues a request made after the call,\n * and wins the race against anything already running regardless of which\n * lands first. This is the guarantee an explicit user-initiated refresh\n * needs.\n */\nexport function refetchQuery<T>(key: string, fetcher: () => Promise<T>, force = false): Promise<void> {\n return execute(key, fetcher, force ? { force: true } : { joinWindowMs: REFETCH_JOIN_WINDOW_MS });\n}\n\n/** Re-run the last fetcher registered for `key`, if any (used by sse.ts). */\nexport function invalidate(key: string): void {\n const e = store.get(key);\n if (!e || !e.fetcher) return;\n void runQuery(key, e.fetcher);\n}\n\n/** Re-run the fetcher for every cached key starting with `prefix`. For\n * parameterized resources (per-filter inbox list queries, per-id inbox\n * detail queries) the exact live key isn't known ahead of time — used by\n * sse.ts and data/mutations.ts so those views live-update too. */\nexport function invalidatePrefix(prefix: string): void {\n for (const key of store.keys()) {\n if (key.startsWith(prefix)) invalidate(key);\n }\n}\n\n/**\n * Reset one entry to idle WITHOUT dropping its subscriber set, and supersede\n * anything still in flight for it so a late result cannot repopulate the key\n * after it was deliberately cleared.\n */\nfunction resetEntry(key: string, e: Bookkeeping): void {\n e.snapshot = IDLE_SNAPSHOT as QueryEntry<unknown>;\n e.fetcher = null;\n e.inFlight = null;\n e.issued += 1;\n e.applied = e.issued;\n notify(key);\n}\n\n/** Drop a key entirely (rare — e.g. on logout, so stale data never leaks\n * into the next session's first paint). An entry that still has mounted\n * subscribers is RESET rather than deleted: deleting it would orphan the\n * listener set those components registered on, leaving them subscribed to\n * something nothing can notify. */\nexport function evict(key: string): void {\n const e = store.get(key);\n if (!e) return;\n if (e.subscribers.size === 0) {\n store.delete(key);\n return;\n }\n resetEntry(key, e);\n}\n\nexport function evictAll(): void {\n resetQueryGate();\n for (const [key, e] of [...store]) {\n if (e.subscribers.size === 0) {\n store.delete(key);\n continue;\n }\n resetEntry(key, e);\n }\n}\n","/**\n * Ashlr Verse — context arithmetic shared by the server and the browser.\n *\n * PURE and BROWSER-SAFE: no node imports, no I/O. The seat catalog, the\n * session engine, the adapters and the UI all compute thresholds through this\n * one module, so \"compacts at 367k\" can never mean two different numbers in\n * two places. docs/VERSE-CONTEXT.md is the prose authority for every constant.\n *\n * Where each formula comes from (all read from the CLIs themselves, 2026-09-23):\n *\n * Claude Code (2.1.257 and 2.1.280 binaries): the auto-compaction window is\n * `min(--autocompact | autoCompactWindow | auto, model window)`. From it the\n * CLI reserves `min(max output, 20_000)` for the reply and compacts a\n * further 13_000 below that. On a 1M model at `auto` that is 967_000 — the\n * `pre_tokens` observed on real compactions (967_391).\n * Codex (0.136 – 0.155 rollouts): the window the CLI measures against is\n * `context_window × effective_context_window_percent / 100` = 258_400 for\n * every 272k model; auto-compaction triggers at 90% of the RAW window\n * (244_800). The largest uncompacted call across 62,104 was 243_204.\n * Grok (0.2.118 catalog): compacts at `auto_compact_threshold_percent` (80)\n * of `context_window` (500_000) = 400_000.\n * Local seats run the Claude Code binary against Ollama, so they compact by\n * the Claude formula over the window Verse passes in\n * `CLAUDE_CODE_MAX_CONTEXT_TOKENS`.\n */\n\nimport type {\n VerseContextBudget,\n VerseContextMode,\n VerseEngine,\n VerseFitVerdict,\n VerseModelOption,\n VerseSession,\n VerseUsage,\n} from './types.js';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/**\n * Standard-mode compaction window for 1M-context Claude models.\n *\n * WHY 400k and not the CLI's ~967k: every turn re-sends the whole prefix, so a\n * session held near 900k costs ~2.3× per call what one held under 400k does,\n * and long-context recall degrades with length on every published benchmark\n * (docs/VERSE-CONTEXT.md §2). 400k keeps a large working set — far above the\n * 150k Anthropic's own API compacts at by default — while leaving the full\n * window one click away in `expansive` mode. Grok's native budget is also\n * 400k, so the two frontier engines share one standard ceiling.\n */\nexport const CLAUDE_STANDARD_AUTOCOMPACT_WINDOW = 400_000;\n\n/** Claude Code reserves min(max output, this) of the window for the reply. */\nexport const CLAUDE_OUTPUT_RESERVE_CAP = 20_000;\n/** …and compacts this many tokens below the reserved window. */\nexport const CLAUDE_COMPACT_BUFFER = 13_000;\n/** Claude Code clamps `--autocompact` to [100k, 1M]. */\nexport const CLAUDE_AUTOCOMPACT_MIN = 100_000;\nexport const CLAUDE_AUTOCOMPACT_MAX = 1_000_000;\n\n/** Codex: default `effective_context_window_percent` in every catalog entry. */\nexport const CODEX_EFFECTIVE_WINDOW_PERCENT = 95;\n/** Codex: auto-compaction fires at this fraction of the RAW window. */\nexport const CODEX_AUTO_COMPACT_FRACTION = 0.9;\n\n/** Grok: `auto_compact_threshold_percent` in the 0.2.118 catalog. */\nexport const GROK_AUTO_COMPACT_PERCENT = 80;\n\n/**\n * How long a provider keeps a conversation's prompt cache warm while idle.\n * Claude Code on a subscription: 1h (5 min without it). Past this, the next\n * turn re-reads the whole context at full price — the single most expensive\n * thing an idle long session can do.\n */\nexport const CACHE_IDLE_TTL_MS = 60 * 60 * 1000;\n\n/**\n * Fixed prompt overhead (system prompt + tool definitions) a FRESH session\n * already occupies before any work, per engine.\n *\n * local 15k — measured: two real turns through Claude Code 2.1.280 against\n * Ollama with `--exclude-dynamic-system-prompt-sections`\n * reported 14,695 and 14,942 context tokens for a one-word reply.\n * claude 25k — the same CLI without that flag measured 17.8k–23.3k on local\n * seats; rounded up. An ESTIMATE, never shown as a measurement.\n * codex 15k / grok 20k — estimates; neither CLI reports its base prompt.\n *\n * Only used by the fit verdicts, which are labelled as estimates in the UI.\n */\nexport const SESSION_BASE_OVERHEAD_BY_ENGINE: Readonly<Record<VerseEngine, number>> = {\n claude: 25_000,\n codex: 15_000,\n grok: 20_000,\n local: 15_000,\n};\n\n/** Fallback overhead when the engine is unknown (the largest estimate). */\nexport const SESSION_BASE_OVERHEAD_TOKENS = 25_000;\n\nexport function sessionOverheadTokens(engine: VerseEngine | null | undefined): number {\n return engine ? SESSION_BASE_OVERHEAD_BY_ENGINE[engine] ?? SESSION_BASE_OVERHEAD_TOKENS : SESSION_BASE_OVERHEAD_TOKENS;\n}\n\n/**\n * Smallest local window worth offering as a seat. Local turns run Claude Code\n * with `CLAUDE_CODE_MAX_CONTEXT_TOKENS=<window>`, which compacts at\n * `window − 20k − 13k`; below this the compaction point leaves under 8k of\n * working room above the ~15k base prompt, so every turn would compact.\n */\nexport const LOCAL_MIN_WORKING_TOKENS = 8_000;\nexport const LOCAL_MIN_USABLE_WINDOW =\n SESSION_BASE_OVERHEAD_BY_ENGINE.local + LOCAL_MIN_WORKING_TOKENS + CLAUDE_OUTPUT_RESERVE_CAP + CLAUDE_COMPACT_BUFFER;\n\n/** True when a local window leaves real working room under Claude Code's compaction rule. */\nexport function localWindowUsable(window: number | null | undefined): boolean {\n return typeof window === 'number' && Number.isFinite(window) && window >= LOCAL_MIN_USABLE_WINDOW;\n}\n\n/** chars → tokens estimator used everywhere a count is estimated rather than measured. */\nexport const CHARS_PER_TOKEN = 4;\n\n// ---------------------------------------------------------------------------\n// Per-engine formulas\n// ---------------------------------------------------------------------------\n\nfunction positiveInt(value: unknown): number | null {\n return typeof value === 'number' && Number.isFinite(value) && value > 0 ? Math.floor(value) : null;\n}\n\n/**\n * Claude Code's auto-compaction point.\n *\n * @param modelWindow the model's context window (1_000_000, 200_000, or\n * the local num_ctx passed in CLAUDE_CODE_MAX_CONTEXT_TOKENS)\n * @param maxOutputTokens the model's default max output; null → assume the cap\n * @param autocompactWindow the `--autocompact` value in force; null → `auto`\n * (the full model window)\n */\nexport function claudeAutoCompactAt(\n modelWindow: number,\n maxOutputTokens: number | null,\n autocompactWindow: number | null = null,\n): number {\n const window = Math.min(modelWindow, positiveInt(autocompactWindow) ?? modelWindow);\n const reserve = Math.min(positiveInt(maxOutputTokens) ?? CLAUDE_OUTPUT_RESERVE_CAP, CLAUDE_OUTPUT_RESERVE_CAP);\n return Math.max(0, window - reserve - CLAUDE_COMPACT_BUFFER);\n}\n\n/** Codex: the window the CLI measures occupancy against. */\nexport function codexEffectiveWindow(rawWindow: number, percent: number | null = null): number {\n const pct = positiveInt(percent) !== null && (percent as number) <= 100 ? (percent as number) : CODEX_EFFECTIVE_WINDOW_PERCENT;\n return Math.floor((rawWindow * pct) / 100);\n}\n\n/** Codex: where auto-compaction fires for a given RAW window. */\nexport function codexAutoCompactAt(rawWindow: number): number {\n return Math.floor(rawWindow * CODEX_AUTO_COMPACT_FRACTION);\n}\n\n/** Grok: where auto-compaction fires. */\nexport function grokAutoCompactAt(window: number, percent: number | null = null): number {\n const pct = positiveInt(percent) !== null && (percent as number) <= 100 ? (percent as number) : GROK_AUTO_COMPACT_PERCENT;\n return Math.floor((window * pct) / 100);\n}\n\n// ---------------------------------------------------------------------------\n// Model ids\n// ---------------------------------------------------------------------------\n\n/**\n * Ids Verse once shipped that the CLI does NOT resolve to the model the label\n * promised. `claude-opus-5.5` is parsed by Claude Code as a fuzzy match for\n * `claude-opus-5` (its normaliser rejects the `.5` suffix and falls back to\n * `includes('claude-opus-5')`), so every \"Opus 5.5\" turn actually ran Opus 5.\n * Old session records keep their stored id; adapters send the canonical one.\n */\nexport const VERSE_MODEL_ID_ALIASES: Readonly<Record<string, string>> = {\n 'claude-opus-5.5': 'claude-opus-5-5',\n};\n\nexport function canonicalModelId(id: string): string {\n return VERSE_MODEL_ID_ALIASES[id] ?? id;\n}\n\n// ---------------------------------------------------------------------------\n// Budgets and occupancy\n// ---------------------------------------------------------------------------\n\n/** The budget a model runs with in a mode, or null when that mode does not exist for it. */\nexport function budgetFor(option: VerseModelOption | null | undefined, mode: VerseContextMode): VerseContextBudget | null {\n if (!option) return null;\n if (mode === 'expansive') return option.expansive ?? null;\n const window = positiveInt(option.contextWindow);\n if (window === null) return null;\n return { contextWindow: window, autoCompactAt: positiveInt(option.autoCompactAt ?? null) };\n}\n\n/** True when this model has a genuine expansive budget larger than its standard one. */\nexport function hasExpansiveMode(option: VerseModelOption | null | undefined): boolean {\n const standard = budgetFor(option, 'standard');\n const expansive = budgetFor(option, 'expansive');\n if (!expansive) return false;\n if (!standard) return true;\n return (expansive.autoCompactAt ?? expansive.contextWindow) > (standard.autoCompactAt ?? standard.contextWindow);\n}\n\n/**\n * The `--autocompact` value Verse passes to Claude Code for a model in a mode,\n * or null for \"let the CLI use `auto`\". Only 1M-native models are capped in\n * standard mode; a 200k model already compacts near 167k natively.\n */\nexport function claudeAutocompactFlag(option: VerseModelOption | null | undefined, mode: VerseContextMode): number | null {\n if (mode === 'expansive') return null;\n const window = positiveInt(option?.contextWindow ?? null);\n return window !== null && window > CLAUDE_STANDARD_AUTOCOMPACT_WINDOW && hasExpansiveMode(option)\n ? CLAUDE_STANDARD_AUTOCOMPACT_WINDOW\n : null;\n}\n\n/**\n * Recompute the compaction point when the CLI reports a window at runtime\n * that differs from the catalog budget (Claude Code clamps a 1M model to 200k\n * when long-context credit runs out; grok can upgrade a window by header).\n *\n * claude/local — fixed-buffer formula over min(runtime window, --autocompact).\n * codex — the runtime figure is the EFFECTIVE window; the compaction\n * point stays 90% of the raw window it implies.\n * grok/other — keep the budget's compaction RATIO.\n */\nexport function reconcileAutoCompactAt(input: {\n engine: VerseEngine;\n runtimeWindow: number;\n budget: VerseContextBudget | null;\n /** The `--autocompact` value in force for claude/local; null = auto. */\n autocompactWindow?: number | null;\n maxOutputTokens?: number | null;\n}): number | null {\n const runtime = positiveInt(input.runtimeWindow);\n if (runtime === null) return input.budget?.autoCompactAt ?? null;\n if (input.budget && input.budget.contextWindow === runtime) return input.budget.autoCompactAt;\n switch (input.engine) {\n case 'claude':\n case 'local':\n return claudeAutoCompactAt(runtime, input.maxOutputTokens ?? null, input.autocompactWindow ?? null);\n case 'codex':\n return codexAutoCompactAt(Math.round((runtime * 100) / CODEX_EFFECTIVE_WINDOW_PERCENT));\n default: {\n const b = input.budget;\n if (!b || b.autoCompactAt === null || b.contextWindow <= 0) return null;\n return Math.floor(runtime * (b.autoCompactAt / b.contextWindow));\n }\n }\n}\n\nexport type VerseOccupancyTone = 'unknown' | 'ok' | 'warn' | 'danger' | 'over';\n\nexport interface VerseOccupancy {\n tokens: number;\n window: number | null;\n autoCompactAt: number | null;\n /** tokens / window, unclamped; null when the window is unknown. */\n ofWindow: number | null;\n /** tokens / autoCompactAt, unclamped; null when the compaction point is unknown. */\n ofCompaction: number | null;\n /** Tokens left before auto-compaction (≥ 0), null when unknown. */\n untilCompaction: number | null;\n tone: VerseOccupancyTone;\n /** False when `tokens` is an upper bound (codex before its rollout is readable). */\n exact: boolean;\n}\n\n/** Warn at 80% of the compaction point, danger from 95% — BEFORE the CLI compacts, not after. */\nexport const OCCUPANCY_WARN = 0.8;\nexport const OCCUPANCY_DANGER = 0.95;\n\nexport function occupancy(usage: Pick<VerseUsage, 'contextTokens' | 'contextWindow' | 'autoCompactAt' | 'contextTokensExact'>): VerseOccupancy {\n const tokens = Math.max(0, Math.floor(usage.contextTokens || 0));\n const window = positiveInt(usage.contextWindow);\n const compactAt = positiveInt(usage.autoCompactAt ?? null);\n const ofWindow = window !== null ? tokens / window : null;\n const ofCompaction = compactAt !== null ? tokens / compactAt : null;\n const gauge = ofCompaction ?? ofWindow;\n const exact = usage.contextTokensExact !== false;\n let tone: VerseOccupancyTone = 'unknown';\n if (!exact && gauge !== null && gauge >= 1) {\n // An UPPER BOUND past the compaction point says nothing about the real\n // prompt: codex's turn total sums every model call (median 29× the last\n // one). Claiming \"over the window\" from it would be invented precision.\n tone = 'unknown';\n } else if (window !== null && tokens > window) tone = 'over';\n else if (gauge !== null) tone = gauge >= OCCUPANCY_DANGER ? 'danger' : gauge >= OCCUPANCY_WARN ? 'warn' : 'ok';\n return {\n tokens,\n window,\n autoCompactAt: compactAt,\n ofWindow,\n ofCompaction,\n untilCompaction: compactAt !== null ? Math.max(0, compactAt - tokens) : null,\n tone,\n exact,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Efficiency\n// ---------------------------------------------------------------------------\n\n/**\n * Share of prompt tokens served from cache: cacheRead / (input + cacheRead +\n * cacheCreation). Adapters already report `inputTokens` EXCLUDING cached\n * tokens (codex's turn total is split by its adapter), so the buckets are\n * disjoint. Null when nothing has been read yet.\n */\nexport function cacheHitRatio(usage: Pick<VerseUsage, 'inputTokens' | 'cacheReadTokens' | 'cacheCreationTokens'>): number | null {\n const denominator = usage.inputTokens + usage.cacheReadTokens + usage.cacheCreationTokens;\n return denominator > 0 ? usage.cacheReadTokens / denominator : null;\n}\n\nexport function estimateTokensFromChars(chars: number): number {\n return Math.ceil(Math.max(0, chars) / CHARS_PER_TOKEN);\n}\n\n// ---------------------------------------------------------------------------\n// Advice (pure; the UI decides how to show it, nothing here spends)\n// ---------------------------------------------------------------------------\n\n/**\n * Would this working set fit one context on this model?\n *\n * fits — working set + fixed overhead ≤ 60% of the standard compaction point\n * tight — ≤ 100% of it: it fits, but the session will compact soon\n * expansive — only the expansive budget holds it\n * split — no single context holds it; fan the work out instead\n *\n * Null when the model's budget is unknown (never guessed). Pass the engine's\n * `sessionOverheadTokens(engine)`; the default is the largest estimate.\n */\nexport function fitVerdict(\n workingSetTokens: number,\n option: VerseModelOption | null | undefined,\n overheadTokens: number = SESSION_BASE_OVERHEAD_TOKENS,\n): VerseFitVerdict | null {\n const standard = budgetFor(option, 'standard');\n if (!standard) return null;\n const need = Math.max(0, workingSetTokens) + Math.max(0, overheadTokens);\n const standardCap = standard.autoCompactAt ?? standard.contextWindow;\n if (need <= standardCap * 0.6) return 'fits';\n if (need <= standardCap) return 'tight';\n const expansive = budgetFor(option, 'expansive');\n if (expansive && need <= (expansive.autoCompactAt ?? expansive.contextWindow)) return 'expansive';\n return 'split';\n}\n\nexport type VerseAdviceLevel = 'none' | 'suggest' | 'urge';\n\nexport interface VerseHandoffAdvice {\n level: VerseAdviceLevel;\n /** Plain sentences, most important first. Empty when level is 'none'. */\n reasons: string[];\n}\n\n/** Compactions after which early-turn detail is mostly summaries. */\nexport const HANDOFF_COMPACTION_THRESHOLD = 2;\n/** Below this, an expired cache is cheap to re-read and not worth a handoff. */\nexport const HANDOFF_IDLE_MIN_TOKENS = 100_000;\n\nfunction compactTokens(n: number): string {\n if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(n % 1_000_000 === 0 ? 0 : 1)}M`;\n if (n >= 1_000) return `${Math.round(n / 1_000)}k`;\n return String(n);\n}\n\n/**\n * Should the operator continue in a fresh session? Pure: `now` and\n * `lastActivityAt` are passed in, so the verdict is reproducible in tests.\n */\nexport function handoffAdvice(input: {\n usage: VerseUsage;\n compactionCount?: number;\n lastActivityAt?: string | null;\n now: number;\n}): VerseHandoffAdvice {\n const occ = occupancy(input.usage);\n const reasons: string[] = [];\n let level: VerseAdviceLevel = 'none';\n const bump = (to: VerseAdviceLevel): void => {\n // (level only ever rises)\n if (to === 'urge' || (to === 'suggest' && level === 'none')) level = to;\n };\n\n if (!occ.exact) {\n // An upper bound cannot justify \"you are near compaction\"; wait for a reading.\n } else if (occ.tone === 'over') {\n bump('urge');\n reasons.push(`Context (${compactTokens(occ.tokens)}) is past the ${compactTokens(occ.window ?? 0)} window.`);\n } else if (occ.ofCompaction !== null && occ.ofCompaction >= OCCUPANCY_WARN) {\n bump('suggest');\n reasons.push(`About ${compactTokens(occ.untilCompaction ?? 0)} tokens left before the CLI auto-compacts.`);\n }\n\n const compactions = input.compactionCount ?? 0;\n if (compactions >= HANDOFF_COMPACTION_THRESHOLD) {\n bump('suggest');\n reasons.push(`Compacted ${compactions} times — early turns now survive only as summaries.`);\n }\n\n if (input.lastActivityAt) {\n const idle = input.now - Date.parse(input.lastActivityAt);\n if (occ.exact && Number.isFinite(idle) && idle >= CACHE_IDLE_TTL_MS && occ.tokens >= HANDOFF_IDLE_MIN_TOKENS) {\n bump('suggest');\n reasons.push(`Idle over an hour: the prompt cache has likely expired, so the next turn re-reads ~${compactTokens(occ.tokens)} tokens at full cost.`);\n }\n }\n return { level, reasons };\n}\n\nexport interface VerseExpansiveAdvice {\n suggest: boolean;\n reason: string | null;\n}\n\n/**\n * Suggest — never switch on — expansive mode. Only for a session in standard\n * mode whose model has a real expansive budget, and only on evidence: it has\n * already compacted repeatedly, or its working set only fits expansive.\n */\nexport function expansiveAdvice(input: {\n session: Pick<VerseSession, 'contextMode' | 'compactionCount'>;\n option: VerseModelOption | null | undefined;\n workingSetTokens?: number | null;\n}): VerseExpansiveAdvice {\n if ((input.session.contextMode ?? 'standard') !== 'standard' || !hasExpansiveMode(input.option)) {\n return { suggest: false, reason: null };\n }\n const compactions = input.session.compactionCount ?? 0;\n if (compactions >= HANDOFF_COMPACTION_THRESHOLD) {\n return { suggest: true, reason: `This session has compacted ${compactions} times; expansive mode keeps more of it in view.` };\n }\n if (typeof input.workingSetTokens === 'number' && fitVerdict(input.workingSetTokens, input.option) === 'expansive') {\n return { suggest: true, reason: `The reachable code (~${compactTokens(input.workingSetTokens)} tokens) only fits the expansive budget.` };\n }\n return { suggest: false, reason: null };\n}\n\n/** Engines whose CLIs can be told a compaction budget per invocation. */\nexport function engineSupportsModes(engine: VerseEngine): boolean {\n return engine === 'claude' || engine === 'codex';\n}\n","/**\n * routes/verse/chat/composer-state.ts — the composer's memory, and an\n * honest estimate of what a message will cost.\n *\n * Two things a long agentic day needs from a message box and does not get\n * from a bare `useState`:\n *\n * 1. A draft survives switching chats and reloading the app. Half-written\n * instructions are expensive to re-type and are lost today the moment\n * ⌘K lands somewhere else.\n * 2. What was already sent can be recalled with ↑, the way every shell\n * works, because the second attempt at a prompt is usually the first\n * one plus a clause.\n *\n * Both are per-session and stored in `localStorage`, bounded so a long-lived\n * browser cannot accumulate an unbounded map. Every access is wrapped: a\n * private window with storage blocked still runs the app.\n */\n\nimport { CHARS_PER_TOKEN, occupancy } from '../../../../core/verse/context-math.js';\n\nconst DRAFT_KEY = 'ashlr.verse.drafts.v1';\nconst SENT_KEY = 'ashlr.verse.sent.v1';\n\n/** Sessions remembered, newest last. */\nconst SESSION_LIMIT = 40;\n/** Messages recalled per session. */\nconst HISTORY_LIMIT = 25;\n/** Matches VERSE_MAX_TURN_TEXT_BYTES; a draft over it cannot be sent anyway. */\nconst TEXT_LIMIT = 64 * 1024;\n\nfunction readMap<T>(key: string, parse: (value: unknown) => T | null): Record<string, T> {\n const out: Record<string, T> = {};\n try {\n const raw = localStorage.getItem(key);\n const parsed = raw ? (JSON.parse(raw) as unknown) : null;\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {\n for (const [id, value] of Object.entries(parsed as Record<string, unknown>)) {\n const clean = parse(value);\n if (clean !== null) out[id] = clean;\n }\n }\n } catch {\n /* storage unavailable or corrupt — an empty memory is a valid one */\n }\n return out;\n}\n\n/** True when the write landed; false when storage refused it (blocked, over quota). */\nfunction writeMap(key: string, map: Record<string, unknown>): boolean {\n // Insertion order is recency order: every write deletes before re-inserting.\n const ids = Object.keys(map);\n for (const stale of ids.slice(0, Math.max(0, ids.length - SESSION_LIMIT))) delete map[stale];\n try {\n localStorage.setItem(key, JSON.stringify(map));\n return true;\n } catch {\n /* best-effort — the caller decides whether a refusal loses anything */\n return false;\n }\n}\n\nconst asText = (value: unknown): string | null =>\n typeof value === 'string' && value.length > 0 ? value.slice(0, TEXT_LIMIT) : null;\n\nconst asHistory = (value: unknown): string[] | null => {\n if (!Array.isArray(value)) return null;\n const list = value.filter((v): v is string => typeof v === 'string' && v.length > 0)\n .slice(-HISTORY_LIMIT)\n .map((v) => v.slice(0, TEXT_LIMIT));\n return list.length > 0 ? list : null;\n};\n\n/**\n * Drafts whose last write storage REFUSED, held for the page's lifetime ('' =\n * a clear that could not be written) and consulted BEFORE storage.\n *\n * WHY: `localStorage` writes fail silently (blocked storage, a full quota),\n * and one draft is not a convenience but the only copy of something: the\n * handoff note. The dialog hands it over with `saveDraft` and the new chat's\n * Composer restores it with `loadDraft`; with storage refusing, the edited\n * note — possibly written after a paid summary turn — simply vanished. An\n * entry here is always newer than whatever storage holds for that chat, and a\n * later write that lands removes it, so storage is the answer again.\n */\nconst unsavedDrafts = new Map<string, string>();\n\n/** The unsent draft for this chat, or `''`. */\nexport function loadDraft(sessionId: string | null | undefined): string {\n if (!sessionId) return '';\n const unsaved = unsavedDrafts.get(sessionId);\n if (unsaved !== undefined) return unsaved;\n return readMap(DRAFT_KEY, asText)[sessionId] ?? '';\n}\n\n/** Persist (or clear, when empty) the draft for this chat. */\nexport function saveDraft(sessionId: string | null | undefined, text: string): void {\n if (!sessionId) return;\n const clean = text.trim().length > 0 ? text.slice(0, TEXT_LIMIT) : '';\n const map = readMap(DRAFT_KEY, asText);\n delete map[sessionId];\n if (clean) map[sessionId] = clean;\n unsavedDrafts.delete(sessionId);\n if (!writeMap(DRAFT_KEY, map)) {\n unsavedDrafts.set(sessionId, clean);\n // Bounded like storage: oldest refusals go first.\n for (const stale of [...unsavedDrafts.keys()].slice(0, Math.max(0, unsavedDrafts.size - SESSION_LIMIT))) unsavedDrafts.delete(stale);\n }\n}\n\n/** Messages sent in this chat, oldest first. */\nexport function loadHistory(sessionId: string | null | undefined): string[] {\n if (!sessionId) return [];\n return readMap(SENT_KEY, asHistory)[sessionId] ?? [];\n}\n\n/** Record a sent message; consecutive duplicates are not stored twice. */\nexport function pushHistory(sessionId: string | null | undefined, text: string): void {\n if (!sessionId || text.trim().length === 0) return;\n const map = readMap(SENT_KEY, asHistory);\n const current = map[sessionId] ?? [];\n delete map[sessionId];\n const next = current[current.length - 1] === text ? current : [...current, text.slice(0, TEXT_LIMIT)];\n map[sessionId] = next.slice(-HISTORY_LIMIT);\n writeMap(SENT_KEY, map);\n}\n\n/**\n * Forget everything this chat remembered — its unsent draft and its sent\n * messages.\n *\n * Deleting a chat removes its transcript from disk, but the verbatim prompts\n * stayed in browser storage under the dead session id until forty newer\n * sessions rotated them out.\n */\nexport function forgetComposerMemory(sessionId: string | null | undefined): void {\n if (!sessionId) return;\n unsavedDrafts.delete(sessionId);\n for (const [key, parse] of [\n [DRAFT_KEY, asText],\n [SENT_KEY, asHistory],\n ] as const) {\n const map: Record<string, unknown> = readMap(key, parse as (v: unknown) => unknown);\n if (!(sessionId in map)) continue;\n delete map[sessionId];\n writeMap(key, map);\n }\n}\n\n/**\n * Drop every remembered draft and sent message, for every session.\n *\n * Called on disconnect/logout alongside the query cache's `evictAll`, whose\n * contract is that stale data must not leak into the next session's first\n * paint. Prompt text is precisely the content that wipe exists for, and it is\n * the one thing that used to survive it.\n */\nexport function clearComposerMemory(): void {\n unsavedDrafts.clear();\n for (const key of [DRAFT_KEY, SENT_KEY]) {\n try {\n localStorage.removeItem(key);\n } catch {\n /* storage unavailable — nothing to clear */\n }\n }\n}\n\nexport const VERSE_DRAFT_STORAGE_KEY = DRAFT_KEY;\nexport const VERSE_SENT_STORAGE_KEY = SENT_KEY;\n\n// ---------------------------------------------------------------------------\n// Cost\n// ---------------------------------------------------------------------------\n\n/**\n * Characters per token — context-math's CHARS_PER_TOKEN, the one estimator\n * every surface uses (handoff size, fit badges, this hint). A rough\n * English/code average, and rough is the point: the real count is the\n * provider's, which this app does not have before the turn runs. Every\n * surface that shows this figure marks it `≈` and never presents it as\n * measured (DESIGN §6, VERSE-TELEMETRY-V2).\n */\nexport function estimateTokens(text: string): number {\n const trimmed = text.trim();\n if (trimmed.length === 0) return 0;\n return Math.max(1, Math.ceil(trimmed.length / CHARS_PER_TOKEN));\n}\n\n/** `over` = the projection is past the whole window, not just the compaction point. */\nexport type CostTone = 'warn' | 'danger' | 'over';\n\nexport interface CostHint {\n /** Estimated tokens for the draft itself. */\n draftTokens: number;\n /** Occupancy this message would push the session to (current + draft), in tokens. */\n projectedTokens: number;\n /** …as a percent of the context WINDOW (the meter's track), unclamped. */\n projectedPercent: number;\n /** Where the CLI auto-compacts, when known. */\n autoCompactAt: number | null;\n /** True when the projection reaches the compaction point: the CLI compacts before or during this turn. */\n pastCompaction: boolean;\n tone: CostTone;\n /** False when the current occupancy is itself an upper bound (codex before its rollout is read). */\n exact: boolean;\n}\n\n/** The session's context budget as the composer sees it (verse-model `sessionContextBudget`). */\nexport interface CostBudget {\n contextTokens: number | null | undefined;\n contextWindow: number | null | undefined;\n autoCompactAt?: number | null;\n exact?: boolean;\n}\n\n/**\n * What sending this draft would do to the context — or null when the window\n * is unknown, the box is empty, or the session stays comfortable.\n *\n * The tones are context-math `occupancy()`'s — measured against the\n * COMPACTION point when it is known (warn from 80%, danger from 95%), else\n * against the window — which is exactly what colours the ContextMeter, so the\n * hint and the meter can never disagree about when things are getting tight.\n */\nexport function costHint(text: string, budget: CostBudget): CostHint | null {\n const draftTokens = estimateTokens(text);\n if (draftTokens === 0) return null;\n const window = typeof budget.contextWindow === 'number' && budget.contextWindow > 0 ? budget.contextWindow : null;\n if (window === null) return null;\n const used = typeof budget.contextTokens === 'number' && Number.isFinite(budget.contextTokens) ? Math.max(0, budget.contextTokens) : 0;\n const projected = occupancy({\n contextTokens: used + draftTokens,\n contextWindow: window,\n autoCompactAt: budget.autoCompactAt ?? null,\n contextTokensExact: budget.exact,\n });\n if (projected.tone === 'ok' || projected.tone === 'unknown') return null;\n return {\n draftTokens,\n projectedTokens: projected.tokens,\n projectedPercent: Math.min(999, Math.round((projected.ofWindow ?? 0) * 100)),\n autoCompactAt: projected.autoCompactAt,\n pastCompaction: projected.ofCompaction !== null && projected.ofCompaction >= 1,\n tone: projected.tone,\n exact: projected.exact,\n };\n}\n","/**\n * data/auth-store.ts — the read-session + mutation-token authority for the\n * whole app. Mirrors the two-token model in src/core/web/server.ts exactly:\n *\n * READ authority — exchanged via POST /api/session (raw readToken + a\n * per-tab client proof) for an HttpOnly cookie good\n * for 15 minutes. The raw read token is remembered in\n * MEMORY ONLY (never any storage) for the tab's\n * lifetime so the 15-minute ticket can be renewed\n * silently when a GET/SSE comes back 401 — a live\n * chat must not turn into \"paste a 64-hex token every\n * 15 minutes\". The client proof is not a secret by\n * itself (it has no authority without the signed\n * cookie) and is kept in sessionStorage so a page\n * reload can keep using the still-valid cookie instead\n * of forcing re-entry.\n *\n * MUTATION authority — the raw x-ashlr-token header, required on every\n * mutating call, NEVER exchanged for a session and\n * NEVER persisted to any storage. Held in memory only,\n * for a bounded \"hold\" window, so approving a run of\n * proposals isn't one window.prompt() per click. The\n * hold is cleared on tab close, explicit lock, or\n * idle timeout.\n *\n * This module has no React dependency — it is a plain external store\n * consumed via useSyncExternalStore in hooks.ts, per the same pattern used\n * for the query cache.\n */\n\nimport { evictAll, invalidatePrefix } from './cache.js';\n// The composer owns its own storage format, so the key names live with it\n// rather than being duplicated here where they could drift.\nimport { clearComposerMemory } from '../routes/verse/chat/composer-state.js';\n\nconst READ_CLIENT_STORAGE_KEY = 'ashlr.readClientProof.v1';\nconst READ_CLIENT_RE = /^[a-f0-9]{64}$/;\nconst MUTATION_HOLD_MS = 20 * 60 * 1000; // 20 minutes of inactivity clears the hold.\n\nexport type AuthPhase = 'checking' | 'unauthenticated' | 'authenticated';\n\ninterface AuthState {\n phase: AuthPhase;\n /** True once a session check has actually completed (vs. still booting). */\n checked: boolean;\n mutationTokenHeldUntil: number | null;\n}\n\nfunction randomHex64(): string {\n const bytes = new Uint8Array(32);\n crypto.getRandomValues(bytes);\n return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');\n}\n\nfunction loadOrCreateClientProof(): string {\n try {\n const existing = sessionStorage.getItem(READ_CLIENT_STORAGE_KEY);\n if (existing && READ_CLIENT_RE.test(existing)) return existing;\n } catch {\n // sessionStorage unavailable (privacy mode, etc.) — fall through to an\n // in-memory-only proof for this page life.\n }\n const proof = randomHex64();\n try {\n sessionStorage.setItem(READ_CLIENT_STORAGE_KEY, proof);\n } catch {\n /* best-effort persistence only */\n }\n return proof;\n}\n\nlet clientProof = loadOrCreateClientProof();\n\n/** In-memory only. Never touches storage. Cleared by clearMutationToken(). */\nlet mutationToken: string | null = null;\nlet mutationHoldTimer: ReturnType<typeof setTimeout> | null = null;\n/**\n * The mutation token came from the desktop host (window.__ASHLR_TOKENS__).\n * The operator never saw it, so an idle timeout must re-arm the hold rather\n * than clear it — a cleared hold would ask for a token nobody can paste.\n */\nlet mutationHeldByHost = false;\n\n/**\n * Last read token that the server accepted (typed or host-injected). Memory\n * only, same trust boundary as the mutation hold; used to renew the cookie\n * silently on 401. Dropped on explicit disconnect or when renewal fails.\n */\nlet rememberedReadToken: string | null = null;\nlet renewal: Promise<boolean> | null = null;\n/** Desktop-injected tokens kept for re-adoption (see the host-injected section below). */\nlet hostTokens: InjectedTokens | null = null;\n\nlet state: AuthState = { phase: 'checking', checked: false, mutationTokenHeldUntil: null };\nconst listeners = new Set<() => void>();\n\nfunction emit(): void {\n for (const l of listeners) l();\n}\n\nfunction setState(patch: Partial<AuthState>): void {\n state = { ...state, ...patch };\n emit();\n}\n\nexport function subscribeAuth(listener: () => void): () => void {\n listeners.add(listener);\n return () => listeners.delete(listener);\n}\n\nexport function getAuthSnapshot(): AuthState {\n return state;\n}\n\nexport function getReadClientProof(): string {\n return clientProof;\n}\n\n/** Regenerate the client proof (e.g. after an explicit \"forget this device\"). */\nexport function resetReadClientProof(): string {\n clientProof = randomHex64();\n try {\n sessionStorage.setItem(READ_CLIENT_STORAGE_KEY, clientProof);\n } catch {\n /* best-effort */\n }\n return clientProof;\n}\n\n/**\n * Exchange a raw read token for a session cookie. Throws on failure with a\n * message safe to show the operator. The raw token never leaves this call.\n */\nexport async function establishReadSession(rawReadToken: string): Promise<void> {\n const trimmed = rawReadToken.trim();\n if (!/^[a-f0-9]{64}$/.test(trimmed)) {\n throw new Error('That does not look like a read token — expected 64 hex characters.');\n }\n const res = await fetch('/api/session', {\n method: 'POST',\n credentials: 'same-origin',\n headers: {\n 'x-ashlr-token': trimmed,\n 'x-ashlr-read-client': clientProof,\n },\n });\n if (res.status === 204) {\n rememberedReadToken = trimmed;\n setState({ phase: 'authenticated', checked: true });\n return;\n }\n if (res.status === 401) {\n throw new Error('Server rejected that token. Double-check the read token printed by this server at startup.');\n }\n throw new Error(`Unexpected response establishing session (HTTP ${res.status}).`);\n}\n\nexport async function clearReadSession(): Promise<void> {\n try {\n await fetch('/api/session', {\n method: 'DELETE',\n credentials: 'same-origin',\n headers: { 'x-ashlr-read-client': clientProof },\n });\n } catch {\n /* best-effort */\n }\n rememberedReadToken = null;\n hostTokens = null;\n expireNow();\n}\n\nfunction expireNow(): void {\n clearMutationToken();\n evictAll();\n // `evictAll` clears the query cache so nothing stale leaks into the next\n // session's first paint. The composer's drafts and sent-message history live\n // in localStorage, not in that cache, and they are verbatim prompt text —\n // exactly the kind of content this wipe exists for.\n clearComposerMemory();\n setState({ phase: 'unauthenticated', checked: true });\n}\n\n/**\n * Called by data/client.ts whenever any authenticated GET comes back 401\n * (and by sse.ts on `session-expired`). With a remembered read token the\n * cookie is re-issued silently and the phase never flips; the failed call's\n * own retry/reconnect (SSE backoff, the next refetch) then succeeds. Only\n * when renewal fails (server restarted with fresh tokens) does the gate show.\n */\nexport function reportSessionExpired(): void {\n if (state.phase !== 'authenticated') return;\n if (rememberedReadToken) {\n void renewReadSession();\n return;\n }\n expireNow();\n}\n\n/** Re-exchange the remembered read token for a fresh cookie; one flight at a time. */\nexport function renewReadSession(): Promise<boolean> {\n if (renewal) return renewal;\n const token = rememberedReadToken;\n if (!token) return Promise.resolve(false);\n renewal = (async () => {\n try {\n await establishReadSession(token);\n // Whatever 401ed while the ticket was lapsed refetches against the new cookie.\n invalidatePrefix('');\n return true;\n } catch {\n rememberedReadToken = null;\n hostTokens = null;\n expireNow();\n return false;\n } finally {\n renewal = null;\n }\n })();\n return renewal;\n}\n\n/** Test hygiene: forget the remembered read token and host tokens without a network call. */\nexport function forgetRememberedTokens(): void {\n rememberedReadToken = null;\n hostTokens = null;\n renewal = null;\n}\n\nexport function markCheckComplete(authenticated: boolean): void {\n setState({ phase: authenticated ? 'authenticated' : 'unauthenticated', checked: true });\n}\n\n// ---------------------------------------------------------------------------\n// Host-injected tokens (desktop wrapper). The Tauri shell starts the server\n// itself and hands both tokens to the page through an initialization script\n// (`window.__ASHLR_TOKENS__`, see desktop/README.md) so the operator never\n// pastes them. The object is removed from `window` before either token is\n// used and kept in this module's memory instead (`hostTokens`), because the\n// desktop operator has no terminal to paste from: when the 15-minute cookie\n// lapses or the gate re-mounts, adoption re-runs from memory. The read token\n// is exchanged for the cookie exactly like a typed one and the mutation token\n// goes into the memory-only hold. Nothing is persisted and nothing is logged.\n// ---------------------------------------------------------------------------\n\nexport interface InjectedTokens {\n readToken: string;\n /** Mutation token; absent or null when the host started the server without dispatch. */\n token?: string | null;\n}\n\ndeclare global {\n interface Window {\n __ASHLR_TOKENS__?: InjectedTokens;\n }\n}\n\n/**\n * Take host-injected tokens: from `window` (cleared from the page on first\n * sight, remembered here) or, on later calls, from memory.\n */\nexport function takeInjectedTokens(): InjectedTokens | null {\n const injected = window.__ASHLR_TOKENS__;\n if (injected && typeof injected === 'object') {\n try {\n delete window.__ASHLR_TOKENS__;\n } catch {\n window.__ASHLR_TOKENS__ = undefined;\n }\n if (typeof injected.readToken === 'string') {\n hostTokens = { readToken: injected.readToken, ...(typeof injected.token === 'string' ? { token: injected.token } : {}) };\n }\n }\n return hostTokens ? { ...hostTokens } : null;\n}\n\n/**\n * Establish the read session (and set the mutation hold) from host-injected\n * tokens. Resolves true when a session was established; false when nothing\n * was injected or the server rejected the read token (the caller then falls\n * back to the normal SessionGate flow — the failure is never surfaced with\n * the token in it).\n */\nexport async function adoptInjectedTokens(): Promise<boolean> {\n const injected = takeInjectedTokens();\n if (!injected) return false;\n try {\n await establishReadSession(injected.readToken);\n } catch {\n // Wrong or stale tokens are not worth retrying from memory.\n hostTokens = null;\n return false;\n }\n if (injected.token && /^[a-f0-9]{64}$/.test(injected.token.trim())) setMutationToken(injected.token.trim(), { host: true });\n return true;\n}\n\n// ---------------------------------------------------------------------------\n// Mutation token — session-scoped hold, memory-only.\n// ---------------------------------------------------------------------------\n\nexport function getMutationToken(): string | null {\n return mutationToken;\n}\n\nexport function hasMutationHold(): boolean {\n return mutationToken !== null;\n}\n\nfunction armIdleClear(): void {\n if (mutationHoldTimer) clearTimeout(mutationHoldTimer);\n mutationHoldTimer = setTimeout(() => {\n // A host-held token has no operator to re-paste it: keep the hold alive.\n if (mutationHeldByHost && mutationToken !== null) touchMutationHold();\n else clearMutationToken();\n }, MUTATION_HOLD_MS);\n}\n\n/**\n * Set the mutation token for this tab's session hold. Never persisted.\n * `host` marks a token injected by the desktop wrapper (see adoptInjectedTokens).\n */\nexport function setMutationToken(token: string, opts: { host?: boolean } = {}): void {\n mutationToken = token;\n mutationHeldByHost = opts.host === true;\n const heldUntil = Date.now() + MUTATION_HOLD_MS;\n setState({ mutationTokenHeldUntil: heldUntil });\n armIdleClear();\n}\n\n/** Extend the hold window on real use (called after every mutating call). */\nexport function touchMutationHold(): void {\n if (mutationToken === null) return;\n const heldUntil = Date.now() + MUTATION_HOLD_MS;\n setState({ mutationTokenHeldUntil: heldUntil });\n armIdleClear();\n}\n\nexport function clearMutationToken(): void {\n mutationToken = null;\n mutationHeldByHost = false;\n if (mutationHoldTimer) {\n clearTimeout(mutationHoldTimer);\n mutationHoldTimer = null;\n }\n setState({ mutationTokenHeldUntil: null });\n}\n","/**\n * data/sse.ts — the live channel. One EventSource against /api/events for\n * the whole app, fanning out NAMED events (see api.ts handleSseEvents:\n * 'runs' | 'swarms' | 'inbox' | 'daemon' | 'daemon-observation' |\n * 'fleet-activity-ping' | 'fleet-activity-observation' | 'snapshot') to\n * subscribers, and invalidating the matching cache key so any mounted\n * useQuery(key) re-fetches in the background.\n *\n * The server polls disk at a fixed interval (api.ts SSE_POLL_MS, ~1.5s at\n * time of writing) — that is the real latency floor for \"live\". This layer\n * does not, and should not, try to beat that; it just makes sure the UI\n * never polls MORE often than the server already pushes.\n */\nimport { getReadClientProof, getAuthSnapshot, reportSessionExpired, subscribeAuth } from './auth-store.js';\nimport { invalidate, invalidatePrefix } from './cache.js';\nimport { isScopedConsolePath } from '../app/console-mode.js';\n\nexport const SSE_EVENT_NAMES = [\n 'runs',\n 'swarms',\n 'inbox',\n 'daemon',\n 'daemon-observation',\n 'fleet-activity-ping',\n 'fleet-activity-observation',\n 'snapshot',\n 'session-expired',\n] as const;\nexport type SseEventName = (typeof SSE_EVENT_NAMES)[number];\n\n/** Cache keys (see data/queries.ts) invalidated when a given SSE event fires. */\nconst EVENT_TO_CACHE_KEYS: Record<SseEventName, string[]> = {\n runs: ['runs'],\n swarms: ['swarms'],\n inbox: ['inbox'],\n // 'daemon' was previously mapped but nothing subscribed to a 'daemon'-keyed\n // query (control/daemon didn't exist yet) — now daemonObservationQuery\n // (data/queries.ts) does. Also refreshing 'control-snapshot' here so\n // /control/daemon and /control/security (both read off controlSnapshotQuery)\n // stay live on the same daemon-tick cadence instead of only on page load.\n // `/api/verse/control` and `/api/verse/audit` ARE the daemon projection, so\n // they ride the same signal. Without this the Autonomy cockpit — running\n // state, last tick outcome, today's spend against the cap — was frozen at\n // whatever was true when the section mounted, and DEFAULT_QUERY_FRESH_MS\n // then made a rail switch away and back inside 10s a no-op too.\n //\n // Deliberately NOT a coarse `verse-` prefix sweep: `verse-usage-accounts`\n // and `verse-usage-local-models` spawn provider probes, and firing those on\n // every daemon tick would turn a liveness fix into a process storm. Those\n // two poll on their own visibility-gated 30s cadence in UsageSection,\n // matching the collector's cycle.\n daemon: ['daemon', 'control-snapshot', 'verse-control', 'verse-usage-control', 'verse-caps'],\n 'daemon-observation': ['daemon', 'control-snapshot', 'verse-control', 'verse-usage-control', 'verse-caps'],\n // fleet-activity pings are the closest live signal fleet state changed;\n // 'fleet' (fleetStatusQuery, /control/fleet's queue depth + lease health)\n // has no dedicated SSE event of its own, so it rides these instead of only\n // refreshing on mount.\n 'fleet-activity-ping': ['fleet-activity', 'fleet'],\n 'fleet-activity-observation': ['fleet-activity', 'fleet'],\n // Agent OS snapshots are immutable records but their current authenticated\n // projection advances alongside the server's general snapshot signal.\n snapshot: ['dashboard-snapshot', 'agent-os-snapshot'],\n 'session-expired': [],\n};\n\ntype RawListener = (payload: unknown) => void;\nconst rawListeners = new Map<SseEventName, Set<RawListener>>();\n\nlet source: EventSource | null = null;\nlet reconnectTimer: ReturnType<typeof setTimeout> | null = null;\nlet reconnectAttempt = 0;\n\nfunction backoffMs(): number {\n // 1s, 2s, 4s, 8s, capped at 15s — the server's own poll floor is ~1.5s so\n // there is no benefit reconnecting faster than that even on attempt 0.\n return Math.min(1000 * 2 ** reconnectAttempt, 15_000);\n}\n\nfunction teardown(): void {\n if (source) {\n source.close();\n source = null;\n }\n if (reconnectTimer) {\n clearTimeout(reconnectTimer);\n reconnectTimer = null;\n }\n}\n\nfunction scheduleReconnect(): void {\n if (reconnectTimer) return;\n reconnectTimer = setTimeout(() => {\n reconnectTimer = null;\n reconnectAttempt += 1;\n connect();\n }, backoffMs());\n}\n\nfunction connect(): void {\n // Shared hooks import this module, but the dedicated console has no global\n // Hub event channel. Check on every reconnect, not just the initial import.\n if (isScopedConsolePath()) { teardown(); return; }\n if (getAuthSnapshot().phase !== 'authenticated') return;\n teardown();\n\n const url = `/api/events?client=${encodeURIComponent(getReadClientProof())}`;\n const es = new EventSource(url, { withCredentials: true });\n source = es;\n\n es.onopen = () => {\n reconnectAttempt = 0;\n };\n\n for (const name of SSE_EVENT_NAMES) {\n es.addEventListener(name, (evt) => {\n const messageEvent = evt as MessageEvent<string>;\n let payload: unknown = undefined;\n try {\n payload = JSON.parse(messageEvent.data);\n } catch {\n payload = undefined;\n }\n for (const key of EVENT_TO_CACHE_KEYS[name]) invalidate(key);\n if (name === 'inbox') {\n // Parameterized inbox queries (one cache key per filter combo / per\n // proposal id) aren't in EVENT_TO_CACHE_KEYS's static map — sweep\n // both prefixes so the list (any filter) and any open detail view\n // (proposalDetailQuery, data/queries.ts) live-update on the same\n // 'inbox' event as the unfiltered badge.\n invalidatePrefix('inbox-list:');\n invalidatePrefix('proposal-detail-');\n }\n if (name === 'session-expired') reportSessionExpired();\n const listeners = rawListeners.get(name);\n if (listeners) for (const l of listeners) l(payload);\n });\n }\n\n es.onerror = () => {\n // EventSource auto-retries on its own, but its built-in retry delay is\n // opaque and can hammer a server that just lost its read session. Close\n // it and drive reconnection ourselves so we stop entirely once\n // auth-store reports the session as expired (see the subscribeAuth\n // wiring at the bottom of this file).\n es.close();\n if (source === es) source = null;\n if (getAuthSnapshot().phase === 'authenticated') scheduleReconnect();\n };\n}\n\n/** Subscribe to one named SSE event's raw payload (rarely needed directly —\n * most views should prefer useQuery + the cache-key mapping above). */\nexport function onSseEvent(name: SseEventName, listener: RawListener): () => void {\n let set = rawListeners.get(name);\n if (!set) {\n set = new Set();\n rawListeners.set(name, set);\n }\n set.add(listener);\n return () => set!.delete(listener);\n}\n\n// Connect once authenticated; disconnect the instant the session drops so a\n// stale connection never masks a 401 the rest of the app needs to react to.\nsubscribeAuth(() => {\n const phase = getAuthSnapshot().phase;\n if (phase === 'authenticated') {\n reconnectAttempt = 0;\n connect();\n } else {\n teardown();\n }\n});\n","/**\n * data/theme-store.ts — the theme preference, extracted out of Topbar.tsx\n * so the command palette's \"Toggle theme\" action can read/write the same\n * source of truth Topbar's own button uses (same module-level state +\n * subscribe pattern as auth-store.ts — commands.ts's `run` closures have no\n * React context to call useState from, so this can't live in a component).\n */\n\nexport type ThemePreference = 'system' | 'light' | 'dark';\n\nconst THEME_STORAGE_KEY = 'ashlr.theme.v1';\n\nfunction applyTheme(pref: ThemePreference): void {\n const root = document.documentElement;\n if (pref === 'system') root.removeAttribute('data-theme');\n else root.setAttribute('data-theme', pref);\n}\n\nfunction loadTheme(): ThemePreference {\n try {\n const v = localStorage.getItem(THEME_STORAGE_KEY);\n if (v === 'light' || v === 'dark' || v === 'system') return v;\n } catch {\n /* ignore */\n }\n return 'system';\n}\n\nlet theme: ThemePreference = loadTheme();\napplyTheme(theme);\n\nconst listeners = new Set<() => void>();\n\nfunction emit(): void {\n for (const l of listeners) l();\n}\n\nexport function subscribeTheme(listener: () => void): () => void {\n listeners.add(listener);\n return () => listeners.delete(listener);\n}\n\nexport function getTheme(): ThemePreference {\n return theme;\n}\n\nexport function setTheme(next: ThemePreference): void {\n theme = next;\n applyTheme(theme);\n try {\n localStorage.setItem(THEME_STORAGE_KEY, theme);\n } catch {\n /* best-effort persistence only */\n }\n emit();\n}\n\nexport function cycleTheme(): void {\n setTheme(theme === 'system' ? 'light' : theme === 'light' ? 'dark' : 'system');\n}\n","/**\n * data/appearance-store.ts — the operator's appearance preferences: the\n * \"Linux for agentic engineering\" part of the design language\n * (docs/VERSE-DESIGN-V2.md §3). Theme, accent, density, display face,\n * radius, and motion, applied live by writing custom properties and\n * data-attributes on <html>.\n *\n * Same shape as ./theme-store.ts on purpose — module-level state + a\n * listener set, zero React — so it is unit-testable without a DOM renderer\n * and usable from non-component code (the command palette's closures, the\n * rail's quick-toggle). data/hooks.ts binds it into React via\n * `useAppearance()`, exactly as it does for the theme.\n *\n * THEME IS NOT FORKED. `theme-store.ts` remains the single authority for the\n * light/dark/system preference (key `ashlr.theme.v1`, already read by the\n * topbar and the command palette); this module re-exposes it as one field of\n * the appearance snapshot and delegates writes to `setTheme`. Two stores\n * writing `data-theme` would race; one store that owns it does not.\n *\n * Everything is applied at MODULE LOAD (before React's first render, since\n * hooks.ts imports this and every view imports hooks.ts), so the app never\n * paints the default accent for a frame before swapping to the chosen one.\n */\nimport {\n getTheme,\n setTheme,\n subscribeTheme,\n type ThemePreference,\n} from './theme-store.js';\n\nexport type { ThemePreference };\n\nexport type Density = 'comfortable' | 'compact';\n\n/**\n * How large the whole interface is drawn. Scales TYPE AND SPACING TOGETHER\n * (design/tokens.css multiplies its type ramp by --ui-text-scale and its\n * spacing/geometry ramp by --ui-scale), because scaling type alone produces\n * big text in cramped boxes — worse to read than the default, not better.\n *\n * ORTHOGONAL TO `density`, not a replacement for it: display size answers\n * \"how big is this interface\", density answers \"how tightly are its rows\n * packed\". They compose, which is why tokens.css expresses the scale as a\n * multiplier rather than a second table of literal heights.\n *\n * There is no step below 'default' on purpose — see the [data-ui-scale]\n * block in tokens.css for why (the 12px body floor, and Density already\n * being the tightening control).\n */\nexport type UiScale = 'default' | 'large' | 'xlarge';\n\n/**\n * The operator's motion choice. Tri-state ON PURPOSE.\n *\n * A boolean cannot distinguish \"I want full motion\" from \"I never touched\n * this\", and `persist()` writes the whole snapshot on every appearance change\n * — so dragging the accent hue once used to store `reduceMotion: false` and\n * permanently override the OS `prefers-reduced-motion` preference, because\n * `applyAppearance` then wrote `data-motion=\"full\"`, which is exactly the\n * value every `:root:not([data-motion=\"full\"])` guard exists to defer to.\n *\n * 'system' removes the attribute entirely and lets the media queries decide,\n * which is what DESIGN-V2 §3 and §6 ask for by default.\n */\nexport type MotionPreference = 'system' | 'reduce' | 'full';\nexport type DisplayFont = 'grotesk' | 'ui' | 'mono';\nexport type RadiusScale = 'sharp' | 'default' | 'soft';\n\nexport interface Appearance {\n /** Delegated to theme-store.ts — persisted under its own key. */\n theme: ThemePreference;\n /** Accent hue 0-360. */\n accentH: number;\n /** Accent saturation 0-100 (%). */\n accentS: number;\n /** Accent lightness, clamped to a readable band (%). */\n accentL: number;\n density: Density;\n /** How large the whole interface is drawn. Type and spacing together. */\n uiScale: UiScale;\n displayFont: DisplayFont;\n radius: RadiusScale;\n /** The stored choice: 'system' follows `prefers-reduced-motion`. */\n motion: MotionPreference;\n /**\n * DERIVED, never stored — what the Settings switch shows and what the app\n * behaves as right now. `setAppearance({ reduceMotion })` is the only call\n * that turns this back into an explicit 'reduce'/'full'.\n */\n reduceMotion: boolean;\n}\n\n/**\n * The persisted subset. `theme` lives in theme-store, and `reduceMotion` is\n * derived from `motion` — storing it is what created the override bug.\n */\ntype StoredAppearance = Omit<Appearance, 'theme' | 'reduceMotion'>;\n\nconst STORAGE_KEY = 'ashlr.verse.appearance.v1';\n\n/**\n * Accent lightness band. Below ~30% the accent stops reading as a color on a\n * dark canvas; above ~78% it disappears on a light one. The hue and\n * saturation are unconstrained — this is the one axis where a free slider\n * can make the app genuinely unusable.\n */\nconst ACCENT_L_MIN = 30;\nconst ACCENT_L_MAX = 78;\n\n/** Must match the bare `:root` values in design/tokens.css. */\nexport const DEFAULT_ACCENT = { h: 245, s: 72, l: 58 } as const;\n\nexport interface AccentPreset {\n id: string;\n label: string;\n h: number;\n s: number;\n l: number;\n}\n\n/**\n * Eight presets (design doc §3). Each was checked with design/contrast.ts so\n * its derived link color clears 4.5:1 in BOTH themes — the hue slider can\n * still be dragged anywhere, and the Settings panel warns when it lands\n * somewhere unreadable.\n */\nexport const ACCENT_PRESETS: readonly AccentPreset[] = [\n { id: 'indigo', label: 'Indigo', h: 245, s: 72, l: 58 },\n { id: 'blue', label: 'Blue', h: 212, s: 80, l: 50 },\n { id: 'cyan', label: 'Cyan', h: 190, s: 80, l: 40 },\n { id: 'teal', label: 'Teal', h: 168, s: 70, l: 38 },\n { id: 'green', label: 'Green', h: 145, s: 60, l: 38 },\n { id: 'amber', label: 'Amber', h: 38, s: 80, l: 42 },\n { id: 'rose', label: 'Rose', h: 348, s: 72, l: 50 },\n { id: 'violet', label: 'Violet', h: 278, s: 66, l: 55 },\n] as const;\n\nfunction prefersReducedMotion(): boolean {\n try {\n return typeof window !== 'undefined' && typeof window.matchMedia === 'function'\n ? window.matchMedia('(prefers-reduced-motion: reduce)').matches\n : false;\n } catch {\n // Some embedded webviews throw on an unsupported media feature.\n return false;\n }\n}\n\nexport function defaultAppearance(): Appearance {\n return {\n theme: 'system',\n accentH: DEFAULT_ACCENT.h,\n accentS: DEFAULT_ACCENT.s,\n accentL: DEFAULT_ACCENT.l,\n density: 'comfortable',\n uiScale: 'default',\n displayFont: 'grotesk',\n radius: 'default',\n motion: 'system',\n reduceMotion: prefersReducedMotion(),\n };\n}\n\nfunction isMotion(v: unknown): v is MotionPreference {\n return v === 'system' || v === 'reduce' || v === 'full';\n}\n\n/** What 'system' resolves to right now; an explicit choice answers itself. */\nfunction resolveReduceMotion(motion: MotionPreference): boolean {\n return motion === 'reduce' ? true : motion === 'full' ? false : prefersReducedMotion();\n}\n\nconst clamp = (n: number, min: number, max: number): number => Math.min(max, Math.max(min, n));\n\nfunction isDensity(v: unknown): v is Density {\n return v === 'comfortable' || v === 'compact';\n}\nfunction isUiScale(v: unknown): v is UiScale {\n return v === 'default' || v === 'large' || v === 'xlarge';\n}\nfunction isDisplayFont(v: unknown): v is DisplayFont {\n return v === 'grotesk' || v === 'ui' || v === 'mono';\n}\nfunction isRadius(v: unknown): v is RadiusScale {\n return v === 'sharp' || v === 'default' || v === 'soft';\n}\n\n/** Coerce anything (stored JSON, a caller's patch) into a valid Appearance. */\nfunction normalize(base: Appearance, patch: Partial<Appearance> | Partial<StoredAppearance>): Appearance {\n const merged = { ...base, ...patch } as Appearance;\n // `reduceMotion` in a PATCH is the Settings switch and is authoritative —\n // it is the one call that converts the derived boolean back into a stored\n // choice. `reduceMotion` in a stored blob is not a choice at all, which is\n // why load() strips it before it ever gets here.\n const asked = (patch as Partial<Appearance>).reduceMotion;\n const motion: MotionPreference = isMotion((patch as Partial<Appearance>).motion)\n ? ((patch as Partial<Appearance>).motion as MotionPreference)\n : typeof asked === 'boolean'\n ? (asked ? 'reduce' : 'full')\n : isMotion(base.motion)\n ? base.motion\n : 'system';\n return {\n theme: merged.theme === 'light' || merged.theme === 'dark' ? merged.theme : 'system',\n accentH: Number.isFinite(merged.accentH) ? ((Math.round(merged.accentH) % 360) + 360) % 360 : DEFAULT_ACCENT.h,\n accentS: Number.isFinite(merged.accentS) ? clamp(Math.round(merged.accentS), 0, 100) : DEFAULT_ACCENT.s,\n accentL: Number.isFinite(merged.accentL)\n ? clamp(Math.round(merged.accentL), ACCENT_L_MIN, ACCENT_L_MAX)\n : DEFAULT_ACCENT.l,\n density: isDensity(merged.density) ? merged.density : 'comfortable',\n uiScale: isUiScale(merged.uiScale) ? merged.uiScale : 'default',\n displayFont: isDisplayFont(merged.displayFont) ? merged.displayFont : 'grotesk',\n radius: isRadius(merged.radius) ? merged.radius : 'default',\n motion,\n reduceMotion: resolveReduceMotion(motion),\n };\n}\n\nfunction load(): Appearance {\n const base = defaultAppearance();\n let stored: Partial<StoredAppearance> = {};\n try {\n const raw = localStorage.getItem(STORAGE_KEY);\n if (raw) {\n const parsed: unknown = JSON.parse(raw);\n if (parsed && typeof parsed === 'object') stored = parsed as Partial<StoredAppearance>;\n }\n } catch {\n // Unavailable storage, quota errors, or corrupt JSON all mean the same\n // thing here: fall back to defaults rather than failing to boot.\n }\n // Drop any `reduceMotion` a pre-tri-state build left behind: it was written\n // by every unrelated appearance change, so it records nothing the operator\n // decided. Without `motion`, the media query decides — as it should.\n const { reduceMotion: _legacy, ...rest } = stored as Partial<StoredAppearance> & { reduceMotion?: unknown };\n return normalize(base, { ...rest, theme: getTheme() });\n}\n\nfunction persist(value: Appearance): void {\n try {\n // theme is deliberately excluded — theme-store.ts owns its own key.\n // reduceMotion is excluded because it is derived: persisting it is what\n // let an accent drag silently override the OS preference.\n const { theme: _theme, reduceMotion: _derived, ...rest } = value;\n localStorage.setItem(STORAGE_KEY, JSON.stringify(rest satisfies StoredAppearance));\n } catch {\n /* best-effort persistence only */\n }\n}\n\nconst FONT_VAR: Record<DisplayFont, string | null> = {\n // null = remove the inline override and fall back to the token default.\n grotesk: null,\n ui: 'var(--font-ui)',\n mono: 'var(--font-mono)',\n};\n\n/**\n * Write the settings onto <html>. The accent is written as the three raw\n * channels tokens.css derives its whole ramp from, so one paint recolors\n * links, focus rings, primary buttons, meters and charts together.\n */\nexport function applyAppearance(value: Appearance): void {\n if (typeof document === 'undefined') return;\n const root = document.documentElement;\n root.style.setProperty('--accent-h', String(value.accentH));\n root.style.setProperty('--accent-s', `${value.accentS}%`);\n root.style.setProperty('--accent-l', `${value.accentL}%`);\n\n const font = FONT_VAR[value.displayFont];\n if (font === null) root.style.removeProperty('--font-display');\n else root.style.setProperty('--font-display', font);\n\n root.setAttribute('data-density', value.density);\n // The display size is an ATTRIBUTE, not an inline custom property, for the\n // same reason the theme is: tokens.css owns the numbers, and a\n // `[data-ui-scale]` block can be read, tested and retuned in the stylesheet\n // that every other token lives in. Writing `--ui-scale` inline here would\n // put half the design system in a TypeScript file.\n root.setAttribute('data-ui-scale', value.uiScale);\n root.setAttribute('data-radius', value.radius);\n // 'system' removes the attribute so the `prefers-reduced-motion` media\n // queries decide on their own. \"full\" is meaningful, not a no-op: it opts an\n // operator explicitly OUT of the OS preference (tokens.css and the four\n // Verse stylesheets guard their media query with :not([data-motion=\"full\"])),\n // which is precisely why it must never be written by default.\n if (value.motion === 'system') root.removeAttribute('data-motion');\n else root.setAttribute('data-motion', value.motion);\n}\n\nlet current: Appearance = load();\napplyAppearance(current);\n\nconst listeners = new Set<() => void>();\n\nfunction emit(): void {\n for (const l of listeners) l();\n}\n\n// The theme can also be changed from the rail's quick-toggle and the command\n// palette (both go through theme-store). Mirror those into the snapshot so a\n// Settings panel rendered at the same time never shows a stale radio.\nsubscribeTheme(() => {\n if (current.theme === getTheme()) return;\n current = { ...current, theme: getTheme() };\n emit();\n});\n\nexport function subscribeAppearance(listener: () => void): () => void {\n listeners.add(listener);\n return () => listeners.delete(listener);\n}\n\n/** Stable snapshot — a NEW object on every change (useSyncExternalStore uses Object.is). */\nexport function getAppearance(): Appearance {\n return current;\n}\n\n/** Apply and persist a partial change. Unknown/out-of-range values are coerced. */\nexport function setAppearance(patch: Partial<Appearance>): void {\n const next = normalize(current, patch);\n const themeChanged = next.theme !== current.theme;\n current = next;\n applyAppearance(current);\n persist(current);\n // Delegated last so theme-store's own listeners see the final snapshot.\n if (themeChanged) setTheme(current.theme);\n emit();\n}\n\n/** Back to the shipped defaults, including the theme and the stored blob. */\nexport function resetAppearance(): void {\n try {\n localStorage.removeItem(STORAGE_KEY);\n } catch {\n /* best-effort */\n }\n current = defaultAppearance();\n applyAppearance(current);\n setTheme('system');\n emit();\n}\n\n/** True when nothing has been customized — drives the \"Reset\" affordance. */\nexport function isDefaultAppearance(value: Appearance = current): boolean {\n const base = defaultAppearance();\n return (\n value.theme === base.theme &&\n value.accentH === base.accentH &&\n value.accentS === base.accentS &&\n value.accentL === base.accentL &&\n value.density === base.density &&\n value.uiScale === base.uiScale &&\n value.displayFont === base.displayFont &&\n value.radius === base.radius &&\n value.motion === base.motion\n );\n}\n\n/** The preset whose channels match the current accent, if any. */\nexport function matchingAccentPreset(value: Appearance = current): AccentPreset | null {\n return (\n ACCENT_PRESETS.find((p) => p.h === value.accentH && p.s === value.accentS && p.l === value.accentL) ?? null\n );\n}\n\nexport const ACCENT_LIGHTNESS_RANGE = { min: ACCENT_L_MIN, max: ACCENT_L_MAX } as const;\nexport const APPEARANCE_STORAGE_KEY = STORAGE_KEY;\n","/**\n * data/hooks.ts — React bindings over the framework-free stores in\n * cache.ts / auth-store.ts / sse.ts. This is the only file in the data\n * layer that imports React; every module underneath it stays plain\n * TypeScript so it's trivially unit-testable without a DOM.\n */\nimport { useCallback, useEffect, useSyncExternalStore } from 'react';\nimport { ensureQuery, getQuerySnapshot, refetchQuery, subscribeQuery, type QueryEntry } from './cache.js';\nimport type { QueryDef } from './queries.js';\nimport {\n getAuthSnapshot,\n subscribeAuth,\n type AuthPhase,\n hasMutationHold,\n getMutationToken,\n setMutationToken as storeSetMutationToken,\n touchMutationHold,\n clearMutationToken,\n} from './auth-store.js';\nimport { onSseEvent, type SseEventName } from './sse.js';\nimport { subscribeTheme, getTheme, setTheme, cycleTheme, type ThemePreference } from './theme-store.js';\nimport {\n getAppearance,\n resetAppearance,\n setAppearance,\n subscribeAppearance,\n type Appearance,\n} from './appearance-store.js';\n\n/**\n * How long a cached value is accepted on MOUNT without re-reading it.\n *\n * A section that re-mounts inside this window (rail switch, a parent\n * re-keying, React 18's double-invoked effects in StrictMode) issues no\n * request at all. Ten seconds is short enough that any surface a human\n * revisits deliberately still re-reads, and long enough that the Usage\n * section's seven probe-spawning reads happen ONCE per visit rather than\n * once per mount.\n *\n * This does not make data stale: keys with an SSE event still refresh the\n * instant the server says they changed (sse.ts → invalidate, which is not\n * freshness-gated), and `useRefetch` always ignores it.\n */\nexport const DEFAULT_QUERY_FRESH_MS = 10_000;\n\nexport interface UseQueryOptions {\n /** Override `DEFAULT_QUERY_FRESH_MS` for this subscription. 0 = always fetch. */\n freshMs?: number;\n}\n\n/**\n * Subscribe to a resource. Fetches on mount (and on `key` change) unless the\n * cached value is still fresh, stays subscribed to background refreshes\n * triggered by sse.ts, and NEVER clears `data` while refetching —\n * `status === 'refreshing'` is the signal to show a subtle indicator instead\n * of a skeleton, `status === 'loading'` (no data yet at all) is the only\n * state that should render a skeleton.\n */\nexport function useQuery<T>(def: QueryDef<T>, options?: UseQueryOptions): QueryEntry<T> {\n const freshMs = options?.freshMs ?? DEFAULT_QUERY_FRESH_MS;\n const snapshot = useSyncExternalStore(\n useCallback((listener) => subscribeQuery(def.key, listener), [def.key]),\n () => getQuerySnapshot<T>(def.key),\n () => getQuerySnapshot<T>(def.key),\n );\n\n useEffect(() => {\n // Cache refreshers outlive any one component. Binding the stored fetcher\n // to this component's AbortSignal poisons later SSE invalidations after\n // unmount, so the shared cache owns the request lifetime.\n void ensureQuery(def.key, () => def.fetch(), freshMs);\n // Re-fetch only when the resource identity changes, not on every render.\n // eslint-disable-next-line react-hooks/exhaustive-deps -- def.fetch is re-created per render on purpose; only def.key identifies the resource.\n }, [def.key, freshMs]);\n\n return snapshot;\n}\n\n/**\n * Manually trigger a re-fetch without waiting for SSE — a \"Retry\" button, a\n * \"Refresh\" button, or a poll loop.\n *\n * Ignores freshness always, and joins an in-flight read only while that read\n * is younger than `REFETCH_JOIN_WINDOW_MS`. Both halves matter, because this\n * one hook backs two different callers:\n *\n * - A poll (UniverseView ticks every 3–15s and again on visibilitychange)\n * landing on a read that started milliseconds ago SHOULD coalesce with\n * it; issuing a second request there is pure waste.\n * - A person's click landing on a read that has been running for seconds\n * SHOULD NOT. That was the old behavior: the click joined the running\n * promise and returned data fetched before the click — a silent no-op,\n * and with local-model probes taking seconds under load the window was\n * wide enough to hit in ordinary use.\n *\n * For the strict guarantee — a request issued after the click, every time,\n * no matter what else is running — use `useRefresh`.\n */\nexport function useRefetch<T>(def: QueryDef<T>): () => void {\n return useCallback(() => {\n void refetchQuery(def.key, () => def.fetch());\n }, [def]);\n}\n\n/**\n * The explicit user-initiated refresh: ALWAYS issues a new request, never\n * joins one already running, and the read it issues is the one whose result\n * is kept even if an older read lands after it.\n *\n * Use this behind anything a person presses that promises them a fresh\n * reading — a Refresh button on a surface with no SSE invalidation, where\n * the whole reason the button exists is that nothing else will re-read.\n */\nexport function useRefresh<T>(def: QueryDef<T>): () => void {\n return useCallback(() => {\n void refetchQuery(def.key, () => def.fetch(), true);\n }, [def]);\n}\n\nexport function useAuthPhase(): AuthPhase {\n return useSyncExternalStore(subscribeAuth, () => getAuthSnapshot().phase, () => getAuthSnapshot().phase);\n}\n\nexport interface MutationHold {\n hasHold: boolean;\n token: string | null;\n heldUntil: number | null;\n setToken: (token: string) => void;\n touch: () => void;\n clear: () => void;\n}\n\nexport function useMutationHold(): MutationHold {\n const heldUntil = useSyncExternalStore(\n subscribeAuth,\n () => getAuthSnapshot().mutationTokenHeldUntil,\n () => getAuthSnapshot().mutationTokenHeldUntil,\n );\n return {\n hasHold: hasMutationHold(),\n token: getMutationToken(),\n heldUntil,\n setToken: storeSetMutationToken,\n touch: touchMutationHold,\n clear: clearMutationToken,\n };\n}\n\n/** Subscribe to a raw named SSE event for the lifetime of the component. */\nexport function useSseEvent(name: SseEventName, onEvent: (payload: unknown) => void): void {\n useEffect(() => onSseEvent(name, onEvent), [name, onEvent]);\n}\n\nexport interface ThemeControl {\n theme: ThemePreference;\n set: (next: ThemePreference) => void;\n cycle: () => void;\n}\n\n/** Shared with Topbar's theme button and the command palette's \"Toggle\n * theme\" action — both read/write data/theme-store.ts through this hook so\n * neither can drift out of sync with the other. */\nexport function useTheme(): ThemeControl {\n const theme = useSyncExternalStore(subscribeTheme, getTheme, getTheme);\n return { theme, set: setTheme, cycle: cycleTheme };\n}\n\nexport interface AppearanceControl {\n appearance: Appearance;\n /** Partial change; applied to <html> and persisted immediately (no save button). */\n set: (patch: Partial<Appearance>) => void;\n reset: () => void;\n}\n\n/**\n * The Settings section's appearance controls and anything that needs to READ\n * the current density/accent (e.g. a chart sizing rows) share this hook, for\n * the same reason useTheme exists: two readers of one external store, never\n * a second copy of the state.\n *\n * Importing this module is also what applies the stored appearance before\n * the first paint — every view imports hooks.ts, so appearance-store.ts is\n * evaluated (and has written <html>) before React renders anything.\n */\nexport function useAppearance(): AppearanceControl {\n const appearance = useSyncExternalStore(subscribeAppearance, getAppearance, getAppearance);\n return { appearance, set: setAppearance, reset: resetAppearance };\n}\n","/**\n * data/client.ts — thin typed HTTP client for the existing /api/* surface.\n * No redefinition of response shapes here — callers type each call with the\n * interfaces re-exported from ./api-types.ts.\n */\nimport { getReadClientProof, reportSessionExpired } from './auth-store.js';\n\nexport class ApiError extends Error {\n constructor(\n message: string,\n public readonly status: number,\n public readonly path: string,\n /**\n * The server's own sentence about the refusal, when it sent one, without\n * the \"POST … failed (HTTP N)\" wrapper. A surface that wants to show the\n * operator why an action was refused should prefer this over `message`:\n * it is the text the route author wrote for a person to read.\n */\n public readonly detail: string | null = null,\n /**\n * The route's machine-readable refusal code (`VERSE_SESSION_NOT_FOUND`,\n * `VERSE_SESSION_BUSY`, …) when the body carried one; null otherwise. Lets a\n * caller branch on WHAT was refused without parsing the sentence.\n */\n public readonly code: string | null = null,\n ) {\n super(message);\n this.name = 'ApiError';\n }\n}\n\n/**\n * Raised for the 404 a mutation route answers when --allow-dispatch is off.\n *\n * That gate answers a bare `{ error: 'not found' }` — deliberately the same\n * body as a route that does not exist, so a read-only server does not\n * advertise its write surface. A route that DOES exist and refuses with a 404\n * of its own (\"session not found\") says so with a `code`; apiPost keeps those\n * as plain ApiErrors, so a deleted chat is never reported as \"this server is\n * read-only\".\n */\nexport class DispatchDisabledError extends ApiError {\n constructor(path: string) {\n super('This server was started without --allow-dispatch, so mutations are disabled.', 404, path);\n this.name = 'DispatchDisabledError';\n }\n}\n\n/** GET an authenticated read route. 401 reports session-expired and throws. */\nexport async function apiGet<T>(path: string, signal?: AbortSignal): Promise<T> {\n const res = await fetch(path, {\n method: 'GET',\n credentials: 'same-origin',\n headers: { 'x-ashlr-read-client': getReadClientProof() },\n signal,\n });\n if (res.status === 401) {\n reportSessionExpired();\n throw new ApiError('Read session expired.', 401, path);\n }\n if (!res.ok) {\n throw new ApiError(`GET ${path} failed (HTTP ${res.status}).`, res.status, path);\n }\n return (await res.json()) as T;\n}\n\n/**\n * POST a mutating route with the raw mutation token. `mutationToken` must\n * come from the caller (data/mutations.ts pulls it from auth-store) — this\n * function does not read auth-store itself so it stays trivially testable.\n */\nexport async function apiPost<T>(\n path: string,\n body: unknown,\n mutationToken: string,\n signal?: AbortSignal,\n): Promise<T> {\n const res = await fetch(path, {\n method: 'POST',\n credentials: 'same-origin',\n headers: {\n 'Content-Type': 'application/json',\n 'x-ashlr-token': mutationToken,\n },\n body: JSON.stringify(body ?? {}),\n signal,\n });\n if (res.status === 401) {\n throw new ApiError('Mutation token was rejected.', 401, path);\n }\n if (!res.ok) {\n const { detail, code } = await readRefusal(res);\n // Only a CODELESS 404 is the dispatch gate (see DispatchDisabledError).\n // An unknown sub-path on an older server is codeless too and lands here\n // as well — the two are indistinguishable by design, so callers that\n // word this error must not assert which one it was.\n if (res.status === 404 && code === null) {\n throw new DispatchDisabledError(path);\n }\n throw new ApiError(\n `POST ${path} failed (HTTP ${res.status})${detail ? `: ${detail}` : ''}.`,\n res.status,\n path,\n detail || null,\n code,\n );\n }\n if (res.status === 204) return undefined as T;\n return (await res.json()) as T;\n}\n\n/**\n * The refusal sentence and code from a failed POST's body, if it had any.\n *\n * `error` is the documented refusal field, but the Verse control plane\n * answers a refused daemon/scope action with a full result body whose\n * plain-language sentence lives in `note` and which has NO `error` key at all\n * (see VerseDaemonActionResult). Reading only `error` threw away sentences\n * like \"no repositories are enrolled, so the loop would do nothing\" and left\n * the caller with a bare status code to guess from.\n */\nasync function readRefusal(res: Response): Promise<{ detail: string; code: string | null }> {\n try {\n const j = (await res.json()) as { error?: unknown; note?: unknown; code?: unknown };\n const detail = typeof j.error === 'string' && j.error ? j.error : typeof j.note === 'string' ? j.note : '';\n const code = typeof j.code === 'string' && j.code ? j.code : null;\n return { detail, code };\n } catch {\n /* body wasn't JSON */\n return { detail: '', code: null };\n }\n}\n\n/** Build the SSE URL, carrying the client proof as the query-string proof\n * EventSource can't attach as a header (see server.ts readSessionClientProof). */\nexport function eventsUrl(): string {\n return `/api/events?client=${encodeURIComponent(getReadClientProof())}`;\n}\n",".grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));\n gap: var(--space-4);\n}\n\n/* No border and no fill: a skeleton stands in for CONTENT, and drawing it as\n a bordered card meant the placeholder had a box the real card does not\n (DESIGN-V2 §1.2) and the layout visibly lost an outline on first paint. */\n.card {\n display: flex;\n flex-direction: column;\n gap: var(--space-3);\n padding: var(--space-4) 0;\n}\n\n/* Each bar matches the LINE BOX of the text it replaces, so nothing reflows\n when the real content lands: a label line, a display-face stat, a caption. */\n.title {\n height: var(--text-xs-line);\n width: 60%;\n border-radius: var(--radius-xs);\n}\n\n.stat {\n height: var(--text-2xl-line);\n width: 40%;\n border-radius: var(--radius-xs);\n}\n\n.caption {\n height: var(--text-2xs-line);\n width: 80%;\n border-radius: var(--radius-xs);\n}\n\n.line {\n height: var(--text-base-line);\n border-radius: var(--radius-xs);\n}\n\n.row {\n display: flex;\n align-items: center;\n gap: var(--space-4);\n min-height: var(--density-row);\n padding: var(--space-3) var(--space-4);\n border-bottom: 1px solid var(--border-subtle);\n}\n\n.cell {\n height: var(--text-sm-line);\n border-radius: var(--radius-xs);\n}\n","/**\n * components/primitives/Skeleton.tsx — loading placeholders. Used ONLY for\n * `status === 'loading'` (no data at all yet). A resource that already has\n * data and is merely refreshing should show <RefreshIndicator/> instead —\n * never re-blank a populated view (see DESIGN.md \"no blank screens\").\n */\nimport styles from './Skeleton.module.css';\n\nexport function SkeletonLine({ width = '100%' }: { width?: string }) {\n return <div className={`skeleton ${styles.line}`} style={{ width }} />;\n}\n\nexport function SkeletonCard() {\n return (\n <div className={styles.card}>\n <div className={`skeleton ${styles.title}`} />\n <div className={`skeleton ${styles.stat}`} />\n <div className={`skeleton ${styles.caption}`} />\n </div>\n );\n}\n\nexport function SkeletonRow() {\n return (\n <div className={styles.row}>\n <div className={`skeleton ${styles.cell}`} style={{ width: '18%' }} />\n <div className={`skeleton ${styles.cell}`} style={{ width: '38%' }} />\n <div className={`skeleton ${styles.cell}`} style={{ width: '14%' }} />\n <div className={`skeleton ${styles.cell}`} style={{ width: '10%' }} />\n </div>\n );\n}\n\nexport function SkeletonCardGrid({ count = 4 }: { count?: number }) {\n return (\n <div className={styles.grid}>\n {Array.from({ length: count }, (_, i) => (\n <SkeletonCard key={i} />\n ))}\n </div>\n );\n}\n",".skipLink {\n position: fixed;\n top: -100px;\n left: var(--space-3);\n z-index: var(--z-toast);\n background: var(--accent-500);\n color: var(--text-on-accent);\n padding: var(--space-2) var(--space-4);\n border-radius: var(--radius-md);\n font-size: var(--text-sm-size);\n font-weight: var(--font-weight-medium);\n transition: top var(--duration-fast) var(--ease-standard);\n}\n\n.skipLink:focus-visible {\n top: var(--space-3);\n box-shadow: var(--shadow-focus-ring);\n}\n","/**\n * app/SkipToContent.tsx — a real skip link, deliberately NOT `<a\n * href=\"#main-content\">`: this app uses HashRouter, so a literal `#`\n * anchor would rewrite location.hash and hijack client-side routing\n * instead of just scrolling. A button that imperatively focuses the\n * `#main-content` element (set on <main> in components/layout/Shell.tsx)\n * gets the same keyboard behavior without touching the route.\n */\nimport styles from './SkipToContent.module.css';\n\nexport function SkipToContent() {\n return (\n <button\n type=\"button\"\n className={styles.skipLink}\n onClick={() => {\n const main = document.getElementById('main-content');\n main?.focus();\n main?.scrollIntoView({ block: 'start' });\n }}\n >\n Skip to content\n </button>\n );\n}\n",".wrap {\n min-height: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: var(--space-6);\n background: var(--bg-canvas);\n}\n\n.card {\n width: min(480px, 100%);\n background: var(--bg-surface);\n border: 1px solid var(--border-default);\n border-radius: var(--radius-xl);\n box-shadow: var(--shadow-lg);\n padding: var(--space-8);\n}\n\n.badge {\n font-size: 28px;\n margin-bottom: var(--space-4);\n}\n\n.heading {\n font-size: var(--text-2xl-size);\n line-height: var(--text-2xl-line);\n font-weight: var(--font-weight-semibold);\n margin-bottom: var(--space-3);\n}\n\n.body {\n font-size: var(--text-sm-size);\n line-height: var(--text-sm-line);\n color: var(--text-secondary);\n margin-bottom: var(--space-3);\n}\n\n.code {\n font-family: var(--font-mono);\n font-size: var(--text-sm-size);\n background: var(--bg-code);\n border: 1px solid var(--border-subtle);\n border-radius: var(--radius-sm);\n padding: var(--space-3);\n margin-bottom: var(--space-3);\n overflow-x: auto;\n}\n\n.example {\n font-family: var(--font-mono);\n background: var(--bg-code);\n padding: 1px 4px;\n border-radius: var(--radius-xs);\n}\n\n.form {\n display: flex;\n flex-direction: column;\n gap: var(--space-2);\n margin-top: var(--space-5);\n}\n\n.label {\n font-size: var(--text-sm-size);\n font-weight: var(--font-weight-medium);\n}\n\n.input {\n padding: var(--space-3);\n border-radius: var(--radius-md);\n border: 1px solid var(--border-default);\n background: var(--bg-input);\n font-family: var(--font-mono);\n font-size: var(--text-sm-size);\n}\n\n.input:focus-visible {\n border-color: var(--border-focus);\n box-shadow: var(--shadow-focus-ring);\n}\n\n.error {\n color: var(--status-danger-fg);\n font-size: var(--text-sm-size);\n}\n\n.submit {\n margin-top: var(--space-2);\n padding: var(--space-3) var(--space-4);\n background: var(--accent-500);\n color: var(--text-on-accent);\n border-radius: var(--radius-md);\n font-weight: var(--font-weight-medium);\n text-align: center;\n transition: background var(--duration-fast) var(--ease-standard);\n}\n\n.submit:hover:not(:disabled) {\n background: var(--accent-600);\n}\n\n.submit:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.optional { font-weight: var(--font-weight-normal, 400); color: var(--text-tertiary); }\n","/**\n * components/auth/SessionGate.tsx — replaces the old bare \"⚠ HTTP 401 +\n * Retry\" first-run experience (foundation brief item 5). Shown whenever\n * auth-store's phase is 'unauthenticated': on first load before any read\n * token has been entered, AND any time a session expires mid-use (SSE\n * disconnects, the next GET reports 401).\n *\n * By default this screen owns nothing about the MUTATION token — that is a\n * separate, later step (MutationTokenDialog), because read authority and\n * dispatch authority are genuinely different capabilities on this server\n * (see server.ts). Surfaces where sending is the whole point (Verse) can opt\n * into an optional second field with `mutationField`; the authorities stay\n * separate (the mutation token only ever enters the memory-only hold), it\n * just spares the operator a second paste screen on the first Enter.\n */\nimport { useEffect, useId, useRef, useState, type FormEvent } from 'react';\nimport { adoptInjectedTokens, establishReadSession, setMutationToken } from '../../data/auth-store.js';\nimport styles from './SessionGate.module.css';\n\nconst TOKEN_RE = /^[a-f0-9]{64}$/;\n\nexport interface SessionGateProps {\n onAuthenticated?: () => void;\n heading?: string;\n command?: string;\n /** What the copy calls this surface (\"This dashboard\", \"Ashlr Verse\"). */\n subject?: string;\n /**\n * Also offer the mutation token on the same screen. The two authorities\n * stay separate (a pasted mutation token goes straight into the memory-only\n * hold, exactly as MutationTokenDialog would set it); this only collapses\n * two paste screens into one for surfaces where sending is the whole point.\n */\n mutationField?: boolean;\n}\n\nexport function SessionGate({ onAuthenticated, heading = 'Connect to ashlr serve', command = 'ashlr serve', subject = 'This dashboard', mutationField = false }: SessionGateProps) {\n const [token, setToken] = useState('');\n const [mutation, setMutation] = useState('');\n const [error, setError] = useState<string | null>(null);\n const [busy, setBusy] = useState(false);\n const inputRef = useRef<HTMLInputElement>(null);\n const inputId = useId();\n const mutationId = useId();\n const errorId = useId();\n const authenticatedRef = useRef(onAuthenticated);\n authenticatedRef.current = onAuthenticated;\n\n // Desktop wrapper: when the host injected tokens (window.__ASHLR_TOKENS__),\n // establish the session silently instead of asking for a paste.\n useEffect(() => {\n let cancelled = false;\n void adoptInjectedTokens().then((adopted) => {\n if (adopted && !cancelled) authenticatedRef.current?.();\n });\n return () => { cancelled = true; };\n }, []);\n\n async function onSubmit(e: FormEvent) {\n e.preventDefault();\n setError(null);\n const mutationTrimmed = mutation.trim();\n if (mutationField && mutationTrimmed && !TOKEN_RE.test(mutationTrimmed)) {\n setError('That does not look like a mutation token — expected 64 hex characters (leave it empty to unlock later).');\n return;\n }\n setBusy(true);\n try {\n await establishReadSession(token);\n setToken('');\n if (mutationField && mutationTrimmed) setMutationToken(mutationTrimmed);\n setMutation('');\n onAuthenticated?.();\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Could not establish a session.');\n inputRef.current?.select();\n } finally {\n setBusy(false);\n }\n }\n\n return (\n <div className={styles.wrap}>\n <div className={styles.card}>\n <div className={styles.badge} aria-hidden=\"true\">\n ⛺\n </div>\n <h1 className={styles.heading}>{heading}</h1>\n <p className={styles.body}>\n {subject} only ever talks to the ashlr server running on this machine\n (<code>127.0.0.1</code>, never a remote host). To read data, paste the{' '}\n <strong>read token</strong> printed in the terminal where you ran:\n </p>\n <pre className={styles.code}>{command}</pre>\n <p className={styles.body}>\n It looks like a 64-character hex string, printed once at startup — something like{' '}\n <code className={styles.example}>a1b2c3…</code>. This dialog never stores it: it is\n exchanged once for a short-lived session cookie, then discarded.\n </p>\n <form onSubmit={onSubmit} className={styles.form} noValidate>\n <label htmlFor={inputId} className={styles.label}>\n Read token\n </label>\n <input\n ref={inputRef}\n id={inputId}\n name=\"read-token\"\n type=\"password\"\n autoComplete=\"off\"\n autoCorrect=\"off\"\n spellCheck={false}\n inputMode=\"text\"\n className={styles.input}\n value={token}\n onChange={(e) => setToken(e.target.value)}\n aria-invalid={error ? true : undefined}\n aria-describedby={error ? errorId : undefined}\n placeholder=\"64 hex characters\"\n autoFocus\n />\n {mutationField ? (\n <>\n <label htmlFor={mutationId} className={styles.label}>\n Mutation token <span className={styles.optional}>optional — paste it now to enable sending</span>\n </label>\n <input\n id={mutationId}\n name=\"mutation-token\"\n type=\"password\"\n autoComplete=\"off\"\n autoCorrect=\"off\"\n spellCheck={false}\n inputMode=\"text\"\n className={styles.input}\n value={mutation}\n onChange={(e) => setMutation(e.target.value)}\n placeholder=\"64 hex characters, printed under “Dispatch enabled”\"\n />\n </>\n ) : null}\n {error ? (\n <p id={errorId} className={styles.error} role=\"alert\">\n {error}\n </p>\n ) : null}\n <button type=\"submit\" className={styles.submit} disabled={busy || token.trim().length === 0}>\n {busy ? 'Connecting…' : 'Connect'}\n </button>\n </form>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{g as e,o as t}from"./colors-B-vHsX1S.js";var n={local:`local`,"grok-cli":`grok`,"claude-cli":`claude`,codex:`codex`},r={local:`Local`,"grok-cli":`Grok`,"claude-cli":`Claude`,codex:`Codex`};function i(e){return e.phase===`queued`||e.phase===`parked`?e.phase:e.phase===`reverting`?`reverting`:e.outcome??e.phase}var a={producing:`running`,verifying:`running`,judging:`running`,landing:`running`,watching:`info`,reverting:`danger`,merged:`success`,proposed:`info`,"owner-lane":`warning`,refused:`warning`,failed:`danger`,reverted:`danger`,cancelled:`neutral`,queued:`neutral`,parked:`neutral`};function o(e){return a[e]??`unknown`}function s(e){let t=Date.parse(e.startedAt??e.phaseStartedAt??``);return Number.isFinite(t)?t:null}function c(e){if(!e.endedAt)return null;let t=Date.parse(e.endedAt);return Number.isFinite(t)?t:null}function l(e){return[e.repo.includes(`/`)?e.repo.split(`/`)[1]:e.repo,e.title,e.model,e.prNumber?`PR #${e.prNumber}`:null].filter(Boolean).join(` · `)}var u=[`local`,`grok-cli`,`claude-cli`,`codex`,`none`];function d(e,t,a,o={}){let d=o.includeParked??!0,f=new Map;for(let n of e){if(!d&&n.phase===`parked`)continue;let e=s(n);if(e===null||(c(n)??a)<t||e>a)continue;let r=n.lane??`none`;f.has(r)||f.set(r,[]),f.get(r).push(n)}let p=[];for(let e of u){let t=f.get(e);if(!t?.length)continue;t.sort((e,t)=>s(e)-s(t));let o=[],u=[];for(let e of t){let t=s(e),n=c(e)??a,r=o.findIndex(e=>e<=t);r===-1?(r=o.length,o.push(n),u.push([])):o[r]=n,u[r].push(e)}let d=e===`none`?`Unassigned`:r[e];u.forEach((t,r)=>{p.push({id:`${e}:${r}`,label:u.length>1?`${d} · ${r+1}`:d,...e===`none`?{}:{engine:n[e]},items:t.map(e=>({id:e.id,start:s(e),end:c(e),status:i(e),detail:l(e)}))})})}return p}function f(e,t){let r=e.filter(e=>e.phase===`parked`||e.hold!==null),i=r.map(e=>e.hold?.nextEligibleAt?Date.parse(e.hold.nextEligibleAt):NaN).filter(Number.isFinite),a=r.map(e=>s(e)??t),o=Math.min(t-36e5,...a),c=Math.min(t+1728e5,Math.max(t+72e5,...i)),l=0;return{lanes:r.map(e=>{let r=e.hold?.nextEligibleAt?Date.parse(e.hold.nextEligibleAt):NaN;Number.isFinite(r)||l++;let i=e.repo.includes(`/`)?e.repo.split(`/`)[1]:e.repo;return{id:e.id,label:`${i}: ${e.title}`,...e.lane?{engine:n[e.lane]}:{},items:[{id:e.id,start:s(e)??t,end:Number.isFinite(r)?r:null,status:e.hold?.kind===`split`?`split`:`parked`,outline:!0,detail:`${e.hold?.reason??`parked`}${Number.isFinite(r)?``:` · release time unknown`}`}]}}),from:o,to:c,unknownRelease:l}}var p={G0:`Authority`,G1:`Protected paths`,G1b:`Tamper`,G2:`Scope`,G3:`Verify`,G4:`Claims`,G5:`Blast radius`,G6:`Judge`,G7:`GitHub checks`};function m(e){return!e||e.stages.length===0?[]:[{id:`entered`,label:`Proposals`,value:e.stages[0].entered},...e.stages.map(e=>({id:e.gate,label:`${e.gate} ${p[e.gate]??``}`.trim(),value:e.passed}))]}function h(n,r=5){if(!n)return{categories:[],segments:[],values:[],total:0};let i=new Map;for(let e of n.stages)for(let t of e.refusals){let e=i.get(t.code);i.set(t.code,{reason:e?.reason??t.reason,count:(e?.count??0)+t.count})}let a=[...i.entries()].sort((e,t)=>t[1].count-e[1].count||e[0].localeCompare(t[0])),o=a.slice(0,r).map(([e])=>e),s=a.length>r,c=[...o.map((t,n)=>({id:t,label:i.get(t).reason,color:e(n)})),...s?[{id:`__other`,label:`Other reasons`,color:t}]:[]],l=n.stages.filter(e=>e.refusals.length>0||e.entered!==e.passed);return{categories:l.map(e=>e.gate),segments:c,values:l.map(e=>{let t=o.map(t=>e.refusals.find(e=>e.code===t)?.count??0);return s&&t.push(e.refusals.filter(e=>!o.includes(e.code)).reduce((e,t)=>e+t.count,0)),t}),total:a.reduce((e,[,t])=>e+t.count,0)}}function g(e){if(!e)return null;let t=e.runs.filter(e=>e.seatDecision!==null);if(!t.length)return null;t.sort((e,t)=>(s(t)??0)-(s(e)??0));let n=t[0];return{decision:n.seatDecision,run:n}}export{g as a,o as c,d as i,r as n,f as o,m as r,h as s,n as t};
|
|
2
|
-
//# sourceMappingURL=live-model-Dm6ITHvX.js.map
|