@ashlr/hub 3.10.0 → 3.10.1
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 +101 -0
- package/dist/authority-surface.json +43 -23
- package/dist/build-identity.json +2 -2
- 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/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.js +2 -1
- package/dist/core/verse/verse-api.js.map +1 -1
- package/dist/core/web/public/next/assets/{ActionMenu-DhbNne-M.js → ActionMenu-BBB7VYjz.js} +2 -2
- package/dist/core/web/public/next/assets/{ActionMenu-DhbNne-M.js.map → ActionMenu-BBB7VYjz.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-xVYFAeA8.js +2 -0
- package/dist/core/web/public/next/assets/Advanced-xVYFAeA8.js.map +1 -0
- package/dist/core/web/public/next/assets/{App-DyBg1v_q.js → App-CbDCLAxn.js} +5 -5
- package/dist/core/web/public/next/assets/{App-DyBg1v_q.js.map → App-CbDCLAxn.js.map} +1 -1
- package/dist/core/web/public/next/assets/AppsSection-B5HWJi9I.js +2 -0
- package/dist/core/web/public/next/assets/AppsSection-B5HWJi9I.js.map +1 -0
- package/dist/core/web/public/next/assets/AreaTrend-BgnK0wAy.js +2 -0
- package/dist/core/web/public/next/assets/AreaTrend-BgnK0wAy.js.map +1 -0
- package/dist/core/web/public/next/assets/AutonomySection-BF5UCsMv.js +2 -0
- package/dist/core/web/public/next/assets/{AutonomySection-CoMngwNB.js.map → AutonomySection-BF5UCsMv.js.map} +1 -1
- package/dist/core/web/public/next/assets/BranchBar-BZNQ50YE.css +1 -0
- package/dist/core/web/public/next/assets/BranchBar-nfSb59Kv.js +2 -0
- package/dist/core/web/public/next/assets/BranchBar-nfSb59Kv.js.map +1 -0
- package/dist/core/web/public/next/assets/BudgetControl-BESM12dO.js +2 -0
- package/dist/core/web/public/next/assets/BudgetControl-BESM12dO.js.map +1 -0
- package/dist/core/web/public/next/assets/BurnDown-D1AfskPT.js +2 -0
- package/dist/core/web/public/next/assets/BurnDown-D1AfskPT.js.map +1 -0
- package/dist/core/web/public/next/assets/{Button-0-nNbv1h.js → Button-Cr_qkQEc.js} +2 -2
- package/dist/core/web/public/next/assets/{Button-0-nNbv1h.js.map → Button-Cr_qkQEc.js.map} +1 -1
- package/dist/core/web/public/next/assets/CapacityStrip-Cc28CyWk.css +1 -0
- package/dist/core/web/public/next/assets/CapacityStrip-DqqYnLek.js +2 -0
- package/dist/core/web/public/next/assets/CapacityStrip-DqqYnLek.js.map +1 -0
- package/dist/core/web/public/next/assets/ChatSection-DoqjyVoj.js +3 -0
- package/dist/core/web/public/next/assets/{ChatSection-B9cdRKUh.js.map → ChatSection-DoqjyVoj.js.map} +1 -1
- package/dist/core/web/public/next/assets/{CommandPalette-GpeCSrPy.js → CommandPalette-lLV-OR63.js} +2 -2
- package/dist/core/web/public/next/assets/{CommandPalette-GpeCSrPy.js.map → CommandPalette-lLV-OR63.js.map} +1 -1
- package/dist/core/web/public/next/assets/CommandSection-fiZ4DJpM.js +2 -0
- package/dist/core/web/public/next/assets/CommandSection-fiZ4DJpM.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-BVxhK-cy.js +2 -0
- package/dist/core/web/public/next/assets/DiffPane-BVxhK-cy.js.map +1 -0
- package/dist/core/web/public/next/assets/{DiffViewer-tOGWk0CA.js → DiffViewer-yCdw9J-Y.js} +2 -2
- package/dist/core/web/public/next/assets/{DiffViewer-tOGWk0CA.js.map → DiffViewer-yCdw9J-Y.js.map} +1 -1
- package/dist/core/web/public/next/assets/DockHost-B4RaYOET.css +1 -0
- package/dist/core/web/public/next/assets/DockHost-BZReKwos.js +2 -0
- package/dist/core/web/public/next/assets/DockHost-BZReKwos.js.map +1 -0
- package/dist/core/web/public/next/assets/{EmptyState-DdJ4CxtO.js → EmptyState-D1GDxVZy.js} +2 -2
- package/dist/core/web/public/next/assets/{EmptyState-DdJ4CxtO.js.map → EmptyState-D1GDxVZy.js.map} +1 -1
- package/dist/core/web/public/next/assets/{Epistemic-BLjM5y_y.js → Epistemic-DRp4dBfB.js} +2 -2
- package/dist/core/web/public/next/assets/{Epistemic-BLjM5y_y.js.map → Epistemic-DRp4dBfB.js.map} +1 -1
- package/dist/core/web/public/next/assets/FleetSection-CFiWeOEs.css +1 -0
- package/dist/core/web/public/next/assets/FleetSection-rPLy_NPo.js +3 -0
- package/dist/core/web/public/next/assets/FleetSection-rPLy_NPo.js.map +1 -0
- package/dist/core/web/public/next/assets/Funnel-zvDj3He4.js +2 -0
- package/dist/core/web/public/next/assets/Funnel-zvDj3He4.js.map +1 -0
- package/dist/core/web/public/next/assets/GearTray-CkviqjYr.js +2 -0
- package/dist/core/web/public/next/assets/GearTray-CkviqjYr.js.map +1 -0
- package/dist/core/web/public/next/assets/GrowthSection-Bg5sW0jI.js +2 -0
- package/dist/core/web/public/next/assets/GrowthSection-Bg5sW0jI.js.map +1 -0
- package/dist/core/web/public/next/assets/HandoffDialog-DcPj1ri9.js +2 -0
- package/dist/core/web/public/next/assets/HandoffDialog-DcPj1ri9.js.map +1 -0
- package/dist/core/web/public/next/assets/{Input-CTu0CycC.js → Input-Ci7TD96x.js} +2 -2
- package/dist/core/web/public/next/assets/{Input-CTu0CycC.js.map → Input-Ci7TD96x.js.map} +1 -1
- package/dist/core/web/public/next/assets/{LeaderCard-BuSDeATv.js → LeaderCard-BSFTlMRF.js} +2 -2
- package/dist/core/web/public/next/assets/{LeaderCard-BuSDeATv.js.map → LeaderCard-BSFTlMRF.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-DojKZe9L.js +2 -0
- package/dist/core/web/public/next/assets/MatrixHeatmap-DojKZe9L.js.map +1 -0
- package/dist/core/web/public/next/assets/McpSection-CvcYu1X_.js +2 -0
- package/dist/core/web/public/next/assets/{McpSection-1UncWBpW.js.map → McpSection-CvcYu1X_.js.map} +1 -1
- package/dist/core/web/public/next/assets/MessageMarkdown-BrLCrb73.js +3 -0
- package/dist/core/web/public/next/assets/{MessageMarkdown-C4pl9LI5.js.map → MessageMarkdown-BrLCrb73.js.map} +1 -1
- package/dist/core/web/public/next/assets/MessageMarkdown-Cgh5f1-Q.css +1 -0
- package/dist/core/web/public/next/assets/{MessageMarkdownRenderer-CZsF77ZM.js → MessageMarkdownRenderer-CjmdBw2p.js} +2 -2
- package/dist/core/web/public/next/assets/{MessageMarkdownRenderer-CZsF77ZM.js.map → MessageMarkdownRenderer-CjmdBw2p.js.map} +1 -1
- package/dist/core/web/public/next/assets/{Meter-C18ciKz4.js → Meter-DTHbmhQg.js} +2 -2
- package/dist/core/web/public/next/assets/{Meter-C18ciKz4.js.map → Meter-DTHbmhQg.js.map} +1 -1
- package/dist/core/web/public/next/assets/MindSection-DcO9fmYe.js +2 -0
- package/dist/core/web/public/next/assets/MindSection-DcO9fmYe.js.map +1 -0
- package/dist/core/web/public/next/assets/MutationTokenDialog-Cqn2uPV6.js +2 -0
- package/dist/core/web/public/next/assets/MutationTokenDialog-Cqn2uPV6.js.map +1 -0
- package/dist/core/web/public/next/assets/NeedsYouDrawer-BpvidnM2.css +1 -0
- package/dist/core/web/public/next/assets/NeedsYouDrawer-hiTINupk.js +2 -0
- package/dist/core/web/public/next/assets/NeedsYouDrawer-hiTINupk.js.map +1 -0
- package/dist/core/web/public/next/assets/NeedsYouList-B4Z37Phx.js +2 -0
- package/dist/core/web/public/next/assets/NeedsYouList-B4Z37Phx.js.map +1 -0
- package/dist/core/web/public/next/assets/NeedsYouList-CruOt4JT.css +1 -0
- package/dist/core/web/public/next/assets/NewChatDialog-utSckh7C.js +3 -0
- package/dist/core/web/public/next/assets/{NewChatDialog-B_BsS9pr.js.map → NewChatDialog-utSckh7C.js.map} +1 -1
- package/dist/core/web/public/next/assets/OnboardingFlow-7N3YSUHN.js +2 -0
- package/dist/core/web/public/next/assets/{OnboardingFlow-CtCPpgoX.js.map → OnboardingFlow-7N3YSUHN.js.map} +1 -1
- package/dist/core/web/public/next/assets/PreviewPane-CqzkaAjG.js +2 -0
- package/dist/core/web/public/next/assets/PreviewPane-CqzkaAjG.js.map +1 -0
- package/dist/core/web/public/next/assets/PreviewPane-DoMsJSG3.css +1 -0
- package/dist/core/web/public/next/assets/RailStatus-sZhApA8k.js +2 -0
- package/dist/core/web/public/next/assets/RailStatus-sZhApA8k.js.map +1 -0
- package/dist/core/web/public/next/assets/{RefreshIndicator-CnhEovMZ.js → RefreshIndicator-C1uQIegT.js} +2 -2
- package/dist/core/web/public/next/assets/{RefreshIndicator-CnhEovMZ.js.map → RefreshIndicator-C1uQIegT.js.map} +1 -1
- package/dist/core/web/public/next/assets/{ResourcePoolConsoleApp-28gLwmwV.js → ResourcePoolConsoleApp-DXjjV6Sh.js} +4 -4
- package/dist/core/web/public/next/assets/{ResourcePoolConsoleApp-28gLwmwV.js.map → ResourcePoolConsoleApp-DXjjV6Sh.js.map} +1 -1
- package/dist/core/web/public/next/assets/{RouteErrorBoundary-AyUPCIb3.js → RouteErrorBoundary-D1LZxQjN.js} +2 -2
- package/dist/core/web/public/next/assets/{RouteErrorBoundary-AyUPCIb3.js.map → RouteErrorBoundary-D1LZxQjN.js.map} +1 -1
- package/dist/core/web/public/next/assets/SeatSelector-CT92PqM_.js +3 -0
- package/dist/core/web/public/next/assets/{SeatSelector-DaUO-zoK.js.map → SeatSelector-CT92PqM_.js.map} +1 -1
- package/dist/core/web/public/next/assets/{Segmented-B_8c_83S.js → Segmented-BMvq-FfD.js} +2 -2
- package/dist/core/web/public/next/assets/{Segmented-B_8c_83S.js.map → Segmented-BMvq-FfD.js.map} +1 -1
- package/dist/core/web/public/next/assets/{Select-Bl5g5Dt_.js → Select-DkdeoSQn.js} +2 -2
- package/dist/core/web/public/next/assets/{Select-Bl5g5Dt_.js.map → Select-DkdeoSQn.js.map} +1 -1
- package/dist/core/web/public/next/assets/{SessionInsightChip-w0I21q0c.js → SessionInsightChip-CwlakUYU.js} +2 -2
- package/dist/core/web/public/next/assets/{SessionInsightChip-w0I21q0c.js.map → SessionInsightChip-CwlakUYU.js.map} +1 -1
- package/dist/core/web/public/next/assets/SessionSearch-DFByh2NZ.js +2 -0
- package/dist/core/web/public/next/assets/{SessionSearch-B5aQ0BCM.js.map → SessionSearch-DFByh2NZ.js.map} +1 -1
- package/dist/core/web/public/next/assets/SettingsSection-DgSaHFK4.js +2 -0
- package/dist/core/web/public/next/assets/SettingsSection-DgSaHFK4.js.map +1 -0
- package/dist/core/web/public/next/assets/{Sheet-CVjk-cLI.js → Sheet-C7APkGPt.js} +2 -2
- package/dist/core/web/public/next/assets/{Sheet-CVjk-cLI.js.map → Sheet-C7APkGPt.js.map} +1 -1
- package/dist/core/web/public/next/assets/{ShortcutsOverlay-BiBjnBcc.js → ShortcutsOverlay-ZPyXUS_I.js} +2 -2
- package/dist/core/web/public/next/assets/{ShortcutsOverlay-BiBjnBcc.js.map → ShortcutsOverlay-ZPyXUS_I.js.map} +1 -1
- package/dist/core/web/public/next/assets/Sidebar-BRc1vGDF.css +1 -0
- package/dist/core/web/public/next/assets/Sidebar-Chsgazmk.js +3 -0
- package/dist/core/web/public/next/assets/Sidebar-Chsgazmk.js.map +1 -0
- package/dist/core/web/public/next/assets/{Slider-BS9MU5ZV.js → Slider-B0kjW7cd.js} +2 -2
- package/dist/core/web/public/next/assets/{Slider-BS9MU5ZV.js.map → Slider-B0kjW7cd.js.map} +1 -1
- package/dist/core/web/public/next/assets/{StatusBadge-DAf-ArKE.js → StatusBadge-wv4e6f8b.js} +2 -2
- package/dist/core/web/public/next/assets/{StatusBadge-DAf-ArKE.js.map → StatusBadge-wv4e6f8b.js.map} +1 -1
- package/dist/core/web/public/next/assets/StepBand-Db8WPw8J.js +2 -0
- package/dist/core/web/public/next/assets/StepBand-Db8WPw8J.js.map +1 -0
- package/dist/core/web/public/next/assets/{Surface-CiYOdNKQ.js → Surface-BkDrQ4t3.js} +2 -2
- package/dist/core/web/public/next/assets/{Surface-CiYOdNKQ.js.map → Surface-BkDrQ4t3.js.map} +1 -1
- package/dist/core/web/public/next/assets/Swimlane-Dcve56cP.js +2 -0
- package/dist/core/web/public/next/assets/Swimlane-Dcve56cP.js.map +1 -0
- package/dist/core/web/public/next/assets/{Switch-BzRXtNPt.js → Switch-DamlpSMY.js} +2 -2
- package/dist/core/web/public/next/assets/{Switch-BzRXtNPt.js.map → Switch-DamlpSMY.js.map} +1 -1
- package/dist/core/web/public/next/assets/{Tag-DEEbDz7T.js → Tag-Cs4tudBN.js} +2 -2
- package/dist/core/web/public/next/assets/{Tag-DEEbDz7T.js.map → Tag-Cs4tudBN.js.map} +1 -1
- package/dist/core/web/public/next/assets/TerminalPane-BDkKFych.js +7 -0
- package/dist/core/web/public/next/assets/TerminalPane-BDkKFych.js.map +1 -0
- package/dist/core/web/public/next/assets/TerminalPane-BwfMbnJz.css +1 -0
- package/dist/core/web/public/next/assets/UniverseConsoleApp-BE72VyBL.js +2 -0
- package/dist/core/web/public/next/assets/{UniverseConsoleApp-DKqAf-eD.js.map → UniverseConsoleApp-BE72VyBL.js.map} +1 -1
- package/dist/core/web/public/next/assets/{UniverseView-D6081LQL.js → UniverseView-BbmYY2P4.js} +2 -2
- package/dist/core/web/public/next/assets/{UniverseView-D6081LQL.js.map → UniverseView-BbmYY2P4.js.map} +1 -1
- package/dist/core/web/public/next/assets/UsageSection-DsB-Jqyl.css +1 -0
- package/dist/core/web/public/next/assets/UsageSection-DsC5WmLk.js +2 -0
- package/dist/core/web/public/next/assets/UsageSection-DsC5WmLk.js.map +1 -0
- package/dist/core/web/public/next/assets/VerseConsoleApp-CXJWDiEM.css +1 -0
- package/dist/core/web/public/next/assets/VerseConsoleApp-Dq_hREXs.js +3 -0
- package/dist/core/web/public/next/assets/VerseConsoleApp-Dq_hREXs.js.map +1 -0
- package/dist/core/web/public/next/assets/Workspace-BYW9dm90.css +1 -0
- package/dist/core/web/public/next/assets/Workspace-CKvVEPFq.js +10 -0
- package/dist/core/web/public/next/assets/Workspace-CKvVEPFq.js.map +1 -0
- package/dist/core/web/public/next/assets/actions-BnjZy8a-.js +2 -0
- package/dist/core/web/public/next/assets/actions-BnjZy8a-.js.map +1 -0
- package/dist/core/web/public/next/assets/actions-Ddfg2iLl.css +1 -0
- package/dist/core/web/public/next/assets/{budget-queries-Dacv1VYP.js → budget-queries-Cp6vO7FH.js} +2 -2
- package/dist/core/web/public/next/assets/{budget-queries-Dacv1VYP.js.map → budget-queries-Cp6vO7FH.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-9-Yb211u.js +2 -0
- package/dist/core/web/public/next/assets/capacity-strip-model-9-Yb211u.js.map +1 -0
- package/dist/core/web/public/next/assets/charts-BmbKhjqw.js +2 -0
- package/dist/core/web/public/next/assets/charts-BmbKhjqw.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-DHJJLWh8.js} +2 -2
- package/dist/core/web/public/next/assets/{command-bus-CpYuam1F.js.map → command-bus-DHJJLWh8.js.map} +1 -1
- package/dist/core/web/public/next/assets/context-model-BGznyHNQ.js +2 -0
- package/dist/core/web/public/next/assets/context-model-BGznyHNQ.js.map +1 -0
- package/dist/core/web/public/next/assets/context-model-DeJ28uSZ.js +2 -0
- package/dist/core/web/public/next/assets/context-model-DeJ28uSZ.js.map +1 -0
- package/dist/core/web/public/next/assets/context-queries-_mEgy4eg.js +2 -0
- package/dist/core/web/public/next/assets/context-queries-_mEgy4eg.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-C4TG8blN.js} +2 -2
- package/dist/core/web/public/next/assets/{control-queries-BGjarMm9.js.map → control-queries-C4TG8blN.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-BB7Fi6hT.js +2 -0
- package/dist/core/web/public/next/assets/dock-icons-BB7Fi6hT.js.map +1 -0
- package/dist/core/web/public/next/assets/dock-panes-mnIwSpRp.js +2 -0
- package/dist/core/web/public/next/assets/{dock-panes-BNTLvX7G.js.map → dock-panes-mnIwSpRp.js.map} +1 -1
- package/dist/core/web/public/next/assets/{dock-store-_E5h6TEq.js → dock-store-CdHQRP8s.js} +2 -2
- package/dist/core/web/public/next/assets/{dock-store-_E5h6TEq.js.map → dock-store-CdHQRP8s.js.map} +1 -1
- package/dist/core/web/public/next/assets/fleet-model-B4FKC7fH.js +2 -0
- package/dist/core/web/public/next/assets/fleet-model-B4FKC7fH.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/format-lW58rL2y.js +2 -0
- package/dist/core/web/public/next/assets/format-lW58rL2y.js.map +1 -0
- package/dist/core/web/public/next/assets/{git-model-5v5xGobf.js → git-model-DaGw1SzS.js} +2 -2
- package/dist/core/web/public/next/assets/{git-model-5v5xGobf.js.map → git-model-DaGw1SzS.js.map} +1 -1
- package/dist/core/web/public/next/assets/global-DG1fmn4y.js +2 -0
- package/dist/core/web/public/next/assets/global-DG1fmn4y.js.map +1 -0
- package/dist/core/web/public/next/assets/{guarded-action-vOPc8v75.js → guarded-action--lguLXfz.js} +2 -2
- package/dist/core/web/public/next/assets/{guarded-action-vOPc8v75.js.map → guarded-action--lguLXfz.js.map} +1 -1
- package/dist/core/web/public/next/assets/{icons-DWb24OA6.js → icons-YPgbel-I.js} +2 -2
- package/dist/core/web/public/next/assets/{icons-DWb24OA6.js.map → icons-YPgbel-I.js.map} +1 -1
- package/dist/core/web/public/next/assets/{index-afDA8cwW.js → index-Dk8R-3a7.js} +3 -3
- package/dist/core/web/public/next/assets/{index-afDA8cwW.js.map → index-Dk8R-3a7.js.map} +1 -1
- package/dist/core/web/public/next/assets/live-model-44wEU6I8.js +2 -0
- package/dist/core/web/public/next/assets/live-model-44wEU6I8.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-BOABBdi_.js +2 -0
- package/dist/core/web/public/next/assets/mind.module-BOABBdi_.js.map +1 -0
- package/dist/core/web/public/next/assets/mutations-DjXM3IDg.js +2 -0
- package/dist/core/web/public/next/assets/{mutations-C6E10SPX.js.map → mutations-DjXM3IDg.js.map} +1 -1
- package/dist/core/web/public/next/assets/{overnight-queries-CCGfiS-g.js → overnight-queries-Be_eossf.js} +2 -2
- package/dist/core/web/public/next/assets/{overnight-queries-CCGfiS-g.js.map → overnight-queries-Be_eossf.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-Dl3SEQvh.js +1 -0
- package/dist/core/web/public/next/assets/{queries-BuYIEHst.js → queries-CYEVCq5r.js} +2 -2
- package/dist/core/web/public/next/assets/{queries-BuYIEHst.js.map → queries-CYEVCq5r.js.map} +1 -1
- package/dist/core/web/public/next/assets/{reasoning-pref-BVzGczV7.js → reasoning-pref-a_ZP6GzU.js} +2 -2
- package/dist/core/web/public/next/assets/{reasoning-pref-BVzGczV7.js.map → reasoning-pref-a_ZP6GzU.js.map} +1 -1
- package/dist/core/web/public/next/assets/seat-subscription-CRigL-GE.js +2 -0
- package/dist/core/web/public/next/assets/seat-subscription-CRigL-GE.js.map +1 -0
- package/dist/core/web/public/next/assets/{section-visibility-CSrsTcwK.js → section-visibility-u7q-Q9AA.js} +2 -2
- package/dist/core/web/public/next/assets/{section-visibility-CSrsTcwK.js.map → section-visibility-u7q-Q9AA.js.map} +1 -1
- package/dist/core/web/public/next/assets/{sidebar-model-C8fyqSQY.js → sidebar-model-i4WRuQ6j.js} +2 -2
- package/dist/core/web/public/next/assets/{sidebar-model-C8fyqSQY.js.map → sidebar-model-i4WRuQ6j.js.map} +1 -1
- package/dist/core/web/public/next/assets/slots-CV9ZuLQU.js +3 -0
- package/dist/core/web/public/next/assets/{slots-W8gkmdfE.js.map → slots-CV9ZuLQU.js.map} +1 -1
- package/dist/core/web/public/next/assets/surface-data-DLvmUX9z.js +2 -0
- package/dist/core/web/public/next/assets/surface-data-DLvmUX9z.js.map +1 -0
- package/dist/core/web/public/next/assets/surface-prefetch-Ct2JwXvL.js +2 -0
- package/dist/core/web/public/next/assets/surface-prefetch-Ct2JwXvL.js.map +1 -0
- package/dist/core/web/public/next/assets/tasks-model-DWs5u9RO.js +2 -0
- package/dist/core/web/public/next/assets/{tasks-model-CnYQzgxk.js.map → tasks-model-DWs5u9RO.js.map} +1 -1
- package/dist/core/web/public/next/assets/{terminal-icons-CxUYaWX9.js → terminal-icons-Ct9cZsEK.js} +2 -2
- package/dist/core/web/public/next/assets/{terminal-icons-CxUYaWX9.js.map → terminal-icons-Ct9cZsEK.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/{usage-queries-CQOpeE1H.js → usage-queries-CI_y9oWZ.js} +2 -2
- package/dist/core/web/public/next/assets/{usage-queries-CQOpeE1H.js.map → usage-queries-CI_y9oWZ.js.map} +1 -1
- package/dist/core/web/public/next/assets/use-guarded-action-DVCtLzSo.js +2 -0
- package/dist/core/web/public/next/assets/use-guarded-action-DVCtLzSo.js.map +1 -0
- package/dist/core/web/public/next/assets/{use-ticker-hryPXnyp.js → use-ticker-C3LQ2IYs.js} +2 -2
- package/dist/core/web/public/next/assets/{use-ticker-hryPXnyp.js.map → use-ticker-C3LQ2IYs.js.map} +1 -1
- package/dist/core/web/public/next/assets/use-token-gate-B5gOFkeH.js +2 -0
- package/dist/core/web/public/next/assets/use-token-gate-B5gOFkeH.js.map +1 -0
- package/dist/core/web/public/next/assets/{useActivity-Cg_9-7nt.js → useActivity-C3R1Sudh.js} +2 -2
- package/dist/core/web/public/next/assets/{useActivity-Cg_9-7nt.js.map → useActivity-C3R1Sudh.js.map} +1 -1
- package/dist/core/web/public/next/assets/useChartWidth-0APW0BR3.js +2 -0
- package/dist/core/web/public/next/assets/useChartWidth-0APW0BR3.js.map +1 -0
- package/dist/core/web/public/next/assets/useChartWidth-BAtVgn2n.css +1 -0
- package/dist/core/web/public/next/assets/{useSeatsRefresh-4Ov9eT1F.js → useSeatsRefresh-BN8bso-n.js} +2 -2
- package/dist/core/web/public/next/assets/{useSeatsRefresh-4Ov9eT1F.js.map → useSeatsRefresh-BN8bso-n.js.map} +1 -1
- package/dist/core/web/public/next/assets/verse-model-yHPwjf-7.js +2 -0
- package/dist/core/web/public/next/assets/verse-model-yHPwjf-7.js.map +1 -0
- package/dist/core/web/public/next/assets/{verse-queries-BjQoOTwv.js → verse-queries-2PxoBISm.js} +2 -2
- package/dist/core/web/public/next/assets/{verse-queries-BjQoOTwv.js.map → verse-queries-2PxoBISm.js.map} +1 -1
- package/dist/core/web/public/next/assets/verse-store-DHTuZpYa.js +3 -0
- package/dist/core/web/public/next/assets/verse-store-DHTuZpYa.js.map +1 -0
- package/dist/core/web/public/next/assets/verse-ui-store-Dy4HuDBV.js +3 -0
- package/dist/core/web/public/next/assets/{verse-ui-store-KTTKfv-T.js.map → verse-ui-store-Dy4HuDBV.js.map} +1 -1
- package/dist/core/web/public/next/assets/{viewport-ZB86jdES.js → viewport-DDoJ_xVq.js} +2 -2
- package/dist/core/web/public/next/assets/{viewport-ZB86jdES.js.map → viewport-DDoJ_xVq.js.map} +1 -1
- package/dist/core/web/public/next/assets/warmup-BsL2LdR-.js +3 -0
- package/dist/core/web/public/next/assets/warmup-BsL2LdR-.js.map +1 -0
- package/dist/core/web/public/next/assets/{workspace-model-4hq_Mv3N.js → workspace-model-Dd3LmIym.js} +2 -2
- package/dist/core/web/public/next/assets/{workspace-model-4hq_Mv3N.js.map → workspace-model-Dd3LmIym.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/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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"colors-BZ3a9wFf.js","names":[],"sources":["../../../../../../src/web-ui/components/charts/colors.ts"],"sourcesContent":["/**\n * components/charts/colors.ts — the one place a chart asks for a colour\n * (owner: unit C0, with chart-tokens.css; the chart components are C7's).\n * Every value is a `var(--chart-*)` / `var(--engine-*)` / `var(--status-*)`\n * reference, never a literal hex, so theming is automatic and light and dark\n * can never drift apart. The roles — quantity, categorical, unknown, queued,\n * engine, status — are explained in chart-tokens.css's header.\n */\n\n/**\n * Identity slots in the categorical rotation. SIX (V3.10): the palette is\n * derived to stay clear of every engine, status and quantity hue, and no\n * seventh family clears that bar — see design/tokens.css.\n */\nexport const SERIES_SLOT_COUNT = 6;\n\n/** The \"Other\"/overflow bucket colour — never part of the identity rotation. */\nexport const CHART_NEUTRAL = 'var(--chart-neutral)';\n\n/**\n * Fixed-order categorical colour for series `slot` (0-based). Colour follows\n * the entity, never its row number: assign each series its slot once (e.g.\n * by a stable sort) and keep it even when others are filtered out — never\n * re-pack slots to \"fill the gap\" (dataviz anti-pattern: recolor-on-filter).\n *\n * Past the sixth slot this answers the NEUTRAL \"Other\" colour instead of\n * cycling: a repeated colour would tell the reader two series are one. A\n * chart with more than six series should fold its tail into \"Other\" and say\n * so in the legend and the table.\n */\nexport function seriesColor(slot: number): string {\n if (!Number.isInteger(slot) || slot < 0 || slot >= SERIES_SLOT_COUNT) return CHART_NEUTRAL;\n return `var(--chart-series-${slot + 1})`;\n}\n\n/** Default single hue for magnitude (sequential) and for one nominal series. */\nexport const CHART_SEQUENTIAL = 'var(--chart-sequential)';\nexport const CHART_SEQUENTIAL_SOFT = 'var(--chart-sequential-soft)';\n\n/** Steps in the quantity ramp (`--chart-seq-1..7`, azure 214°). */\nexport const SEQ_STEP_COUNT = 7;\n\n/** Quantity ramp step 1..7 (1 = least). Out-of-range steps clamp. */\nexport function seqColor(step: number): string {\n const n = Number.isFinite(step) ? Math.max(1, Math.min(SEQ_STEP_COUNT, Math.round(step))) : 1;\n return `var(--chart-seq-${n})`;\n}\n\n/**\n * A magnitude in [0, 1] → its quantity step. For a known value only: an\n * unknown (null / NaN) is the hatch below, never the lightest step — a pale\n * azure cell would claim \"a little\", which is a measurement nobody made.\n */\nexport function quantityColor(fraction: number): string {\n return seqColor(quantityStep(fraction));\n}\n\n/** The quantity step (1..7) a magnitude in [0, 1] lands on — the rounding quantityColor uses. */\nexport function quantityStep(fraction: number): number {\n const f = Number.isFinite(fraction) ? Math.max(0, Math.min(1, fraction)) : 0;\n return Math.max(1, Math.min(SEQ_STEP_COUNT, Math.round(1 + f * (SEQ_STEP_COUNT - 1))));\n}\n\n/** Steps at or past this are the far half of the ramp from the surface (see quantityInk). */\nexport const QUANTITY_INK_FLIP_STEP = 5;\n\n/**\n * Ink for a number printed ON a quantity cell, chosen by the cell's\n * luminance — plain fill, never a stroke or halo (a surface-coloured halo\n * around dark text on a dark cell turned \"1\" into a blob).\n *\n * The ramp is mirrored against the surface between themes (light: more =\n * darker; dark: more = lighter), so steps 1–4 are always NEAR the surface\n * and steps 5–7 always FAR from it. Near cells take the quantity ink (the\n * end of the gray ramp: near-black in light, near-white in dark); far cells\n * take the surface colour as ink — white on dark azure in light mode,\n * near-black on pale azure in dark mode. Measured (WCAG, 8-bit channels as\n * the browser paints them): light step 4 is 5.61:1 with the ink vs 3.54:1\n * with white, step 5 is 4.88:1 with white vs 4.08:1 with the ink; dark\n * step 4 is 4.72:1 with the ink vs 3.80:1 with the surface, step 5 is\n * 5.14:1 with the surface vs 3.49:1 with the ink. (Dark step 4 was 4.49:1\n * with --text-primary — see --chart-quantity-ink in chart-tokens.css.)\n * chart-contrast.test.ts holds every step to 4.5:1 in both themes.\n */\nexport function quantityInk(fraction: number): string {\n return quantityStep(fraction) >= QUANTITY_INK_FLIP_STEP ? 'var(--chart-surface)' : 'var(--chart-quantity-ink)';\n}\n\nexport const CHART_DIVERGING_POS = 'var(--chart-diverging-pos)';\nexport const CHART_DIVERGING_NEG = 'var(--chart-diverging-neg)';\nexport const CHART_DIVERGING_MID = 'var(--chart-diverging-mid)';\n\nexport const CHART_GRID = 'var(--chart-grid)';\nexport const CHART_AXIS = 'var(--chart-axis)';\n\n/** Status tones (the StatusBadge vocabulary). Status colours carry STATE, never\n * identity, and always travel with a text label (legend, tooltip, table). */\nexport type ChartTone = 'neutral' | 'info' | 'running' | 'success' | 'warning' | 'danger' | 'unknown';\n\nexport function toneColor(tone: ChartTone): string {\n return `var(--status-${tone}-solid)`;\n}\n\n/** Sequential heat steps 0..4 (0 = a true zero, not \"no data\"). */\nexport function heatColor(step: number): string {\n const n = Math.max(0, Math.min(4, Math.round(step)));\n return `var(--chart-heat-${n})`;\n}\n\n/** Unfilled meter/gauge track: the lightest step of the quantity ramp, not a gray. */\nexport const CHART_TRACK = 'var(--chart-track)';\n\n// ---------------------------------------------------------------------------\n// Unknown, queued / parked\n// ---------------------------------------------------------------------------\n\n/** The unknown gray — for the hatch stroke and the outline around it. */\nexport const CHART_UNKNOWN = 'var(--chart-unknown)';\n\n/** A CSS background that draws the 45° unknown hatch (HTML bars, tiles, cells). */\nexport const CHART_UNKNOWN_HATCH = 'var(--chart-unknown-hatch)';\n\n/**\n * The SVG version of the same hatch, as data: render it once per chart as\n *\n * <pattern id={id} width={size} height={size} patternUnits=\"userSpaceOnUse\"\n * patternTransform={`rotate(${angle})`}>\n * <line x1={0} y1={0} x2={0} y2={size} stroke={stroke} strokeWidth={strokeWidth} />\n * </pattern>\n *\n * with `id` from `hatchPatternId(useId())` — ids are document-global, so a\n * shared literal id would let one chart's unmount blank another's hatch —\n * and fill unknown marks with `url(#id)` plus a CHART_UNKNOWN outline.\n */\nexport const UNKNOWN_HATCH = Object.freeze({\n size: 6,\n angle: 45,\n strokeWidth: 1.5,\n stroke: CHART_UNKNOWN,\n});\n\n/** A document-safe pattern id from React's `useId()` (which contains `:`). */\nexport function hatchPatternId(reactId: string): string {\n return `chart-hatch-${reactId.replace(/[^A-Za-z0-9_-]/g, '')}`;\n}\n\n/** Queued / parked work: an outline in this colour, no fill. */\nexport const CHART_QUEUED_OUTLINE = 'var(--chart-queued-outline)';\n\n// ---------------------------------------------------------------------------\n// Engines (identity only)\n// ---------------------------------------------------------------------------\n\nexport type ChartEngine = 'claude' | 'codex' | 'grok' | 'local';\n\n/**\n * An engine's identity colour. ONLY for charts that show nothing but engines\n * (runs by engine, usage bars) and the 2px tick beside a lane label — pass it\n * explicitly per series; a chart never picks engine colours by index. When\n * status colour is on screen, show engines by position and monogram instead.\n */\nexport function engineColor(engine: ChartEngine): string {\n return `var(--engine-${engine})`;\n}\n"],"mappings":"AAiBA,IAAa,EAAgB,uBAa7B,SAAgB,EAAY,EAAsB,CAEhD,MADI,CAAC,OAAO,UAAU,CAAI,GAAK,EAAO,GAAK,GAAA,EAAkC,EACtE,sBAAsB,EAAO,EAAE,EACxC,CAGA,IAAa,EAAmB,0BACnB,EAAwB,+BAMrC,SAAgB,EAAS,EAAsB,CAE7C,MAAO,mBADG,OAAO,SAAS,CAAI,EAAI,KAAK,IAAI,EAAG,KAAK,IAAA,EAAoB,KAAK,MAAM,CAAI,CAAC,CAAC,EAAI,EAChE,EAC9B,CAOA,SAAgB,EAAc,EAA0B,CACtD,OAAO,EAAS,EAAa,CAAQ,CAAC,CACxC,CAGA,SAAgB,EAAa,EAA0B,CAErD,OAAO,KAAK,IAAI,EAAG,KAAK,IAAA,EAAoB,KAAK,MAAM,GAD7C,OAAO,SAAS,CAAQ,EAAI,KAAK,IAAI,EAAG,KAAK,IAAI,EAAG,CAAQ,CAAC,EAAI,GACX,CAAmB,CAAC,CAAC,CACvF,CAuBA,SAAgB,EAAY,EAA0B,CACpD,OAAO,EAAa,CAAQ,GAAA,EAA8B,uBAAyB,2BACrF,CAEA,IAAa,EAAsB,6BACtB,EAAsB,6BACtB,EAAsB,6BAEtB,EAAa,oBACb,EAAa,oBAM1B,SAAgB,EAAU,EAAyB,CACjD,MAAO,gBAAgB,EAAK,QAC9B,CAGA,SAAgB,EAAU,EAAsB,CAE9C,MAAO,oBADG,KAAK,IAAI,EAAG,KAAK,IAAI,EAAG,KAAK,MAAM,CAAI,CAAC,CACvB,EAAE,EAC/B,CAGA,IAAa,EAAc,qBAwBd,EAAgB,OAAO,OAAO,CACzC,KAAM,EACN,MAAO,GACP,YAAa,IACb,OAAQ,sBACV,CAAC,EAGD,SAAgB,EAAe,EAAyB,CACtD,MAAO,eAAe,EAAQ,QAAQ,kBAAmB,EAAE,GAC7D,CAGA,IAAa,EAAuB,8BAcpC,SAAgB,EAAY,EAA6B,CACvD,MAAO,gBAAgB,EAAO,EAChC"}
|
package/dist/core/web/public/next/assets/{command-bus-CpYuam1F.js → command-bus-DHJJLWh8.js}
RENAMED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{a as e,s as t}from"./index-
|
|
2
|
-
//# sourceMappingURL=command-bus-
|
|
1
|
+
import{a as e,s as t}from"./index-Dk8R-3a7.js";var n=t(e(),1),r=new Map,i=null,a=4e3;function o(e){if(!i||i.id!==e)return;if(Date.now()>i.until){i=null;return}let{invocation:t}=i;i=null,queueMicrotask(()=>{c(e,t)})}function s(e,t){let n=r.get(e)??[];return n.push(t),r.set(e,n),o(e),()=>{let n=r.get(e);if(!n)return;let i=n.lastIndexOf(t);i>=0&&n.splice(i,1),n.length===0&&r.delete(e)}}function c(e,t={}){let n=r.get(e);if(!n)return!1;for(let r of[...n].reverse())try{if(r(t)!==!1)return!0}catch(t){return console.error(`[verse] command ${e} failed`,t),!0}return!1}function l(e,t={},n=a){return c(e,t)?!0:(i={id:e,invocation:t,until:Date.now()+n},!1)}function u(e,t,r=!0){let i=(0,n.useRef)(t);i.current=t,(0,n.useEffect)(()=>{if(r)return s(e,e=>i.current(e))},[e,r])}export{u as a,l as i,s as n,c as r,a as t};
|
|
2
|
+
//# sourceMappingURL=command-bus-DHJJLWh8.js.map
|
package/dist/core/web/public/next/assets/{command-bus-CpYuam1F.js.map → command-bus-DHJJLWh8.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command-bus-
|
|
1
|
+
{"version":3,"file":"command-bus-DHJJLWh8.js","names":[],"sources":["../../../../../../src/web-ui/routes/verse/shell/command-bus.ts"],"sourcesContent":["/**\n * routes/verse/shell/command-bus.ts — how a catalog command reaches the unit\n * that owns its behaviour (unit C1; the keys and titles are C0's\n * command-catalog.ts).\n *\n * The catalog is one table; the behaviour behind it is spread over units:\n * the shell runs the global commands, the chat (C2) runs `dock.*` and\n * `chat.*`, the composer (C3) runs `composer.*`. The palette, the desktop\n * menu bridge and the shell's key handler all call `runCommand(id)`; the\n * owning unit registers a handler for the ids it serves:\n *\n * useCommandHandler('dock.terminal', () => openDockPane('terminal'));\n *\n * A handler may return `false` to decline (e.g. a composer command with no\n * chat open), and the most recently registered handler for an id is asked\n * first — a mounted chat outranks nothing, a focused one outranks a hidden\n * one because it registers last.\n *\n * DELIVERY LATER. A chat command run from another surface (⌘K \"Terminal\"\n * while on Fleet) has no handler until Chat mounts. The shell switches to\n * Chat and parks the command with `runCommandWhenReady`; the first handler to\n * register for that id within the window receives it. Parked commands expire\n * — a command that runs 20 s after the operator asked for it is a surprise,\n * not a feature.\n *\n * Framework-free except the one hook at the bottom.\n */\nimport { useEffect, useRef } from 'react';\n\n/** What a palette argument (Tab) filled in, or what a caller passes along. */\nexport interface CommandArgument {\n kind: 'seat' | 'project' | 'section' | 'session';\n id: string;\n label: string;\n}\n\nexport interface CommandInvocation {\n argument?: CommandArgument;\n /** Where it came from — handlers may word a toast differently for a key press. */\n via?: 'palette' | 'key' | 'menu' | 'button';\n}\n\n/** Return false to decline (the next handler is asked); anything else = handled. */\nexport type CommandHandler = (invocation: CommandInvocation) => void | boolean;\n\nconst handlers = new Map<string, CommandHandler[]>();\n\ninterface Parked {\n id: string;\n invocation: CommandInvocation;\n until: number;\n}\n\nlet parked: Parked | null = null;\n\nexport const PARKED_COMMAND_TTL_MS = 4_000;\n\nfunction deliverParked(id: string): void {\n if (!parked || parked.id !== id) return;\n if (Date.now() > parked.until) {\n parked = null;\n return;\n }\n const { invocation } = parked;\n parked = null;\n // After the registering effect has finished: the handler's own component\n // is mounted, but its sibling effects may not have run yet.\n queueMicrotask(() => {\n runCommand(id, invocation);\n });\n}\n\n/** Register `handler` for command `id`. Returns the unregister function. */\nexport function registerCommandHandler(id: string, handler: CommandHandler): () => void {\n const list = handlers.get(id) ?? [];\n list.push(handler);\n handlers.set(id, list);\n deliverParked(id);\n return () => {\n const current = handlers.get(id);\n if (!current) return;\n const index = current.lastIndexOf(handler);\n if (index >= 0) current.splice(index, 1);\n if (current.length === 0) handlers.delete(id);\n };\n}\n\nexport function hasCommandHandler(id: string): boolean {\n return (handlers.get(id)?.length ?? 0) > 0;\n}\n\n/** Run `id` now. True when a handler took it. */\nexport function runCommand(id: string, invocation: CommandInvocation = {}): boolean {\n const list = handlers.get(id);\n if (!list) return false;\n for (const handler of [...list].reverse()) {\n try {\n if (handler(invocation) !== false) return true;\n } catch (err) {\n // A broken handler must not take the palette or the key handler down.\n console.error(`[verse] command ${id} failed`, err);\n return true;\n }\n }\n return false;\n}\n\n/** Run `id` now if someone serves it, else hold it for the next handler to register (within the TTL). */\nexport function runCommandWhenReady(id: string, invocation: CommandInvocation = {}, ttlMs: number = PARKED_COMMAND_TTL_MS): boolean {\n if (runCommand(id, invocation)) return true;\n parked = { id, invocation, until: Date.now() + ttlMs };\n return false;\n}\n\n/** Test hygiene. */\nexport function resetCommandBus(): void {\n handlers.clear();\n parked = null;\n}\n\n/**\n * Serve command `id` while mounted (and `enabled`). The latest `handler` is\n * always the one called, so an inline closure does not re-register each render.\n */\nexport function useCommandHandler(id: string, handler: CommandHandler, enabled = true): void {\n const ref = useRef(handler);\n ref.current = handler;\n useEffect(() => {\n if (!enabled) return;\n return registerCommandHandler(id, (invocation) => ref.current(invocation));\n }, [id, enabled]);\n}\n"],"mappings":"8DA6CM,EAAW,IAAI,IAQjB,EAAwB,KAEf,EAAwB,IAErC,SAAS,EAAc,EAAkB,CACvC,GAAI,CAAC,GAAU,EAAO,KAAO,EAAI,OACjC,GAAI,KAAK,IAAI,EAAI,EAAO,MAAO,CAC7B,EAAS,KACT,MACF,CACA,GAAM,CAAE,cAAe,EACvB,EAAS,KAGT,mBAAqB,CACnB,EAAW,EAAI,CAAU,CAC3B,CAAC,CACH,CAGA,SAAgB,EAAuB,EAAY,EAAqC,CACtF,IAAM,EAAO,EAAS,IAAI,CAAE,GAAK,CAAC,EAIlC,OAHA,EAAK,KAAK,CAAO,EACjB,EAAS,IAAI,EAAI,CAAI,EACrB,EAAc,CAAE,MACH,CACX,IAAM,EAAU,EAAS,IAAI,CAAE,EAC/B,GAAI,CAAC,EAAS,OACd,IAAM,EAAQ,EAAQ,YAAY,CAAO,EACrC,GAAS,GAAG,EAAQ,OAAO,EAAO,CAAC,EACnC,EAAQ,SAAW,GAAG,EAAS,OAAO,CAAE,CAC9C,CACF,CAOA,SAAgB,EAAW,EAAY,EAAgC,CAAC,EAAY,CAClF,IAAM,EAAO,EAAS,IAAI,CAAE,EAC5B,GAAI,CAAC,EAAM,MAAO,GAClB,IAAK,IAAM,IAAW,CAAC,GAAG,CAAI,CAAC,CAAC,QAAQ,EACtC,GAAI,CACF,GAAI,EAAQ,CAAU,IAAM,GAAO,MAAO,EAC5C,OAAS,EAAK,CAGZ,OADA,QAAQ,MAAM,mBAAmB,EAAG,SAAU,CAAG,EAC1C,EACT,CAEF,MAAO,EACT,CAGA,SAAgB,EAAoB,EAAY,EAAgC,CAAC,EAAG,EAAgB,EAAgC,CAGlI,OAFI,EAAW,EAAI,CAAU,EAAU,IACvC,EAAS,CAAE,KAAI,aAAY,MAAO,KAAK,IAAI,EAAI,CAAM,EAC9C,GACT,CAYA,SAAgB,EAAkB,EAAY,EAAyB,EAAU,GAAY,CAC3F,IAAM,GAAA,EAAM,EAAA,OAAA,CAAO,CAAO,EAC1B,EAAI,QAAU,GACd,EAAA,EAAA,UAAA,KAAgB,CACT,KACL,OAAO,EAAuB,EAAK,GAAe,EAAI,QAAQ,CAAU,CAAC,CAC3E,EAAG,CAAC,EAAI,CAAO,CAAC,CAClB"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{$ as e,G as t,W as n,Z as r,nt as i}from"./global-DG1fmn4y.js";import{r as a}from"./verse-store-DHTuZpYa.js";import{f as o}from"./format-lW58rL2y.js";import{S as s,b as c,i as l,m as u,t as d}from"./verse-model-yHPwjf-7.js";function f(e,t){return e?u([e],{seatId:e.id,model:t}):null}function p(e,t){return t!==`standard`&&n(e,t)!==null?t:`standard`}function m(e,t){return n(e,p(e,t))}function h(e,t){let n=c(e);if(n!==null)return n;let r=t.unavailableReason;return typeof r==`string`&&r.trim().length>0?r.trim():null}var g={standard:`Standard`,expansive:`Expansive`},_={claude:`Claude Code`,codex:`Codex CLI`,grok:`Grok CLI`,local:`Claude Code`};function v(e){let t=typeof e.cliVersion==`string`?e.cliVersion.trim():``;return t.length>0?`${_[e.engine]} ${t}`:null}function y(e){return(e.notes??[]).filter(e=>typeof e==`string`&&e.trim().length>0)}function b(e){return e===`fallback`}function x(e,t=`standard`){let r=p(e,t),i=n(e,r);return i?`${`${a(i.contextWindow)} ctx${b(e.windowSource)?` (est.)`:``}`}${i.autoCompactAt===null?``:` · compacts ≈${a(i.autoCompactAt)}`}${r===`expansive`?` (expansive)`:``}`:`window unknown`}function S(t){let r=n(t,`standard`),i=e(t)?n(t,`expansive`):null,o=[];if(!r)o.push(`Context window unknown for this model.`);else{let e=r.autoCompactAt===null?``:`; compacts at about ${a(r.autoCompactAt)}${i?` in Standard`:``}`;o.push(`${a(r.contextWindow)}-token window${e}.`)}return i&&o.push(`Expansive runs to about ${a(i.autoCompactAt??i.contextWindow)} before compacting.`),t.windowSource&&o.push(`Window ${l[t.windowSource]}.`),o.join(` `)}function C(e){return e===`codex`?d:null}function w(e){let t=n(e,`standard`),r=n(e,`expansive`),i=t?t.autoCompactAt??t.contextWindow:null,o=r?r.autoCompactAt??r.contextWindow:null;return i===null||o===null||i<=0?null:`A turn at ${a(o)} re-sends ${(o/i).toFixed(1)}× the tokens of one at ${a(i)}.`}function T(e,t,r){let i=n(t,`standard`),o=n(t,`expansive`),s=i?i.autoCompactAt??i.contextWindow:null,c=o?o.autoCompactAt??o.contextWindow:null;if(r===`standard`||c===null)return s===null?`The CLI compacts at its own default point for this model.`:e===`claude`?`Compacts at about ${a(s)}. Every turn re-sends the whole context, so holding it under this point keeps each turn cheaper and recall sharper.`:`Compacts at about ${a(s)} — the CLI's own default budget for this model.`;let l=w(t),u=C(e);return`Runs to about ${a(c)} before compacting — for tightly coupled, cross-cutting work that needs everything in view at once. Each turn re-sends everything held, so a long expansive chat uses noticeably more of your plan per turn.${l?` ${l}`:``}${u?` ${u}`:``}`}function E(e,t){return n(t,`standard`)?e===`grok`||e===`local`?`One budget: this CLI compacts at its own fixed point, and Verse cannot raise it per chat.`:`One budget: this model has no larger window to expand into.`:`This model’s window is unknown, so Verse cannot offer a larger budget for it.`}var D={fits:`code fits`,tight:`code fits, tight`,expansive:`code needs expansive`,split:`code too big — split`},O={fits:`Fits`,tight:`Tight fit`,expansive:`Needs expansive`,split:`Split the work`};function k(e){return e.roots.some(e=>e.truncated)}function A(e,t,a,o){if(typeof e!=`number`||!Number.isFinite(e)||e<0)return null;let s=i(o);if(p(t,a)===`expansive`&&t){let i=n(t,`expansive`);return r(e,{...t,contextWindow:i.contextWindow,autoCompactAt:i.autoCompactAt,expansive:null},s)}return r(e,t,s)}function j(t){let r=`${t.floor?`at least `:``}~${a(t.tokens)} tokens of tracked code`,i=p(t.option,t.mode)===`expansive`,o=n(t.option,`standard`),s=n(t.option,`expansive`),c=o?a(o.autoCompactAt??o.contextWindow):null,l=s?a(s.autoCompactAt??s.contextWindow):null,u=i?l:c,d=i?`Expansive budget`:`budget`;switch(t.verdict){case`fits`:return`All ${r} fits well inside this model's ${d}${u?` (compacts ≈${u})`:``}, with room left for the conversation.`;case`tight`:{let n=!i&&l!==null&&e(t.option)?` Expansive (≈${l}) would hold it with room to spare.`:``;return`${N(r)} fits under the ≈${u??`?`} compaction point${i?` of the Expansive budget`:``}, with little room for the conversation — expect it to compact if the agent reads widely.${n}`}case`expansive`:return`${N(r)} is more than the Standard budget (≈${c??`?`}) holds; only Expansive (≈${l??`?`}) takes it all. Switch to Expansive, or narrow the folders to the part you are changing.`;case`split`:return`${N(r)} is more than any single context on this model holds${l?`, even Expansive (≈${l})`:``}. Split the work: fan it out across several chats, each scoped to one folder or subsystem, or narrow this chat to the part you are changing.`}}function M(e){let t=e?` for ${_[e]}`:``;return`Estimated from the size of tracked text files (bytes ÷ 4), plus an estimated ~${a(i(e))} of fixed prompt${t} that every chat starts with. An agent rarely reads everything, so this is a ceiling, not a forecast.`}function N(e){return e.length===0?e:e.charAt(0).toUpperCase()+e.slice(1)}function P(t,n){let r=s(n,t);return{tokens:Math.floor(r.contextTokens),exact:r.exact,window:r.contextWindow,autoCompactAt:r.autoCompactAt,source:r.source,mode:r.mode,hasModes:e(r.option)}}function F(e){return e.cacheReadTokens<=0&&e.cacheCreationTokens<=0?null:t(e)}function I(e){return e===null||!Number.isFinite(e)?`—`:o(e)}function L(e){let t=new Map,n=0,r=null,i=(e,n,r)=>{if(!Number.isFinite(n)||n<0)return;let i=t.get(e)??{exact:null,bound:null};r?i.exact=Math.max(i.exact??0,n):i.bound=Math.max(i.bound??0,n),t.set(e,i)};for(let t of e)switch(t.type){case`usage`:r=t.usage,i(t.turnId,t.usage.contextTokens,t.usage.contextTokensExact!==!1);break;case`context`:t.turnId!==null&&i(t.turnId,t.contextTokens,t.exact);break;case`compaction`:n+=1}let a=0,o=null,s=!0,c=0;for(let e of t.values()){let t=e.exact??e.bound;t!==null&&(e.exact===null&&(s=!1),c+=1,a+=t,o=o===null?t:Math.max(o,t))}return{turns:c,average:c===0?null:Math.round(a/c),peak:o,exact:s,compactions:n,lastTurnCacheHit:r===null?null:F(r)}}function R(e,t,n,r){if(e.status===`running`||r===null)return null;let a=Date.parse(r);if(!Number.isFinite(a))return null;let o=n-a;return o<36e5||t<i(e.engine)?null:{idleMs:o,tokens:t,local:e.engine===`local`}}function z(e,t){return e.window!==null&&t.window!==null?e.tokens/e.window>t.tokens/t.window:e.window!==null||t.window===null&&e.tokens>t.tokens}function B(e,t){let n=new Map;for(let t of e){let e=n.get(t.seatId)??[];e.push(t),n.set(t.seatId,e)}let r=[];for(let[e,i]of n){let n=t.find(t=>t.id===e)??null,a=0,o=0,s=0,c=0,l=0,u=0,d=0,f=null;for(let e of i){a+=e.usage.inputTokens,o+=e.usage.cacheReadTokens,s+=e.usage.cacheCreationTokens,c+=e.usage.outputTokens,l+=e.turnCount,u+=e.compactionCount??0;let n=P(e,t);if(n.mode===`expansive`&&(d+=1),n.tokens<=0)continue;let r={sessionId:e.id,title:e.title,tokens:n.tokens,window:n.window,exact:n.exact};(f===null||z(r,f))&&(f=r)}r.push({seatId:e,label:n?.label??e,engine:n?.engine??i[0].engine,retired:n===null,sessions:i.length,turns:l,promptTokens:a+o+s,outputTokens:c,cacheHitRatio:F({inputTokens:a,cacheReadTokens:o,cacheCreationTokens:s}),compactions:u,expansiveSessions:d,fullest:f})}return r.sort((e,t)=>t.promptTokens-e.promptTokens||e.label.localeCompare(t.label))}export{f as C,B as S,L as T,E as _,m as a,v as b,j as c,I as d,R as f,h as g,A as h,T as i,k as l,S as m,O as n,C as o,x as p,D as r,w as s,g as t,M as u,F as v,P as w,y as x,p as y};
|
|
2
|
+
//# sourceMappingURL=context-model-BGznyHNQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-model-BGznyHNQ.js","names":[],"sources":["../../../../../../src/web-ui/routes/verse/usage/context-model.ts"],"sourcesContent":["/**\n * routes/verse/usage/context-model.ts — the ONE projection of context facts\n * for every surface that shows a seat: the seat picker's rows, the new-chat\n * dialog's context block, the resources panel's \"This chat\" efficiency block,\n * and the Usage section's per-seat efficiency table.\n *\n * WHY ONE MODULE. Before V3.9 every surface read `seat.contextWindow` (the\n * DEFAULT model's window) or the session's creation-time window, and three of\n * them rendered \"200k\" for a 1M Claude model. The arithmetic now lives in\n * core/verse/context-math.ts, shared with the server; this module is only the\n * wording and the aggregation, so the picker, the dialog and the panel cannot\n * describe one model's budget three different ways.\n *\n * HONESTY RULES this module enforces (docs/VERSE-CONTEXT.md):\n *\n * 1. UNKNOWN IS NOT A NUMBER. A model whose budget is unknown reads \"window\n * unknown\", never a default dressed as a fact. A Verse fallback window is\n * marked \"est.\", and its source says so in words.\n * 2. AN UPPER BOUND IS NOT A MEASUREMENT. Codex's turn total sums every model\n * call; until its rollout is read, occupancy is \"≤ N\", and any statistic\n * built on such a reading carries the same flag.\n * 3. NO CACHE REPORTED IS NOT A 0% HIT RATE. A provider that reported no\n * cache activity at all (Ollama; codex on its very first call) gets \"none\n * reported\", not a 0% that reads \"caching is broken\".\n * 4. NOTHING HERE SPENDS. Every figure is derived from what the server\n * already returned (seats, sessions, the open chat's event log).\n *\n * Pure: no React, no I/O.\n */\nimport {\n CACHE_IDLE_TTL_MS,\n budgetFor,\n cacheHitRatio,\n fitVerdict,\n hasExpansiveMode,\n sessionOverheadTokens,\n} from '../../../../core/verse/context-math.js';\nimport type {\n VerseContextBudget,\n VerseContextFit,\n VerseContextMode,\n VerseEngine,\n VerseEvent,\n VerseFitVerdict,\n VerseModelOption,\n VerseSeat,\n VerseSession,\n VerseUsage,\n VerseWindowSource,\n} from '../../../../core/verse/types.js';\nimport { CODEX_EXPANSIVE_METERING_NOTE, WINDOW_SOURCE_TEXT, modelOptionFor, seatUnavailableReason, sessionContextBudget } from '../verse-model.js';\nimport { formatTokens } from '../verse-store.js';\nimport { formatWholePercent } from '../autonomy/format.js';\n\n// ---------------------------------------------------------------------------\n// Models and modes\n// ---------------------------------------------------------------------------\n\n/**\n * A seat's option for a model id, matching through the alias table (a chat\n * remembered on `claude-opus-5.5` is the catalog's `claude-opus-5-5`). The\n * lookup itself is verse-model's `modelOptionFor`, the one the context meter\n * uses, so the picker and the meter can never resolve one id two ways.\n */\nexport function seatModelOption(seat: VerseSeat | null | undefined, modelId: string): VerseModelOption | null {\n return seat ? modelOptionFor([seat], { seatId: seat.id, model: modelId }) : null;\n}\n\n/** `mode` when this model has a budget for it, else `standard` — never a mode the CLI cannot be told. */\nexport function resolveContextMode(option: VerseModelOption | null | undefined, mode: VerseContextMode): VerseContextMode {\n return mode !== 'standard' && budgetFor(option, mode) !== null ? mode : 'standard';\n}\n\n/** The budget a model runs with in a mode (resolved), or null when even its standard window is unknown. */\nexport function effectiveBudget(option: VerseModelOption | null | undefined, mode: VerseContextMode): VerseContextBudget | null {\n return budgetFor(option, resolveContextMode(option, mode));\n}\n\n/** Why this model cannot be chosen on this seat, or null when it can. The seat's own outage wins. */\nexport function modelUnavailableReason(seat: VerseSeat, option: VerseModelOption): string | null {\n const seatReason = seatUnavailableReason(seat);\n if (seatReason !== null) return seatReason;\n const reason = option.unavailableReason;\n return typeof reason === 'string' && reason.trim().length > 0 ? reason.trim() : null;\n}\n\nexport const CONTEXT_MODE_LABEL: Record<VerseContextMode, string> = {\n standard: 'Standard',\n expansive: 'Expansive',\n};\n\n/** How each seat's CLI is named when its pinned version is shown. */\nexport const ENGINE_CLI_NAME: Record<VerseEngine, string> = {\n claude: 'Claude Code',\n codex: 'Codex CLI',\n grok: 'Grok CLI',\n local: 'Claude Code',\n};\n\n/** \"Claude Code 2.1.257\" — the binary this seat is pinned to; null when unknown (local seats, old servers). */\nexport function seatCliLine(seat: VerseSeat): string | null {\n const version = typeof seat.cliVersion === 'string' ? seat.cliVersion.trim() : '';\n return version.length > 0 ? `${ENGINE_CLI_NAME[seat.engine]} ${version}` : null;\n}\n\n/** The seat's own plain-language context facts (binary skew, catalog not yet fetched, …). */\nexport function seatContextNotes(seat: VerseSeat): string[] {\n return (seat.notes ?? []).filter((n): n is string => typeof n === 'string' && n.trim().length > 0);\n}\n\n// ---------------------------------------------------------------------------\n// Window provenance\n// ---------------------------------------------------------------------------\n\n// Window provenance wording is verse-model's `WINDOW_SOURCE_TEXT` — the\n// meter's tooltip, the picker and the resources panel all read that one table.\n\nexport function isEstimatedWindow(source: VerseWindowSource | null | undefined): boolean {\n return source === 'fallback';\n}\n\n// ---------------------------------------------------------------------------\n// Picker and dialog wording\n// ---------------------------------------------------------------------------\n\n/**\n * The compact per-model phrase a picker row carries: \"1M ctx · compacts ≈367k\".\n * Uses the budget for `mode` when the model has one, and names the mode only\n * when it is not the default, so an ordinary row stays short.\n */\nexport function modelContextPhrase(option: VerseModelOption, mode: VerseContextMode = 'standard'): string {\n const resolved = resolveContextMode(option, mode);\n const budget = budgetFor(option, resolved);\n if (!budget) return 'window unknown';\n const window = `${formatTokens(budget.contextWindow)} ctx${isEstimatedWindow(option.windowSource) ? ' (est.)' : ''}`;\n const compacts = budget.autoCompactAt === null ? '' : ` · compacts ≈${formatTokens(budget.autoCompactAt)}`;\n return `${window}${compacts}${resolved === 'expansive' ? ' (expansive)' : ''}`;\n}\n\n/**\n * The full sentence for a tooltip or the dialog: the window, where each mode\n * compacts, and how the window is known.\n */\nexport function modelContextSentence(option: VerseModelOption): string {\n const standard = budgetFor(option, 'standard');\n const expansive = hasExpansiveMode(option) ? budgetFor(option, 'expansive') : null;\n const parts: string[] = [];\n if (!standard) {\n parts.push('Context window unknown for this model.');\n } else {\n const where = standard.autoCompactAt === null\n ? ''\n : `; compacts at about ${formatTokens(standard.autoCompactAt)}${expansive ? ' in Standard' : ''}`;\n parts.push(`${formatTokens(standard.contextWindow)}-token window${where}.`);\n }\n if (expansive) {\n parts.push(`Expansive runs to about ${formatTokens(expansive.autoCompactAt ?? expansive.contextWindow)} before compacting.`);\n }\n if (option.windowSource) parts.push(`Window ${WINDOW_SOURCE_TEXT[option.windowSource]}.`);\n return parts.join(' ');\n}\n\n/**\n * The metering caveat an Expansive cost sentence must carry for this engine,\n * or null when there is none. The wording is verse-model's\n * CODEX_EXPANSIVE_METERING_NOTE — ONE sentence for the new-chat dialog, the\n * handoff dialog, the in-chat mode menu and the \"Expansive could help\" chip.\n * The ratio those surfaces quote (≈3.2× for a 785k vs 245k codex turn) counts\n * re-sent TOKENS only; GPT-5.6-class requests above 272k reportedly also\n * count about 2× against plan limits (docs/VERSE-CONTEXT.md §2.3, a single\n * secondary source), so the note is worded as reported, never as a measured\n * multiplier. Standard never crosses 272k; Expansive does by design.\n */\nexport function expansiveMeteringNote(engine: VerseEngine | null | undefined): string | null {\n return engine === 'codex' ? CODEX_EXPANSIVE_METERING_NOTE : null;\n}\n\n/**\n * \"A turn at 967k re-sends 2.6× the tokens of one at 367k.\" — the only ratio\n * any Expansive cost sentence quotes, and it is arithmetic on the model's own\n * budgets. Null when either compaction point is unknown. It comes BEFORE the\n * codex metering note, whose \"twice that\" refers to it.\n */\nexport function expansiveRatioSentence(option: VerseModelOption | null | undefined): string | null {\n const standard = budgetFor(option, 'standard');\n const expansive = budgetFor(option, 'expansive');\n const stdCap = standard ? standard.autoCompactAt ?? standard.contextWindow : null;\n const expCap = expansive ? expansive.autoCompactAt ?? expansive.contextWindow : null;\n if (stdCap === null || expCap === null || stdCap <= 0) return null;\n return `A turn at ${formatTokens(expCap)} re-sends ${(expCap / stdCap).toFixed(1)}× the tokens of one at ${formatTokens(stdCap)}.`;\n}\n\n/**\n * What each mode costs, in plain words, for the mode selector. Numbers come\n * from the model's own budgets; the only ratio quoted is arithmetic (every\n * turn re-sends everything held, so a turn at X re-sends X/Y of one at Y) —\n * a token ratio, not a usage multiplier, which is why codex also carries the\n * shared metering caveat.\n */\nexport function contextModeDescription(engine: VerseEngine, option: VerseModelOption, mode: VerseContextMode): string {\n const standard = budgetFor(option, 'standard');\n const expansive = budgetFor(option, 'expansive');\n const stdCap = standard ? standard.autoCompactAt ?? standard.contextWindow : null;\n const expCap = expansive ? expansive.autoCompactAt ?? expansive.contextWindow : null;\n if (mode === 'standard' || expCap === null) {\n if (stdCap === null) return 'The CLI compacts at its own default point for this model.';\n return engine === 'claude'\n ? `Compacts at about ${formatTokens(stdCap)}. Every turn re-sends the whole context, so holding it under this point keeps each turn cheaper and recall sharper.`\n : `Compacts at about ${formatTokens(stdCap)} — the CLI's own default budget for this model.`;\n }\n const ratio = expansiveRatioSentence(option);\n const metering = expansiveMeteringNote(engine);\n return `Runs to about ${formatTokens(expCap)} before compacting — for tightly coupled, cross-cutting work that needs everything in view at once. Each turn re-sends everything held, so a long expansive chat uses noticeably more of your plan per turn.${ratio ? ` ${ratio}` : ''}${metering ? ` ${metering}` : ''}`;\n}\n\n/** Why a model offers no mode choice, for the one line where the selector would be. */\nexport function noModeReason(engine: VerseEngine, option: VerseModelOption): string {\n if (!budgetFor(option, 'standard')) return 'This model’s window is unknown, so Verse cannot offer a larger budget for it.';\n if (engine === 'grok' || engine === 'local') return 'One budget: this CLI compacts at its own fixed point, and Verse cannot raise it per chat.';\n return 'One budget: this model has no larger window to expand into.';\n}\n\n// ---------------------------------------------------------------------------\n// Context fit\n// ---------------------------------------------------------------------------\n\n/** Short form for a picker row, where it sits beside the capacity words. */\nexport const FIT_SHORT: Record<VerseFitVerdict, string> = {\n fits: 'code fits',\n tight: 'code fits, tight',\n expansive: 'code needs expansive',\n split: 'code too big — split',\n};\n\n/** Badge text for the chosen model. */\nexport const FIT_LABEL: Record<VerseFitVerdict, string> = {\n fits: 'Fits',\n tight: 'Tight fit',\n expansive: 'Needs expansive',\n split: 'Split the work',\n};\n\n/** A fit reading that is a FLOOR (a root hit its file or time cap). */\nexport function fitIsFloor(fit: VerseContextFit): boolean {\n return fit.roots.some((r) => r.truncated);\n}\n\n/**\n * The verdict for a model IN THE MODE THE CHAT WOULD RUN IN, or null when its\n * budget is unknown (never guessed).\n *\n * `engine` is the seat's engine: the fixed prompt added to the code is that\n * CLI's estimate (context-math `sessionOverheadTokens` — ≈15k local, ≈25k\n * claude), not one flat figure. With a flat 30k a 64k local seat (compacts\n * ≈32.5k) called anything over ~2.5k tokens of code \"too big — split\". Null\n * falls back to the largest estimate.\n *\n * In Standard this is context-math's `fitVerdict` as is — including\n * \"expansive\", the suggestion that only the bigger budget holds the code. In\n * Expansive the bigger budget IS the budget, so the same thresholds are\n * applied to it: the code fits, fits tightly, or no single context on this\n * model holds it. Without this, a chat already set to Expansive would still be\n * told its code \"fits tightly under ≈367k\" — true of a budget it is not using.\n */\nexport function modelFit(\n tokens: number | null | undefined,\n option: VerseModelOption | null | undefined,\n mode: VerseContextMode,\n engine: VerseEngine | null,\n): VerseFitVerdict | null {\n if (typeof tokens !== 'number' || !Number.isFinite(tokens) || tokens < 0) return null;\n const overhead = sessionOverheadTokens(engine);\n if (resolveContextMode(option, mode) === 'expansive' && option) {\n const expansive = budgetFor(option, 'expansive')!;\n return fitVerdict(tokens, { ...option, contextWindow: expansive.contextWindow, autoCompactAt: expansive.autoCompactAt, expansive: null }, overhead);\n }\n return fitVerdict(tokens, option, overhead);\n}\n\n/**\n * The sentence under the fit badge, for the verdict `modelFit` gave in the\n * chat's mode. In Standard a tight fit also names the Expansive budget when\n * one exists, and \"needs expansive\" says what to do — suggestions only;\n * nothing here switches the mode.\n */\nexport function fitExplanation(input: {\n verdict: VerseFitVerdict;\n tokens: number;\n option: VerseModelOption;\n mode: VerseContextMode;\n floor?: boolean;\n}): string {\n const amount = `${input.floor ? 'at least ' : ''}~${formatTokens(input.tokens)} tokens of tracked code`;\n const inExpansive = resolveContextMode(input.option, input.mode) === 'expansive';\n const standard = budgetFor(input.option, 'standard');\n const expansive = budgetFor(input.option, 'expansive');\n const stdCap = standard ? formatTokens(standard.autoCompactAt ?? standard.contextWindow) : null;\n const expCap = expansive ? formatTokens(expansive.autoCompactAt ?? expansive.contextWindow) : null;\n const cap = inExpansive ? expCap : stdCap;\n const budget = inExpansive ? 'Expansive budget' : 'budget';\n switch (input.verdict) {\n case 'fits':\n return `All ${amount} fits well inside this model's ${budget}${cap ? ` (compacts ≈${cap})` : ''}, with room left for the conversation.`;\n case 'tight': {\n const roomier = !inExpansive && expCap !== null && hasExpansiveMode(input.option)\n ? ` Expansive (≈${expCap}) would hold it with room to spare.`\n : '';\n return `${capitalize(amount)} fits under the ≈${cap ?? '?'} compaction point${inExpansive ? ' of the Expansive budget' : ''}, with little room for the conversation — expect it to compact if the agent reads widely.${roomier}`;\n }\n case 'expansive':\n return `${capitalize(amount)} is more than the Standard budget (≈${stdCap ?? '?'}) holds; only Expansive (≈${expCap ?? '?'}) takes it all. Switch to Expansive, or narrow the folders to the part you are changing.`;\n case 'split':\n return `${capitalize(amount)} is more than any single context on this model holds${expCap ? `, even Expansive (≈${expCap})` : ''}. Split the work: fan it out across several chats, each scoped to one folder or subsystem, or narrow this chat to the part you are changing.`;\n }\n}\n\n/**\n * How the working-set figure is made — shown once, beside the badge. Names the\n * fixed-prompt figure the verdict added, and calls it an estimate: no CLI\n * reports its base prompt before a turn has run.\n */\nexport function fitMethodNote(engine: VerseEngine | null): string {\n const cli = engine ? ` for ${ENGINE_CLI_NAME[engine]}` : '';\n return `Estimated from the size of tracked text files (bytes ÷ 4), plus an estimated ~${formatTokens(sessionOverheadTokens(engine))} of fixed prompt${cli} that every chat starts with. An agent rarely reads everything, so this is a ceiling, not a forecast.`;\n}\n\nfunction capitalize(text: string): string {\n return text.length === 0 ? text : text.charAt(0).toUpperCase() + text.slice(1);\n}\n\n// ---------------------------------------------------------------------------\n// One session's context\n// ---------------------------------------------------------------------------\n\nexport interface SessionContextView {\n tokens: number;\n /** False when `tokens` is an upper bound (codex before its rollout is read). */\n exact: boolean;\n window: number | null;\n autoCompactAt: number | null;\n /** How the window is known; null when nothing says (no window, or a stored one without provenance). */\n source: VerseWindowSource | null;\n mode: VerseContextMode;\n /** The session's model has a real expansive budget on its seat. */\n hasModes: boolean;\n}\n\n/**\n * The window a session is measured against, by the precedence every surface\n * shares — runtime → the seat's CURRENT catalog budget for the model and mode\n * → what the record stored → the seat default. The precedence itself lives in\n * verse-model's `sessionContextBudget` (the context meter reads it too); this\n * only adds whether the model offers a second mode, for the panels here.\n */\nexport function sessionContext(session: VerseSession, seats: readonly VerseSeat[]): SessionContextView {\n const budget = sessionContextBudget(seats, session);\n return {\n tokens: Math.floor(budget.contextTokens),\n exact: budget.exact,\n window: budget.contextWindow,\n autoCompactAt: budget.autoCompactAt,\n source: budget.source,\n mode: budget.mode,\n hasModes: hasExpansiveMode(budget.option),\n };\n}\n\n/**\n * Cache-hit ratio, except when the provider reported no cache activity at all\n * — then null (\"none reported\"), because 0% would claim caching failed when\n * the truth is that nothing was said about it.\n */\nexport function reportedCacheHitRatio(usage: Pick<VerseUsage, 'inputTokens' | 'cacheReadTokens' | 'cacheCreationTokens'>): number | null {\n if (usage.cacheReadTokens <= 0 && usage.cacheCreationTokens <= 0) return null;\n return cacheHitRatio(usage);\n}\n\n/** A 0–1 ratio as a whole percent; a real sliver reads \"<1%\", never \"0%\". */\nexport function formatRatio(ratio: number | null): string {\n if (ratio === null || !Number.isFinite(ratio)) return '—';\n return formatWholePercent(ratio);\n}\n\nexport interface TurnContextStats {\n /** Turns with at least one context reading. */\n turns: number;\n /** Mean of each turn's context (its largest trustworthy reading); null with no turns. */\n average: number | null;\n /** Largest per-turn context seen. */\n peak: number | null;\n /** False when any counted turn had only an upper-bound reading. */\n exact: boolean;\n /** Compactions recorded in the log. */\n compactions: number;\n /** Cache-hit ratio of the most recent turn (see `reportedCacheHitRatio`). */\n lastTurnCacheHit: number | null;\n}\n\n/**\n * Context per turn from the open chat's event log.\n *\n * A turn's context is its LARGEST trustworthy reading: within one turn the\n * prompt only grows, except across a compaction, and the peak is what the\n * turn actually paid for. Exact readings (`context` events, usage frames not\n * flagged as upper bounds) win; a turn whose only reading is codex's turn\n * total contributes that total and flags the statistic as an upper bound —\n * that figure can be many times the real prompt, so it is never mixed in\n * where an exact one exists.\n */\nexport function turnContextStats(events: readonly VerseEvent[]): TurnContextStats {\n const perTurn = new Map<string, { exact: number | null; bound: number | null }>();\n let compactions = 0;\n let lastUsage: VerseUsage | null = null;\n const note = (turnId: string, tokens: number, exact: boolean): void => {\n if (!Number.isFinite(tokens) || tokens < 0) return;\n const entry = perTurn.get(turnId) ?? { exact: null, bound: null };\n if (exact) entry.exact = Math.max(entry.exact ?? 0, tokens);\n else entry.bound = Math.max(entry.bound ?? 0, tokens);\n perTurn.set(turnId, entry);\n };\n for (const event of events) {\n switch (event.type) {\n case 'usage':\n lastUsage = event.usage;\n note(event.turnId, event.usage.contextTokens, event.usage.contextTokensExact !== false);\n break;\n case 'context':\n if (event.turnId !== null) note(event.turnId, event.contextTokens, event.exact);\n break;\n case 'compaction':\n compactions += 1;\n break;\n default:\n break;\n }\n }\n let sum = 0;\n let peak: number | null = null;\n let exact = true;\n let turns = 0;\n for (const entry of perTurn.values()) {\n const value = entry.exact ?? entry.bound;\n if (value === null) continue;\n if (entry.exact === null) exact = false;\n turns += 1;\n sum += value;\n peak = peak === null ? value : Math.max(peak, value);\n }\n return {\n turns,\n average: turns === 0 ? null : Math.round(sum / turns),\n peak,\n exact,\n compactions,\n lastTurnCacheHit: lastUsage === null ? null : reportedCacheHitRatio(lastUsage),\n };\n}\n\nexport interface IdleCacheWarning {\n idleMs: number;\n tokens: number;\n /** A local model: the cost is time, not spend. */\n local: boolean;\n}\n\n/**\n * The next turn of an idle chat re-reads its whole context uncached once the\n * provider's prompt cache has expired (CACHE_IDLE_TTL_MS). Below the fixed\n * prompt every new chat on this engine pays anyway (its estimate) there is\n * nothing worth warning about.\n */\nexport function idleCacheWarning(\n session: Pick<VerseSession, 'status' | 'engine'>,\n tokens: number,\n now: number,\n /**\n * When the chat last TALKED to its provider (`lastTurnActivityAt(events)`),\n * never `session.updatedAt`: a rename or mode switch moves that without\n * warming the provider's cache. Null (no turn yet / log not loaded) → no warning.\n */\n lastActivityAt: string | null,\n): IdleCacheWarning | null {\n if (session.status === 'running' || lastActivityAt === null) return null;\n const last = Date.parse(lastActivityAt);\n if (!Number.isFinite(last)) return null;\n const idleMs = now - last;\n if (idleMs < CACHE_IDLE_TTL_MS || tokens < sessionOverheadTokens(session.engine)) return null;\n return { idleMs, tokens, local: session.engine === 'local' };\n}\n\n// ---------------------------------------------------------------------------\n// Per-seat efficiency across sessions (Usage section)\n// ---------------------------------------------------------------------------\n\nexport interface SeatEfficiencyRow {\n seatId: string;\n label: string;\n engine: VerseEngine;\n /** The seat is no longer in the roster (its sessions remain on disk). */\n retired: boolean;\n sessions: number;\n turns: number;\n /** input + cache read + cache write: every prompt token the seat was sent. */\n promptTokens: number;\n outputTokens: number;\n /** Null when the provider reported no cache activity on any of these sessions. */\n cacheHitRatio: number | null;\n compactions: number;\n expansiveSessions: number;\n /** The fullest context among these sessions right now, as a share of its window. */\n fullest: { sessionId: string; title: string; tokens: number; window: number | null; exact: boolean } | null;\n}\n\n/**\n * \"Fullest\" is a share of the window when both windows are known; a context\n * with a known window outranks one without (its fullness is a fact, the other\n * a size); two unknown windows compare by size.\n */\nfunction fuller(a: NonNullable<SeatEfficiencyRow['fullest']>, b: NonNullable<SeatEfficiencyRow['fullest']>): boolean {\n if (a.window !== null && b.window !== null) return a.tokens / a.window > b.tokens / b.window;\n if (a.window !== null) return true;\n if (b.window !== null) return false;\n return a.tokens > b.tokens;\n}\n\n/**\n * Aggregate every session by seat. Ratios are computed from SUMS, never as a\n * mean of per-session ratios — a two-turn chat must not weigh as much as a\n * two-hundred-turn one. Seats with no sessions are omitted rather than shown\n * as zeros. Sorted by prompt tokens, largest first: that is where efficiency\n * matters most.\n */\nexport function seatEfficiency(sessions: readonly VerseSession[], seats: readonly VerseSeat[]): SeatEfficiencyRow[] {\n const bySeat = new Map<string, VerseSession[]>();\n for (const s of sessions) {\n const list = bySeat.get(s.seatId) ?? [];\n list.push(s);\n bySeat.set(s.seatId, list);\n }\n const rows: SeatEfficiencyRow[] = [];\n for (const [seatId, list] of bySeat) {\n const seat = seats.find((s) => s.id === seatId) ?? null;\n let input = 0;\n let read = 0;\n let write = 0;\n let output = 0;\n let turns = 0;\n let compactions = 0;\n let expansiveSessions = 0;\n let fullest: SeatEfficiencyRow['fullest'] = null;\n for (const s of list) {\n input += s.usage.inputTokens;\n read += s.usage.cacheReadTokens;\n write += s.usage.cacheCreationTokens;\n output += s.usage.outputTokens;\n turns += s.turnCount;\n compactions += s.compactionCount ?? 0;\n // The mode as the meter resolves it (verse-model sessionContextBudget),\n // counted before the empty-reading skip: the mode is a fact about the\n // chat, not about its reading.\n const view = sessionContext(s, seats);\n if (view.mode === 'expansive') expansiveSessions += 1;\n if (view.tokens <= 0) continue;\n const candidate = { sessionId: s.id, title: s.title, tokens: view.tokens, window: view.window, exact: view.exact };\n if (fullest === null || fuller(candidate, fullest)) fullest = candidate;\n }\n rows.push({\n seatId,\n label: seat?.label ?? seatId,\n engine: seat?.engine ?? list[0]!.engine,\n retired: seat === null,\n sessions: list.length,\n turns,\n promptTokens: input + read + write,\n outputTokens: output,\n cacheHitRatio: reportedCacheHitRatio({ inputTokens: input, cacheReadTokens: read, cacheCreationTokens: write }),\n compactions,\n expansiveSessions,\n fullest,\n });\n }\n return rows.sort((a, b) => b.promptTokens - a.promptTokens || a.label.localeCompare(b.label));\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{$ as e,K as t,W as n,Y as r,Z as i,nt as a}from"./global-DG1fmn4y.js";import{C as o,S as s,r as c}from"./verse-store-DHTuZpYa.js";import{g as l,t as u,v as d}from"./format-lW58rL2y.js";import{c as f}from"./verse-model-yHPwjf-7.js";var p=/\s·\spart\s(\d+)$/;function m(e){let t=e.replace(/\s+/g,` `).trim(),n=p.exec(t),r=(n?t.slice(0,n.index):t)||`Untitled chat`,i=` · part ${n?Number.parseInt(n[1],10)+1:2}`,a=120-i.length;return`${r.length>a?`${r.slice(0,a-1).trimEnd()}…`:r}${i}`}function h(e,n){if(!e)return null;let r=e.models.find(e=>e.id===n);if(r)return r;let i=t(n);return e.models.find(e=>t(e.id)===i)??null}function g(e,t,n=Date.now()){return e?e.health.state===`unavailable`?`${e.label} is unavailable: ${u(d(e.health.summary??`no reason given`,n))}.`:t?t.unavailableReason?`${t.label} cannot run on ${e.label}: ${u(d(t.unavailableReason,n))}.`:null:`Pick a model on this seat.`:`Pick a seat to continue on.`}function _(e,t){let n=e.find(e=>e.id===t.seatId);if(n&&n.health.state!==`unavailable`){let e=h(n,t.model);if(e&&!e.unavailableReason)return{seatId:n.id,model:e.id};let r=n.models.find(e=>!e.unavailableReason);if(r)return{seatId:n.id,model:r.id}}for(let t of e){if(t.health.state===`unavailable`)continue;let e=t.models.find(e=>!e.unavailableReason);if(e)return{seatId:t.id,model:e.id}}return null}function v(e){let{option:r,target:i,source:a,preferences:o}=e,s=e=>e!==void 0&&n(r,e)!==null;if(i!==null&&i.seatId===a.seatId&&t(i.model)===t(a.model)&&s(a.contextMode??`standard`))return a.contextMode??`standard`;let c=i?o?.seats[i.seatId]?.contextMode:void 0;return s(c)?c:`standard`}function y(e,t){return n(e,t)===null?void 0:t}function b(e,t){let r=n(e,t);if(!r)return null;let i=`${c(r.contextWindow)} window`;return r.autoCompactAt===null?i:`${i} · compacts ≈${c(r.autoCompactAt)}`}function x(e,t,o,s){let u=r(e),d=a(s),f=u+d,p=i(u,t,d),m=`~${c(u)} tokens of handoff + an estimated ~${c(d)} of fixed prompt`,h=n(t,`standard`),g=h?h.autoCompactAt??h.contextWindow:null,_={verdict:p,handoffTokens:u,overheadTokens:d,needTokens:f};switch(p){case`fits`:return{..._,tone:`ok`,text:`Fits: ${m} leaves most of the budget before the first compaction.`};case`tight`:return{..._,tone:`warn`,text:`Tight: ${m} is ${l(f/(g??f)*100)} of where this model compacts — the new chat will compact early.`};case`expansive`:return o===`expansive`?{..._,tone:`warn`,text:`Fits only the expansive budget: ${m}.`}:{..._,tone:`danger`,text:`Too big for the standard budget (${m}). Switch to Expansive, or trim the handoff.`};case`split`:return{..._,tone:`danger`,text:`Too big for one context on this model (${m}). Trim the handoff or pick a seat with a larger window.`};default:return{..._,verdict:null,tone:`unknown`,text:`This model’s window is not known, so there is no fit estimate.`}}}function S(t){return e(t)}function C(e,t){return e===`local`?`Runs on this machine with ${t}; it spends no subscription usage.`:`Sends one turn to ${t} and spends from its usage. The automatic preview costs nothing.`}var w=typeof TextEncoder==`function`?new TextEncoder:null;function T(e){if(w)return w.encode(e).length;let t=0;for(let n of e){let e=n.codePointAt(0)??0;t+=e<128?1:e<2048?2:e<65536?3:4}return t}var E=65536,D=o;function O(e){return T(JSON.stringify(e)??``)}function k(e){if(!Number.isFinite(e)||e<0)return`—`;if(e<1024)return`${e} B`;let t=e/1024;return`${t<10?t.toFixed(1).replace(/\.0$/,``):Math.round(t)} KB`}function A(e,t=Date.now()){if(!e)return null;let n=f(e,t);return n?n===`now`?`just now`:/^\d+[mhd]$/.test(n)?`${n} ago`:`on ${n}`:null}var j=[{label:`a private key`,re:/-----BEGIN [A-Z ]*PRIVATE KEY-----/},{label:`an Anthropic API key`,re:/\bsk-ant-[A-Za-z0-9_-]{20,}/},{label:`an OpenAI-style API key`,re:/\bsk-(?:proj-)?[A-Za-z0-9_-]{32,}/},{label:`an xAI API key`,re:/\bxai-[A-Za-z0-9]{32,}/},{label:`a GitHub token`,re:/\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{30,}|\bgithub_pat_[A-Za-z0-9_]{40,}/},{label:`an AWS access key`,re:/\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/},{label:`a Slack token`,re:/\bxox[abprs]-[A-Za-z0-9-]{10,}/},{label:`a Google API key`,re:/\bAIza[0-9A-Za-z_-]{35}\b/},{label:`a Stripe secret key`,re:/\b(?:sk|rk)_live_[0-9A-Za-z]{20,}/}],M=s,N=`Reading and editing memory here spends nothing. On a paid seat it is not free to use: each new chat’s system prompt carries a memory block of up to ${M/1024} KB, re-sent every turn (cached after the first), and the agent reads and updates MEMORY.md as it works. Local seats spend nothing.`;function P(e){for(let{label:t,re:n}of j)if(n.test(e))return t;return null}var F=`[REDACTED]`;function I(e){return e.split(F).length-1}function L(e){let t=[];for(let n of e.toLowerCase().split(/\s+/))n&&!t.includes(n)&&t.push(n);return t}function R(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}function z(e,t){let n=[...t].filter(e=>e.length>0).sort((e,t)=>t.length-e.length);if(n.length===0||e.length===0)return e?[{text:e,match:!1}]:[];let r=new RegExp(n.map(R).join(`|`),`gi`),i=[],a=0;for(let t of e.matchAll(r)){let n=t.index??0;t[0].length!==0&&(n>a&&i.push({text:e.slice(a,n),match:!1}),i.push({text:t[0],match:!0}),a=n+t[0].length)}return a<e.length&&i.push({text:e.slice(a),match:!1}),i}function B(e){let t=new Map;for(let n of e){let e=t.get(n.sessionId);if(!e){t.set(n.sessionId,{sessionId:n.sessionId,title:n.title,projectPath:n.projectPath,engine:n.engine,top:n,count:1,latestAt:n.at});continue}e.count+=1,n.at>e.latestAt&&(e.latestAt=n.at)}return[...t.values()]}export{C,g as S,I as _,F as a,L as b,_ as c,x as d,m as f,S as g,h,N as i,k as l,O as m,M as n,b as o,z as p,D as r,v as s,E as t,B as u,A as v,T as w,P as x,y};
|
|
2
|
+
//# sourceMappingURL=context-model-DeJ28uSZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-model-DeJ28uSZ.js","names":[],"sources":["../../../../../../src/web-ui/routes/verse/context/context-model.ts"],"sourcesContent":["/**\n * routes/verse/context/context-model.ts — the pure half of the context\n * components (handoff dialog, memory panel, session search). No React, no\n * I/O, so every rule the components render is pinned by a plain unit test.\n *\n * Every number shown here comes from context-math.ts (the one module the\n * server and the browser share), so \"compacts ≈367k\" in the handoff dialog is\n * the same figure the meter and the seat picker show.\n */\nimport type { VerseEngine, VerseModelOption, VerseSeat, VerseSession } from '../../../data/api-types.js';\nimport type {\n VerseContextMode,\n VerseFitVerdict,\n VersePreferences,\n VerseSearchHit,\n} from '../../../../core/verse/types.js';\nimport { VERSE_MEMORY_BLOCK_MAX_BYTES, VERSE_MEMORY_BODY_MAX_BYTES } from '../../../../core/verse/types.js';\nimport {\n budgetFor,\n canonicalModelId,\n estimateTokensFromChars,\n fitVerdict,\n hasExpansiveMode,\n sessionOverheadTokens,\n} from '../../../../core/verse/context-math.js';\nimport { formatRelative } from '../verse-model.js';\nimport { asClause, percentText, tidyProse } from '../autonomy/format.js';\nimport { formatTokens } from '../verse-store.js';\n\n// ---------------------------------------------------------------------------\n// Handoff\n// ---------------------------------------------------------------------------\n\n// The canned \"Ask this seat to summarize first\" turn is\n// VERSE_HANDOFF_SUMMARY_REQUEST in core/verse/types.ts, not a copy here: the\n// handoff builder on the server matches that exact text to keep it OUT of the\n// new session's \"latest requests\" (otherwise the new chat is told to write\n// another handoff note). A local copy that drifted by one character would\n// silently defeat that filter.\n\n/** Mirrors session-engine's TITLE_MAX so a title we send is never cut by the server instead. */\nexport const TITLE_MAX_CHARS = 120;\n\nconst PART_SUFFIX = /\\s·\\spart\\s(\\d+)$/;\n\n/**\n * Title for the session a handoff continues in: \"Fix the login bug · part 2\".\n * A continuation of a continuation counts up rather than stacking suffixes, so\n * the sidebar reads \"part 3\", never \"part 2 · part 2\".\n */\nexport function handoffTitle(sourceTitle: string): string {\n const collapsed = sourceTitle.replace(/\\s+/g, ' ').trim();\n const match = PART_SUFFIX.exec(collapsed);\n const base = (match ? collapsed.slice(0, match.index) : collapsed) || 'Untitled chat';\n const next = match ? Number.parseInt(match[1]!, 10) + 1 : 2;\n const suffix = ` · part ${next}`;\n const room = TITLE_MAX_CHARS - suffix.length;\n const clipped = base.length > room ? `${base.slice(0, room - 1).trimEnd()}…` : base;\n return `${clipped}${suffix}`;\n}\n\n/**\n * A seat's catalog entry for a model id. Exact id first, then canonical — a\n * session created before the alias fix stores `claude-opus-5.5` while the\n * catalog now lists `claude-opus-5-5` (context-math VERSE_MODEL_ID_ALIASES).\n */\nexport function modelOption(seat: VerseSeat | null | undefined, modelId: string): VerseModelOption | null {\n if (!seat) return null;\n const exact = seat.models.find((m) => m.id === modelId);\n if (exact) return exact;\n const canonical = canonicalModelId(modelId);\n return seat.models.find((m) => canonicalModelId(m.id) === canonical) ?? null;\n}\n\n/**\n * Why a seat+model cannot take a new session right now; null when it can.\n *\n * The seat's own reason is embedded as a clause: its closing period is\n * dropped (no \"exhausted..\") and any ISO instant in it — a health summary\n * saying when usage resets — is read as the viewer's local time.\n */\nexport function targetUnavailableReason(\n seat: VerseSeat | null | undefined,\n option: VerseModelOption | null,\n now: number = Date.now(),\n): string | null {\n if (!seat) return 'Pick a seat to continue on.';\n if (seat.health.state === 'unavailable') {\n return `${seat.label} is unavailable: ${asClause(tidyProse(seat.health.summary ?? 'no reason given', now))}.`;\n }\n if (!option) return 'Pick a model on this seat.';\n if (option.unavailableReason) {\n return `${option.label} cannot run on ${seat.label}: ${asClause(tidyProse(option.unavailableReason, now))}.`;\n }\n return null;\n}\n\nexport interface HandoffTarget {\n seatId: string;\n model: string;\n}\n\n/**\n * Where a handoff lands by default: the SAME seat and model (the common case\n * is \"same agent, fresh context\"). When that model can no longer run there,\n * the seat's first runnable model; when the seat itself is gone or\n * unavailable, the first runnable model on any available seat, in the seat\n * list's own order.\n */\nexport function defaultHandoffTarget(seats: readonly VerseSeat[], source: Pick<VerseSession, 'seatId' | 'model'>): HandoffTarget | null {\n const own = seats.find((s) => s.id === source.seatId);\n if (own && own.health.state !== 'unavailable') {\n const same = modelOption(own, source.model);\n if (same && !same.unavailableReason) return { seatId: own.id, model: same.id };\n const runnable = own.models.find((m) => !m.unavailableReason);\n if (runnable) return { seatId: own.id, model: runnable.id };\n }\n for (const seat of seats) {\n if (seat.health.state === 'unavailable') continue;\n const runnable = seat.models.find((m) => !m.unavailableReason);\n if (runnable) return { seatId: seat.id, model: runnable.id };\n }\n return null;\n}\n\n/**\n * The mode a handoff starts in. Only a mode that EXISTS for the target model\n * is ever returned (a mode with no budget would be refused by the server).\n *\n * 1. Same seat and model as the source → keep the source's mode. \"Continue in\n * a fresh chat\" means the same settings with a clean context; an operator\n * who chose expansive for this work chose it for the work.\n * 2. Otherwise the target seat's standing preference (preferences.json).\n * 3. Otherwise standard.\n */\nexport function defaultHandoffMode(input: {\n option: VerseModelOption | null;\n target: HandoffTarget | null;\n source: Pick<VerseSession, 'seatId' | 'model' | 'contextMode'>;\n preferences: VersePreferences | null | undefined;\n}): VerseContextMode {\n const { option, target, source, preferences } = input;\n const valid = (mode: VerseContextMode | undefined): mode is VerseContextMode =>\n mode !== undefined && budgetFor(option, mode) !== null;\n const sameModel = target !== null && target.seatId === source.seatId\n && canonicalModelId(target.model) === canonicalModelId(source.model);\n if (sameModel && valid(source.contextMode ?? 'standard')) return source.contextMode ?? 'standard';\n const preferred = target ? preferences?.seats[target.seatId]?.contextMode : undefined;\n if (valid(preferred)) return preferred;\n return 'standard';\n}\n\n/**\n * The mode to put on the create request, or undefined to leave it to the\n * server. A mode is sent only when the model has a budget for it — which is\n * exactly the server's acceptance rule — so what the dialog shows is what is\n * created, and an unknown-window model is never refused over a mode it could\n * not have.\n */\nexport function requestMode(option: VerseModelOption | null, mode: VerseContextMode): VerseContextMode | undefined {\n return budgetFor(option, mode) !== null ? mode : undefined;\n}\n\n/** \"1M window · compacts ≈367k\" for a model in a mode; null when the budget is unknown. */\nexport function budgetLine(option: VerseModelOption | null, mode: VerseContextMode): string | null {\n const budget = budgetFor(option, mode);\n if (!budget) return null;\n const window = `${formatTokens(budget.contextWindow)} window`;\n return budget.autoCompactAt === null ? window : `${window} · compacts ≈${formatTokens(budget.autoCompactAt)}`;\n}\n\nexport interface HandoffFit {\n verdict: VerseFitVerdict | null;\n /** The handoff text's own estimate (chars / 4). */\n handoffTokens: number;\n /** The target engine's estimated fixed prompt (context-math `sessionOverheadTokens`). */\n overheadTokens: number;\n /** Handoff + that fixed prompt: what the new session holds before its first reply. */\n needTokens: number;\n /** One sentence for the dialog. */\n text: string;\n tone: 'ok' | 'warn' | 'danger' | 'unknown';\n}\n\n/**\n * Will the new session START comfortably? The handoff is small by design\n * (≤ 12k chars ≈ 3k tokens), but every fresh session also carries the CLI's\n * fixed prompt — a large share of a 64k local slot — so this is a real\n * question on small windows, and the sentence shows both halves of the sum\n * rather than a bare verdict.\n *\n * The fixed prompt is the TARGET seat's engine estimate (local ≈15k measured,\n * claude ≈25k, …), not one flat figure: a flat 30k called every handoff to a\n * 64k local seat \"too big for one context\" when the real sum was ~20k. It is\n * still an estimate — no CLI reports its base prompt before the first turn —\n * so the sentence says \"estimated\" rather than presenting it as a reading.\n * `engine` null (target seat not known yet) falls back to the largest estimate.\n */\nexport function handoffFit(\n textChars: number,\n option: VerseModelOption | null,\n mode: VerseContextMode,\n engine: VerseEngine | null,\n): HandoffFit {\n const handoffTokens = estimateTokensFromChars(textChars);\n const overheadTokens = sessionOverheadTokens(engine);\n const needTokens = handoffTokens + overheadTokens;\n const verdict = fitVerdict(handoffTokens, option, overheadTokens);\n const sum = `~${formatTokens(handoffTokens)} tokens of handoff + an estimated ~${formatTokens(overheadTokens)} of fixed prompt`;\n const standard = budgetFor(option, 'standard');\n const standardCap = standard ? standard.autoCompactAt ?? standard.contextWindow : null;\n const base = { verdict, handoffTokens, overheadTokens, needTokens };\n switch (verdict) {\n case 'fits':\n return { ...base, tone: 'ok', text: `Fits: ${sum} leaves most of the budget before the first compaction.` };\n case 'tight':\n return {\n ...base,\n tone: 'warn',\n text: `Tight: ${sum} is ${percentText((needTokens / (standardCap ?? needTokens)) * 100)} of where this model compacts — the new chat will compact early.`,\n };\n case 'expansive':\n return mode === 'expansive'\n ? { ...base, tone: 'warn', text: `Fits only the expansive budget: ${sum}.` }\n : { ...base, tone: 'danger', text: `Too big for the standard budget (${sum}). Switch to Expansive, or trim the handoff.` };\n case 'split':\n return { ...base, tone: 'danger', text: `Too big for one context on this model (${sum}). Trim the handoff or pick a seat with a larger window.` };\n default:\n return { ...base, verdict: null, tone: 'unknown', text: 'This model’s window is not known, so there is no fit estimate.' };\n }\n}\n\n/** True when the target model has a real expansive budget (context-math, re-exported for the dialog). */\nexport function offersExpansive(option: VerseModelOption | null): boolean {\n return hasExpansiveMode(option);\n}\n\n/** A local turn runs on this machine; every other engine spends subscription usage. */\nexport function turnCostSentence(engine: VerseEngine, seatLabel: string): string {\n return engine === 'local'\n ? `Runs on this machine with ${seatLabel}; it spends no subscription usage.`\n : `Sends one turn to ${seatLabel} and spends from its usage. The automatic preview costs nothing.`;\n}\n\n// ---------------------------------------------------------------------------\n// Bytes\n// ---------------------------------------------------------------------------\n\nconst encoder = typeof TextEncoder === 'function' ? new TextEncoder() : null;\n\n/** UTF-8 byte length — the unit the server's caps (64 KB memory content, 64 KB turn) are written in. */\nexport function utf8Bytes(text: string): number {\n if (encoder) return encoder.encode(text).length;\n // Fallback for an environment with no TextEncoder: count code points by width.\n let bytes = 0;\n for (const ch of text) {\n const cp = ch.codePointAt(0) ?? 0;\n bytes += cp < 0x80 ? 1 : cp < 0x800 ? 2 : cp < 0x10000 ? 3 : 4;\n }\n return bytes;\n}\n\n/**\n * The API's default request-body cap: `readBody` in src/core/web/api.ts, which\n * every /api/verse POST uses unless its route passes a larger one (only\n * POST /memory does — see MEMORY_BODY_MAX_BYTES). A write travels as JSON, so what must fit is\n * the ENCODED body — every `\\n` and `\"` escaped to two bytes — not the text\n * alone; the editors measure it so a refusal happens on screen instead of as\n * \"request body too large\".\n */\nexport const API_BODY_MAX_BYTES = 65_536;\n\n/**\n * POST /api/verse/memory's own body cap — mirrors `VERSE_MEMORY_BODY_MAX_BYTES`\n * in src/core/verse/verse-api.ts (a server module the browser cannot import):\n * room for 64 KiB of content in which EVERY byte escapes to two, plus the\n * project path and the JSON around it. The server still holds the CONTENT to\n * VERSE_MEMORY_MAX_BYTES after parsing. Guarding the memory editor with the\n * 64 KiB default instead blocked saves the server accepts — a 60–64 KB\n * MEMORY.md with ordinary line breaks could not be edited from the panel,\n * which is the exact case the server's larger cap exists for.\n */\nexport const MEMORY_BODY_MAX_BYTES = VERSE_MEMORY_BODY_MAX_BYTES;\n\n/** UTF-8 size of `body` exactly as the client will send it. */\nexport function jsonBodyBytes(body: unknown): number {\n return utf8Bytes(JSON.stringify(body) ?? '');\n}\n\nexport function formatBytes(bytes: number): string {\n if (!Number.isFinite(bytes) || bytes < 0) return '—';\n if (bytes < 1024) return `${bytes} B`;\n const kb = bytes / 1024;\n return `${kb < 10 ? kb.toFixed(1).replace(/\\.0$/, '') : Math.round(kb)} KB`;\n}\n\n/**\n * A relative time that reads as a phrase: \"just now\", \"5m ago\", \"on Sep 19\".\n * `formatRelative` (verse-model) is sized for a 5ch sidebar column and reads\n * as a bare token; prose needs the preposition.\n */\nexport function relativePhrase(iso: string | null | undefined, now: number = Date.now()): string | null {\n if (!iso) return null;\n const short = formatRelative(iso, now);\n if (!short) return null;\n if (short === 'now') return 'just now';\n // Only the relative tokens (\"5m\", \"3h\", \"2d\") take \"ago\". Anything else is a\n // DATE, whatever the locale prints: \"Sep 1\" (en-US) but \"1 Sept\" (en-GB)\n // and \"1. Sept.\" (de) start with a digit — testing for a leading digit\n // printed \"Last completed 1 Sept ago.\" outside the US.\n return /^\\d+[mhd]$/.test(short) ? `${short} ago` : `on ${short}`;\n}\n\n// ---------------------------------------------------------------------------\n// Memory\n// ---------------------------------------------------------------------------\n\n/**\n * Shapes that are almost certainly credentials. MEMORY.md is sent to every\n * seat's model on every turn, so a pasted key would travel to every provider\n * — the panel warns BEFORE the save rather than trusting the server's scrub\n * to catch it on the way out. Conservative on purpose: a false alarm on prose\n * would teach the operator to ignore the warning.\n */\nconst SECRET_PATTERNS: ReadonlyArray<{ label: string; re: RegExp }> = [\n { label: 'a private key', re: /-----BEGIN [A-Z ]*PRIVATE KEY-----/ },\n { label: 'an Anthropic API key', re: /\\bsk-ant-[A-Za-z0-9_-]{20,}/ },\n { label: 'an OpenAI-style API key', re: /\\bsk-(?:proj-)?[A-Za-z0-9_-]{32,}/ },\n { label: 'an xAI API key', re: /\\bxai-[A-Za-z0-9]{32,}/ },\n { label: 'a GitHub token', re: /\\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{30,}|\\bgithub_pat_[A-Za-z0-9_]{40,}/ },\n { label: 'an AWS access key', re: /\\b(?:AKIA|ASIA)[0-9A-Z]{16}\\b/ },\n { label: 'a Slack token', re: /\\bxox[abprs]-[A-Za-z0-9-]{10,}/ },\n { label: 'a Google API key', re: /\\bAIza[0-9A-Za-z_-]{35}\\b/ },\n { label: 'a Stripe secret key', re: /\\b(?:sk|rk)_live_[0-9A-Za-z]{20,}/ },\n];\n\n/**\n * Mirrors `VERSE_MEMORY_BLOCK_MAX_BYTES` in src/core/verse/project-memory.ts\n * (a node module the browser cannot import): the most memory text a chat's\n * instructions carry, however large MEMORY.md itself is.\n */\nexport const MEMORY_BLOCK_MAX_BYTES = VERSE_MEMORY_BLOCK_MAX_BYTES;\n\n/**\n * What memory costs, in the one wording the memory panel and the resources\n * panel share. Memory is ON by default and snapshotted into every new chat's\n * system prompt (claude `--append-system-prompt`, codex `developer_instructions`,\n * grok `--rules`), which re-rides every turn, and it asks writable seats to\n * read and update MEMORY.md — tool calls and output. On a paid seat that is\n * real (if small, mostly cached) usage, so \"nothing here spends\" would be\n * false for exactly the operator deciding whether to leave it on near a limit.\n * Reading and editing the file from the panel is still free.\n */\nexport const MEMORY_SPEND_NOTE =\n `Reading and editing memory here spends nothing. On a paid seat it is not free to use: each new chat’s system prompt carries a memory block of up to ${MEMORY_BLOCK_MAX_BYTES / 1024} KB, re-sent every turn (cached after the first), and the agent reads and updates MEMORY.md as it works. Local seats spend nothing.`;\n\n/** The first credential shape found, as a phrase (\"a GitHub token\"), or null. */\nexport function secretLike(text: string): string | null {\n for (const { label, re } of SECRET_PATTERNS) if (re.test(text)) return label;\n return null;\n}\n\n/**\n * The placeholder the server's scrubber (src/core/util/scrub.ts) writes over\n * secret-shaped text in every rule. In a memory view flagged\n * `contentSanitized`, each one stands for a REAL value still in MEMORY.md.\n */\nexport const REDACTION_MARKER = '[REDACTED]';\n\n/** How many redaction placeholders a text carries. */\nexport function redactionMarkers(text: string): number {\n return text.split(REDACTION_MARKER).length - 1;\n}\n\n// ---------------------------------------------------------------------------\n// Search\n// ---------------------------------------------------------------------------\n\n/** Below this many characters a keyword search over every message is noise, not recall. */\nexport const SEARCH_MIN_CHARS = 2;\n/** Keystrokes inside this window collapse into one request. */\nexport const SEARCH_DEBOUNCE_MS = 250;\n/** Hits asked for per search — enough to fill the sidebar twice over. */\nexport const SEARCH_LIMIT = 30;\n\n/** Whitespace-separated terms, lowercased and de-duplicated (the server ANDs them). */\nexport function searchTerms(query: string): string[] {\n const out: string[] = [];\n for (const raw of query.toLowerCase().split(/\\s+/)) {\n if (raw && !out.includes(raw)) out.push(raw);\n }\n return out;\n}\n\nexport interface HighlightSegment {\n text: string;\n match: boolean;\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Split `text` into plain and matched runs for every term, case-insensitive.\n * Longest terms first so \"migrate\" wins over \"migrat\" at the same position.\n */\nexport function highlightSegments(text: string, terms: readonly string[]): HighlightSegment[] {\n const usable = [...terms].filter((t) => t.length > 0).sort((a, b) => b.length - a.length);\n if (usable.length === 0 || text.length === 0) return text ? [{ text, match: false }] : [];\n const re = new RegExp(usable.map(escapeRegExp).join('|'), 'gi');\n const out: HighlightSegment[] = [];\n let last = 0;\n for (const m of text.matchAll(re)) {\n const start = m.index ?? 0;\n if (m[0].length === 0) continue;\n if (start > last) out.push({ text: text.slice(last, start), match: false });\n out.push({ text: m[0], match: true });\n last = start + m[0].length;\n }\n if (last < text.length) out.push({ text: text.slice(last), match: false });\n return out;\n}\n\nexport interface SessionSearchGroup {\n sessionId: string;\n title: string;\n projectPath: string;\n engine: VerseEngine;\n /** The best-scoring hit in this session (the server returns hits best-first). */\n top: VerseSearchHit;\n /** Every hit this session contributed to the response. */\n count: number;\n /** Newest hit time, for the row's relative time. */\n latestAt: string;\n}\n\n/**\n * One row per session. The server ranks MESSAGES; the sidebar lists CHATS,\n * and five rows for one chat would push every other match off screen. Order\n * is the order each session first appears, i.e. by its best hit.\n */\nexport function groupSearchHits(hits: readonly VerseSearchHit[]): SessionSearchGroup[] {\n const groups = new Map<string, SessionSearchGroup>();\n for (const hit of hits) {\n const existing = groups.get(hit.sessionId);\n if (!existing) {\n groups.set(hit.sessionId, {\n sessionId: hit.sessionId,\n title: hit.title,\n projectPath: hit.projectPath,\n engine: hit.engine,\n top: hit,\n count: 1,\n latestAt: hit.at,\n });\n continue;\n }\n existing.count += 1;\n if (hit.at > existing.latestAt) existing.latestAt = hit.at;\n }\n return [...groups.values()];\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{D as e,M as t,O as n,P as r,at as i,ot as a,s as o,u as s}from"./global-DG1fmn4y.js";import{C as c,d as l,n as u,r as d}from"./verse-queries-2PxoBISm.js";import{f}from"./verse-store-DHTuZpYa.js";import{t as p}from"./format-lW58rL2y.js";var m=`verse-preferences`,h=`verse-memory:`;async function g(e){try{let t=await e.json();if(typeof t.error==`string`&&t.error)return t.error;if(typeof t.note==`string`&&t.note)return t.note}catch{}return null}async function _(e,r){let i=await fetch(e,{method:`GET`,credentials:`same-origin`,headers:{"x-ashlr-read-client":n()},signal:r});if(i.status===401)throw t(),new o(`Read session expired.`,401,e);if(!i.ok){let t=await g(i);throw new o(`GET ${e} failed (HTTP ${i.status})${t?`: ${p(t)}`:``}.`,i.status,e,t)}return await i.json()}async function v(t,n){let i=e();if(!i)throw new d;let a=await s(t,n,i);return r(),a}function y(e,t){let n=t.toString();return n?`${e}?${n}`:e}async function b(e,t){let n={mode:t},r=await v(c(e,`/context-mode`),n);return f(e,r),i(u),r}async function x(e,t={}){let n={};t.includeLastAssistant===!0&&(n.includeLastAssistant=!0);let r=typeof t.focus==`string`?t.focus.trim().slice(0,500):``;return r&&(n.focus=r),v(c(e,`/handoff-preview`),n)}async function S(e){let{source:t}=e,n={projectPath:t.projectPath,seatId:e.seatId,handoffFromSessionId:t.id},r=(t.extraRoots??[]).filter(e=>typeof e==`string`&&e.length>0&&e!==t.projectPath);r.length>0&&(n.extraRoots=r),e.model&&(n.model=e.model),e.contextMode&&(n.contextMode=e.contextMode);let i=e.title?.trim();i&&(n.title=i);let a=await v(`/api/verse/sessions`,n);return l(),a}function C(e){return _(`/api/verse/preferences`,e)}var w={key:m,fetch:e=>C(e)};async function T(e){let t=await v(`/api/verse/preferences`,e);return i(m),a(h),t}function E(e){let t=new URLSearchParams,n=e.workspaceId?.trim()??``,r=e.projectPath?.trim()??``,i=(e.extraRoots??[]).map(e=>e.trim()).filter(e=>e.length>0);if(n){if(r||i.length>0)throw Error(`Estimate a workspace or a folder set, not both.`);t.set(`workspaceId`,n)}else if(r){t.set(`projectPath`,r);for(let e of i)t.append(`extraRoots`,e)}else throw Error(`Pick a project or a workspace to estimate.`);return y(`/api/verse/context-fit`,t)}function D(e,t){return _(E(e),t)}function O(e,t,n){let r=new URLSearchParams({q:e});return typeof t==`number`&&Number.isFinite(t)&&r.set(`limit`,String(Math.max(1,Math.min(50,Math.floor(t))))),_(y(`/api/verse/search`,r),n)}function k(e){return`${h}${e}`}function A(e,t){return _(y(`/api/verse/memory`,new URLSearchParams({projectPath:e})),t)}function j(e){return{key:k(e),fetch:t=>A(e,t)}}async function M(e,t){let n=await v(`/api/verse/memory`,{projectPath:e,content:t});return i(k(e)),n}export{A as a,T as c,M as d,C as i,w as l,D as n,O as o,x as r,b as s,S as t,j as u};
|
|
2
|
+
//# sourceMappingURL=context-queries-_mEgy4eg.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-queries-_mEgy4eg.js","names":[],"sources":["../../../../../../src/web-ui/routes/verse/context/context-queries.ts"],"sourcesContent":["/**\n * routes/verse/context/context-queries.ts — every V3.9 context-orchestration\n * call the Verse console makes (docs/VERSE-CONTEXT.md): context mode, handoff\n * preview + handoff creation, preferences, context fit, session search and\n * shared project memory.\n *\n * Same conventions as ../verse-queries.ts, deliberately:\n *\n * - WRITES pull the held mutation token from auth-store, throw\n * `VerseMutationLockedError` (the SAME class, so ChatSection's `fail`\n * recognises it) when none is held, touch the hold on success, and\n * invalidate exactly the cache keys they obviously change.\n * - READS carry the per-tab read-client proof and report an expired read\n * session, exactly like `apiGet`. They are written out here rather than\n * routed through `apiGet` for one reason: `apiGet` discards the response\n * body on a non-2xx, and the routes below answer a refused read (\"path is\n * not a directory\", \"q must be at least 2 characters\") with a sentence\n * written for a person. Those sentences travel as `ApiError.detail`, the\n * field `describeControlError` already prefers.\n *\n * NOTHING HERE SPENDS. Every route below is deterministic server work\n * (preferences file, git ls-files, events.jsonl scans, a memory file). The\n * one model call a context feature can cause — \"ask this seat to summarize\"\n * — goes through the ordinary `sendVerseTurn`, i.e. through the engine's\n * `startTurn` chokepoint and the local-only policy, never through this file.\n *\n * `handoff-preview` is a POST although it only reads: it spawns\n * `git diff --stat` per root, and the API keeps every spawning route behind\n * the dispatch flag and the mutation token. It therefore needs the token like\n * any write, and the dialog says so.\n */\nimport type { VerseCreateSessionRequest, VerseSession } from '../../../data/api-types.js';\nimport type {\n VerseContextFit,\n VerseContextMode,\n VerseContextModeRequest,\n VerseHandoffPreview,\n VerseHandoffPreviewRequest,\n VersePreferences,\n VersePreferencesUpdate,\n VerseProjectMemory,\n VerseProjectMemoryWrite,\n VerseSearchResponse,\n} from '../../../../core/verse/types.js';\nimport { getMutationToken, reportSessionExpired, getReadClientProof, touchMutationHold } from '../../../data/auth-store.js';\nimport { invalidate, invalidatePrefix } from '../../../data/cache.js';\nimport { ApiError, apiPost } from '../../../data/client.js';\nimport type { QueryDef } from '../../../data/queries.js';\nimport { invalidateVerseLists, verseSessionPath, VERSE_SESSIONS_KEY, VerseMutationLockedError } from '../verse-queries.js';\nimport { setVerseSession } from '../verse-store.js';\nimport { asClause } from '../autonomy/format.js';\n\nexport const VERSE_PREFERENCES_KEY = 'verse-preferences';\n/** Prefix of every per-project memory key; `invalidatePrefix` reaches all of them. */\nexport const VERSE_MEMORY_KEY_PREFIX = 'verse-memory:';\n/** Prefix of every context-fit key (one per root set). */\nexport const VERSE_CONTEXT_FIT_KEY_PREFIX = 'verse-context-fit:';\n\n// ---------------------------------------------------------------------------\n// Transport\n// ---------------------------------------------------------------------------\n\n/** The server's own refusal sentence, when the body carried one. */\nasync function refusalDetail(res: Response): Promise<string | null> {\n try {\n const body = (await res.json()) as { error?: unknown; note?: unknown };\n if (typeof body.error === 'string' && body.error) return body.error;\n if (typeof body.note === 'string' && body.note) return body.note;\n } catch {\n /* not JSON — the status alone has to do */\n }\n return null;\n}\n\n/**\n * GET with the read-client proof. Behaves like `apiGet` (401 reports the\n * expired read session) but keeps the server's refusal sentence.\n */\nasync function getJson<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 const detail = await refusalDetail(res);\n // asClause: a detail ending in its own period would otherwise read \"…..\".\n throw new ApiError(`GET ${path} failed (HTTP ${res.status})${detail ? `: ${asClause(detail)}` : ''}.`, res.status, path, detail);\n }\n return (await res.json()) as T;\n}\n\nasync function post<T>(path: string, body: unknown): Promise<T> {\n const token = getMutationToken();\n if (!token) throw new VerseMutationLockedError();\n const result = await apiPost<T>(path, body, token);\n touchMutationHold();\n return result;\n}\n\nfunction withQuery(path: string, params: URLSearchParams): string {\n const query = params.toString();\n return query ? `${path}?${query}` : path;\n}\n\n// ---------------------------------------------------------------------------\n// Context mode\n// ---------------------------------------------------------------------------\n\n/**\n * Switch a session between `standard` and `expansive`. Applies from the next\n * turn (it only changes CLI flags, never prompt content, so the provider's\n * prompt cache survives the switch).\n *\n * The returned record is also written into verse-store, so the meter, the\n * mode chip and the composer hint all move on this response rather than on\n * the next list refresh — whichever component made the call.\n */\nexport async function setSessionContextMode(sessionId: string, mode: VerseContextMode): Promise<VerseSession> {\n const body: VerseContextModeRequest = { mode };\n const session = await post<VerseSession>(verseSessionPath(sessionId, '/context-mode'), body);\n setVerseSession(sessionId, session);\n invalidate(VERSE_SESSIONS_KEY);\n return session;\n}\n\n// ---------------------------------------------------------------------------\n// Handoff\n// ---------------------------------------------------------------------------\n\n/** Longest focus line the API accepts (types.ts VerseHandoffPreviewRequest). */\nexport const HANDOFF_FOCUS_MAX_CHARS = 500;\n\n/**\n * Build the deterministic handoff note for a session. Zero spend: the server\n * reads events.jsonl and a bounded `git diff --stat` per root.\n *\n * Only keys that carry a value are sent — the route rejects unknown keys, and\n * `includeLastAssistant: false` is the default anyway, so omitting it keeps\n * the body minimal and the server's validation the single authority.\n */\nexport async function fetchHandoffPreview(sessionId: string, req: VerseHandoffPreviewRequest = {}): Promise<VerseHandoffPreview> {\n const body: VerseHandoffPreviewRequest = {};\n if (req.includeLastAssistant === true) body.includeLastAssistant = true;\n const focus = typeof req.focus === 'string' ? req.focus.trim().slice(0, HANDOFF_FOCUS_MAX_CHARS) : '';\n if (focus) body.focus = focus;\n return post<VerseHandoffPreview>(verseSessionPath(sessionId, '/handoff-preview'), body);\n}\n\nexport interface HandoffSessionInput {\n /** The session being handed off. Its PINNED roots are reused, see below. */\n source: VerseSession;\n seatId: string;\n model?: string;\n contextMode?: VerseContextMode;\n /** Optional title; absent → the server titles it from the first message. */\n title?: string;\n}\n\n/**\n * Create the session a handoff continues in. FREE: creating a session spawns\n * nothing. The handoff text is NOT sent here — the caller pre-fills the new\n * chat's composer and the operator presses send, so the first (spent) turn is\n * always one the operator read.\n *\n * Roots: the source's PINNED roots (`projectPath` + `extraRoots`), never its\n * `workspaceId`. A session keeps the roots it was created with even if the\n * named workspace was later edited or deleted (types.ts VerseSession\n * `extraRoots`); re-resolving the workspace here would let the continuation\n * silently gain or lose a folder the conversation was about — or fail\n * outright on a deleted workspace. The API also refuses the two spellings\n * mixed, so it is one or the other, and the pinned set is the honest one.\n *\n * The server resolves `handoffFrom.title` from `handoffFromSessionId` itself;\n * nothing about provenance is taken from this body.\n */\nexport async function createHandoffSession(input: HandoffSessionInput): Promise<VerseSession> {\n const { source } = input;\n const req: VerseCreateSessionRequest = {\n projectPath: source.projectPath,\n seatId: input.seatId,\n handoffFromSessionId: source.id,\n };\n const extras = (source.extraRoots ?? []).filter((root) => typeof root === 'string' && root.length > 0 && root !== source.projectPath);\n if (extras.length > 0) req.extraRoots = extras;\n if (input.model) req.model = input.model;\n if (input.contextMode) req.contextMode = input.contextMode;\n const title = input.title?.trim();\n if (title) req.title = title;\n const session = await post<VerseSession>('/api/verse/sessions', req);\n invalidateVerseLists();\n return session;\n}\n\n// ---------------------------------------------------------------------------\n// Preferences\n// ---------------------------------------------------------------------------\n\nexport function fetchPreferences(signal?: AbortSignal): Promise<VersePreferences> {\n return getJson<VersePreferences>('/api/verse/preferences', signal);\n}\n\nexport const versePreferencesQuery: QueryDef<VersePreferences> = {\n key: VERSE_PREFERENCES_KEY,\n fetch: (signal) => fetchPreferences(signal),\n};\n\n/**\n * Change ONE standing preference (the three request forms are exclusive, so a\n * body never changes more than the operator clicked). Memory records carry an\n * `enabled` flag derived from these preferences, so every cached memory read\n * is refreshed too.\n */\nexport async function updatePreferences(update: VersePreferencesUpdate): Promise<VersePreferences> {\n const prefs = await post<VersePreferences>('/api/verse/preferences', update);\n invalidate(VERSE_PREFERENCES_KEY);\n invalidatePrefix(VERSE_MEMORY_KEY_PREFIX);\n return prefs;\n}\n\n// ---------------------------------------------------------------------------\n// Context fit\n// ---------------------------------------------------------------------------\n\nexport interface ContextFitQuery {\n workspaceId?: string;\n projectPath?: string;\n extraRoots?: string[];\n}\n\n/**\n * The query string for a fit request: a workspace id ALONE, or a project path\n * with each extra root as its own repeated `extraRoots` parameter (a path may\n * contain a comma, so a joined list could not be split back honestly). The\n * API refuses the two spellings mixed — refused here first, with the reason.\n */\nexport function contextFitPath(q: ContextFitQuery): string {\n const params = new URLSearchParams();\n const workspaceId = q.workspaceId?.trim() ?? '';\n const projectPath = q.projectPath?.trim() ?? '';\n const extras = (q.extraRoots ?? []).map((r) => r.trim()).filter((r) => r.length > 0);\n if (workspaceId) {\n if (projectPath || extras.length > 0) throw new Error('Estimate a workspace or a folder set, not both.');\n params.set('workspaceId', workspaceId);\n } else if (projectPath) {\n params.set('projectPath', projectPath);\n for (const root of extras) params.append('extraRoots', root);\n } else {\n throw new Error('Pick a project or a workspace to estimate.');\n }\n return withQuery('/api/verse/context-fit', params);\n}\n\n/** How big the reachable code is, in estimated tokens. Zero spend (git ls-files + stat). */\nexport function fetchContextFit(q: ContextFitQuery, signal?: AbortSignal): Promise<VerseContextFit> {\n return getJson<VerseContextFit>(contextFitPath(q), signal);\n}\n\n/** Cache-backed form, keyed by the exact root set, for pickers that re-render often. */\nexport function verseContextFitQuery(q: ContextFitQuery): QueryDef<VerseContextFit> {\n const path = contextFitPath(q);\n return { key: `${VERSE_CONTEXT_FIT_KEY_PREFIX}${path}`, fetch: (signal) => getJson<VerseContextFit>(path, signal) };\n}\n\n// ---------------------------------------------------------------------------\n// Session search\n// ---------------------------------------------------------------------------\n\n/** The API caps `limit` at 50 (session-search.ts); asking for more is refused, so clamp. */\nexport const SEARCH_LIMIT_MAX = 50;\n\n/** Keyword search over past sessions' messages. Zero spend — a bounded scan of events.jsonl. */\nexport function searchSessions(q: string, limit?: number, signal?: AbortSignal): Promise<VerseSearchResponse> {\n const params = new URLSearchParams({ q });\n if (typeof limit === 'number' && Number.isFinite(limit)) {\n params.set('limit', String(Math.max(1, Math.min(SEARCH_LIMIT_MAX, Math.floor(limit)))));\n }\n return getJson<VerseSearchResponse>(withQuery('/api/verse/search', params), signal);\n}\n\n// ---------------------------------------------------------------------------\n// Shared project memory\n// ---------------------------------------------------------------------------\n\n/**\n * The memory record as the browser receives it. The server's public-JSON\n * sanitizer rewrites the home folder as `~` and secret-shaped text as\n * `[REDACTED]` on the way out, so `content` can differ from the file on disk;\n * the route then adds `contentSanitized: true` (absent otherwise). Declared\n * here, not in types.ts: that contract is frozen and the flag is additive.\n */\nexport type VerseProjectMemoryView = VerseProjectMemory & { contentSanitized?: boolean };\n\nexport function verseProjectMemoryKey(projectPath: string): string {\n return `${VERSE_MEMORY_KEY_PREFIX}${projectPath}`;\n}\n\nexport function fetchProjectMemory(projectPath: string, signal?: AbortSignal): Promise<VerseProjectMemoryView> {\n return getJson<VerseProjectMemoryView>(withQuery('/api/verse/memory', new URLSearchParams({ projectPath })), signal);\n}\n\nexport function verseProjectMemoryQuery(projectPath: string): QueryDef<VerseProjectMemoryView> {\n return { key: verseProjectMemoryKey(projectPath), fetch: (signal) => fetchProjectMemory(projectPath, signal) };\n}\n\n/**\n * Replace MEMORY.md for a project (atomic, 0600 on the server, capped at\n * VERSE_MEMORY_MAX_BYTES). `''` clears it. Chats already open keep the block\n * pinned in their instructions at creation; they see the change when they\n * next read the file.\n */\nexport async function writeProjectMemory(projectPath: string, content: string): Promise<VerseProjectMemoryView> {\n const body: VerseProjectMemoryWrite = { projectPath, content };\n const memory = await post<VerseProjectMemoryView>('/api/verse/memory', body);\n invalidate(verseProjectMemoryKey(projectPath));\n return memory;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var e={label:`_label_1j3uu_14`,optional:`_optional_1j3uu_23`,field:`_field_1j3uu_25`,row:`_row_1j3uu_26`,input:`_input_1j3uu_27`,textarea:`_textarea_1j3uu_30`,hint:`_hint_1j3uu_59`,locked:`_locked_1j3uu_61`,warn:`_warn_1j3uu_62`,error:`_error_1j3uu_70`,primary:`_primary_1j3uu_80`,secondary:`_secondary_1j3uu_81`,cancel:`_cancel_1j3uu_82`,link:`_link_1j3uu_109`,small:`_small_1j3uu_121`,pending:`_pending_1j3uu_124`,"context-pulse":`_context-pulse_1j3uu_1`,dialogWidth:`_dialogWidth_1j3uu_145`,form:`_form_1j3uu_146`,aside:`_aside_1j3uu_150`,fit:`_fit_1j3uu_172`,"fit-ok":`_fit-ok_1j3uu_180`,"fit-warn":`_fit-warn_1j3uu_181`,"fit-danger":`_fit-danger_1j3uu_182`,"fit-unknown":`_fit-unknown_1j3uu_183`,cost:`_cost_1j3uu_185`,actions:`_actions_1j3uu_192`,blocked:`_blocked_1j3uu_193`,memory:`_memory_1j3uu_207`,memoryHead:`_memoryHead_1j3uu_215`,memoryState:`_memoryState_1j3uu_228`,memoryHeadAction:`_memoryHeadAction_1j3uu_229`,memoryBody:`_memoryBody_1j3uu_242`,memoryMeta:`_memoryMeta_1j3uu_257`,memoryFiles:`_memoryFiles_1j3uu_258`,memoryActions:`_memoryActions_1j3uu_265`,spacer:`_spacer_1j3uu_266`,danger:`_danger_1j3uu_267`,counter:`_counter_1j3uu_269`,counterOver:`_counterOver_1j3uu_270`,search:`_search_1j3uu_276`,searchBox:`_searchBox_1j3uu_277`,searchHead:`_searchHead_1j3uu_284`,searchCount:`_searchCount_1j3uu_298`,searchNote:`_searchNote_1j3uu_304`,searchError:`_searchError_1j3uu_305`,hits:`_hits_1j3uu_315`,hit:`_hit_1j3uu_315`,hitMarker:`_hitMarker_1j3uu_332`,hitTitle:`_hitTitle_1j3uu_339`,hitTime:`_hitTime_1j3uu_348`,hitSnippet:`_hitSnippet_1j3uu_355`,hitWho:`_hitWho_1j3uu_365`,"engine-claude":`_engine-claude_1j3uu_374`,"engine-codex":`_engine-codex_1j3uu_375`,"engine-grok":`_engine-grok_1j3uu_376`,"engine-local":`_engine-local_1j3uu_377`};export{e as t};
|
|
2
|
+
//# sourceMappingURL=context.module-BijAfoJw.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.module-BijAfoJw.js","names":[],"sources":["../../../../../../src/web-ui/routes/verse/context/context.module.css"],"sourcesContent":["/*\n * routes/verse/context/context.module.css — the handoff dialog, the project\n * memory panel and the message-search results. One file because the three\n * share one control vocabulary with the new-chat dialog (NewChatDialog.module.css)\n * and the resources panel: micro-labels in the display face, hairline fields,\n * a single accent-filled primary, ghost secondaries. DESIGN-V2 §1: no cards,\n * no shadows outside the dialog itself, colour only where it carries meaning.\n */\n\n/* ======================================================================\n Shared field vocabulary\n ====================================================================== */\n\n.label {\n margin: 0;\n font-family: var(--font-display);\n font-size: var(--text-2xs-size, 11px);\n font-weight: var(--font-weight-semibold);\n letter-spacing: var(--tracking-label, 0.06em);\n text-transform: uppercase;\n color: var(--text-tertiary);\n}\n.optional { font-weight: var(--font-weight-regular); letter-spacing: 0; text-transform: none; opacity: 0.8; }\n\n.field { display: grid; gap: var(--space-1); min-width: 0; }\n.row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); min-width: 0; }\n.row > .input { flex: 1 1 12rem; min-width: 0; }\n\n.input,\n.textarea {\n width: 100%;\n border: 1px solid var(--border-default);\n border-radius: var(--radius-sm);\n background: var(--bg-input);\n color: var(--text-primary);\n font-size: var(--text-sm-size);\n transition: border-color var(--duration-fast) var(--ease-standard), box-shadow var(--duration-fast) var(--ease-standard);\n}\n.input { min-height: var(--control-h); padding: 0 var(--space-2); }\n.textarea {\n display: block;\n min-height: 8rem;\n padding: var(--space-2);\n resize: vertical;\n font-family: var(--font-mono);\n font-size: var(--text-xs-size);\n line-height: var(--text-xs-line);\n white-space: pre-wrap;\n}\n.input::placeholder,\n.textarea::placeholder { color: var(--text-tertiary); }\n.input:hover:not(:disabled),\n.textarea:hover:not(:disabled) { border-color: var(--border-strong); }\n/* Same focus treatment as NewChatDialog and the Input primitive. */\n.input:focus-visible,\n.textarea:focus-visible { border-color: var(--border-focus); box-shadow: var(--shadow-focus-ring); }\n.textarea:disabled { opacity: 0.7; cursor: progress; }\n\n.hint { margin: 0; font-size: var(--text-xs-size); line-height: var(--text-xs-line); color: var(--text-tertiary); }\n.hint code,\n.locked code { font-size: 0.95em; }\n.warn {\n margin: 0;\n padding-left: var(--space-3);\n border-left: 2px solid var(--status-warning-solid);\n color: var(--status-warning-fg);\n font-size: var(--text-xs-size);\n line-height: var(--text-xs-line);\n}\n.error {\n margin: 0;\n padding-left: var(--space-3);\n border-left: 2px solid var(--status-danger-solid);\n color: var(--status-danger-fg);\n font-size: var(--text-sm-size);\n line-height: var(--text-sm-line);\n}\n\n/* Buttons: one filled primary per surface; everything else is a ghost. */\n.primary,\n.secondary,\n.cancel {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n min-height: var(--control-h);\n padding: 0 var(--space-3);\n border: 1px solid transparent;\n border-radius: var(--radius-sm);\n font-size: var(--text-sm-size);\n font-weight: var(--font-weight-medium);\n white-space: nowrap;\n transition: background var(--duration-fast) var(--ease-standard), color var(--duration-fast) var(--ease-standard),\n border-color var(--duration-fast) var(--ease-standard);\n}\n.primary { padding: 0 var(--space-4); background: var(--accent-600); color: var(--accent-contrast); }\n.primary:hover:not(:disabled),\n.primary:active:not(:disabled) { background: var(--accent-700); }\n.secondary { border-color: var(--border-default); color: var(--text-primary); }\n.secondary:hover:not(:disabled) { background: var(--bg-hover); border-color: var(--border-strong); }\n.secondary:active:not(:disabled) { background: var(--bg-active); }\n.cancel { color: var(--text-secondary); }\n.cancel:hover { background: var(--bg-hover); color: var(--text-primary); }\n.cancel:active { background: var(--bg-active); }\n.primary:disabled,\n.secondary:disabled { opacity: 0.45; cursor: not-allowed; }\n\n.link {\n padding: 0;\n border: 0;\n background: none;\n color: var(--accent-500);\n font: inherit;\n text-decoration: underline;\n text-underline-offset: 2px;\n cursor: pointer;\n}\n.link:hover { color: var(--accent-600); }\n\n.small { min-height: var(--control-h-sm); padding: 0 var(--space-2); font-size: var(--text-xs-size); }\n\n/* The quiet running marker: a pulsing dot, never a spinner (DESIGN-V2 §4). */\n.pending {\n flex: none;\n width: 6px;\n height: 6px;\n border-radius: var(--radius-full);\n background: var(--status-running-solid);\n animation: context-pulse 1.8s ease-out infinite;\n}\n@keyframes context-pulse {\n 0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--status-running-solid) 55%, transparent); }\n 100% { box-shadow: 0 0 0 5px transparent; }\n}\n@media (prefers-reduced-motion: reduce) {\n :root:not([data-motion=\"full\"]) .pending { animation: none; }\n}\n:root[data-motion=\"reduce\"] .pending { animation: none; }\n\n/* ======================================================================\n Handoff dialog\n ====================================================================== */\n\n.dialogWidth { width: min(640px, calc(100vw - 32px)); max-height: 86vh; }\n.form { display: grid; gap: var(--space-4); }\n\n/* The optional, spending path sits on a hairline rule so it reads as an\n aside to the free flow around it, not as a step in it. */\n.aside {\n display: grid;\n gap: var(--space-2);\n padding-left: var(--space-3);\n border-left: 1px solid var(--border-default);\n}\n\n.locked {\n display: grid;\n justify-items: start;\n gap: var(--space-2);\n padding: var(--space-3);\n border: 1px dashed var(--border-default);\n border-radius: var(--radius-sm);\n color: var(--text-secondary);\n font-size: var(--text-sm-size);\n line-height: var(--text-sm-line);\n}\n.locked p { margin: 0; }\n\n/* Fit verdict: text first; the rule on its edge is reinforcement, never the\n only signal (DESIGN-V2 §6). */\n.fit {\n margin: 0;\n padding-left: var(--space-3);\n border-left: 2px solid var(--border-default);\n color: var(--text-secondary);\n font-size: var(--text-xs-size);\n line-height: var(--text-xs-line);\n}\n.fit-ok { border-left-color: var(--status-success-solid); }\n.fit-warn { border-left-color: var(--status-warning-solid); }\n.fit-danger { border-left-color: var(--status-danger-solid); color: var(--status-danger-fg); }\n.fit-unknown { border-left-style: dashed; }\n\n.cost {\n margin: 0;\n color: var(--text-secondary);\n font-size: var(--text-xs-size);\n line-height: var(--text-xs-line);\n}\n\n.actions { display: flex; flex-wrap: wrap; justify-content: flex-end; align-items: center; gap: var(--space-2); }\n.blocked {\n flex: 1 1 12rem;\n min-width: 0;\n color: var(--text-tertiary);\n font-size: var(--text-xs-size);\n line-height: var(--text-xs-line);\n}\n\n/* ======================================================================\n Project memory (a section of the resources panel)\n Mirrors ResourcesPanel.module.css `.section + .section`: it is mounted as\n a sibling of those sections, never inside one, so it carries its own rule.\n ====================================================================== */\n\n.memory {\n display: grid;\n gap: var(--space-3);\n margin-top: var(--space-4);\n padding: var(--space-4) var(--space-4) 0;\n border-top: 1px solid var(--border-subtle);\n min-width: 0;\n}\n.memoryHead {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-2);\n margin: 0;\n font-family: var(--font-display);\n font-size: var(--text-2xs-size, 11px);\n font-weight: var(--font-weight-semibold);\n letter-spacing: var(--tracking-label, 0.06em);\n text-transform: uppercase;\n color: var(--text-tertiary);\n}\n.memoryState { font-weight: var(--font-weight-regular); letter-spacing: 0; text-transform: none; color: var(--text-secondary); }\n.memoryHeadAction {\n margin-left: auto;\n padding: 0 var(--space-1);\n border-radius: var(--radius-xs);\n color: var(--text-tertiary);\n font-family: var(--font-ui);\n font-size: var(--text-2xs-size, 11px);\n font-weight: var(--font-weight-regular);\n letter-spacing: 0;\n text-transform: none;\n}\n.memoryHeadAction:hover { color: var(--text-primary); background: var(--bg-hover); }\n\n.memoryBody {\n margin: 0;\n max-height: 16rem;\n overflow: auto;\n padding: var(--space-2);\n border: 1px solid var(--border-subtle);\n border-radius: var(--radius-sm);\n background: var(--bg-code, var(--bg-input));\n color: var(--text-primary);\n font-family: var(--font-mono);\n font-size: var(--text-xs-size);\n line-height: var(--text-xs-line);\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n}\n.memoryMeta { display: flex; flex-wrap: wrap; gap: var(--space-1) var(--space-2); }\n.memoryFiles { margin: 0; padding: 0; list-style: none; display: flex; flex-wrap: wrap; gap: var(--space-1); }\n.memoryFiles code {\n padding: 0 var(--space-1);\n border: 1px solid var(--border-subtle);\n border-radius: var(--radius-xs);\n font-size: var(--text-2xs-size, 11px);\n}\n.memoryActions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }\n.memoryActions .spacer { flex: 1; }\n.danger { color: var(--status-danger-fg); }\n.danger:hover:not(:disabled) { background: var(--status-danger-bg); border-color: var(--status-danger-solid); }\n.counter { font-variant-numeric: tabular-nums; }\n.counterOver { color: var(--status-danger-fg); }\n\n/* ======================================================================\n Message search (the sidebar's lower half while a query is typed)\n ====================================================================== */\n\n.search { padding-top: var(--space-4); min-width: 0; }\n.searchBox {\n position: relative;\n display: flex;\n align-items: center;\n margin: 0 var(--space-3) var(--space-2);\n}\n.searchBox .input { padding-left: var(--space-2); }\n.searchHead {\n display: flex;\n align-items: baseline;\n gap: var(--space-2);\n padding: 0 var(--space-3) var(--space-1);\n margin: 0;\n font-family: var(--font-display);\n font-size: var(--text-2xs-size, 11px);\n line-height: 1.4;\n font-weight: var(--font-weight-semibold);\n letter-spacing: var(--tracking-label, 0.06em);\n text-transform: uppercase;\n color: var(--text-tertiary);\n}\n.searchCount {\n margin-left: auto;\n font-variant-numeric: tabular-nums;\n letter-spacing: 0;\n font-weight: var(--font-weight-regular);\n}\n.searchNote { padding: var(--space-1) var(--space-3); }\n.searchError {\n display: grid;\n justify-items: start;\n gap: var(--space-1);\n padding: var(--space-1) var(--space-3);\n color: var(--status-danger-fg);\n font-size: var(--text-xs-size);\n line-height: var(--text-xs-line);\n}\n\n.hits { display: flex; flex-direction: column; margin: 0; padding: 0; list-style: none; }\n.hit {\n display: grid;\n grid-template-columns: 2px minmax(0, 1fr) auto;\n grid-template-areas:\n 'marker title time'\n 'marker snippet snippet';\n column-gap: var(--space-2);\n row-gap: 2px;\n width: 100%;\n padding: var(--space-2) var(--space-3);\n color: var(--text-secondary);\n text-align: left;\n transition: background var(--duration-fast) var(--ease-standard), color var(--duration-fast) var(--ease-standard);\n}\n.hit:hover { background: var(--bg-hover); color: var(--text-primary); }\n.hit:active { background: var(--bg-active); }\n.hitMarker {\n grid-area: marker;\n align-self: stretch;\n width: 2px;\n border-radius: 1px;\n background: var(--verse-engine, var(--text-tertiary));\n}\n.hitTitle {\n grid-area: title;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--text-primary);\n font-size: var(--text-sm-size);\n line-height: var(--text-sm-line);\n}\n.hitTime {\n grid-area: time;\n font-family: var(--font-display);\n font-size: var(--text-2xs-size, 11px);\n font-variant-numeric: tabular-nums;\n color: var(--text-secondary);\n}\n.hitSnippet {\n grid-area: snippet;\n display: -webkit-box;\n -webkit-line-clamp: 3;\n -webkit-box-orient: vertical;\n overflow: hidden;\n font-size: var(--text-xs-size);\n line-height: var(--text-xs-line);\n overflow-wrap: anywhere;\n}\n.hitWho { color: var(--text-tertiary); }\n/* A match is marked by weight AND tint — never tint alone (DESIGN-V2 §6). */\n.hitSnippet mark {\n padding: 0 1px;\n border-radius: 2px;\n background: color-mix(in srgb, var(--accent-500) 18%, transparent);\n color: var(--text-primary);\n font-weight: var(--font-weight-semibold);\n}\n.engine-claude { --verse-engine: var(--engine-claude, #c96442); }\n.engine-codex { --verse-engine: var(--engine-codex, #10a37f); }\n.engine-grok { --verse-engine: var(--engine-grok, #6b7280); }\n.engine-local { --verse-engine: var(--engine-local, #7c5cff); }\n"],"mappings":""}
|
package/dist/core/web/public/next/assets/{control-queries-BGjarMm9.js → control-queries-C4TG8blN.js}
RENAMED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{D as e,P as t,
|
|
2
|
-
//# sourceMappingURL=control-queries-
|
|
1
|
+
import{D as e,P as t,at as n,l as r,u as i}from"./global-DG1fmn4y.js";import{i as a,n as o}from"./mutations-DjXM3IDg.js";var s={dailyBudgetUsd:{min:0,max:1e3},perTickItems:{min:1,max:50},parallel:{min:1,max:16},intervalMs:{min:3e4,max:864e5},maxConcurrent:{min:1,max:32},concurrency:{min:1,max:32},subscriptionMaxPercent:{min:1,max:100},foundryLimitMax:{min:0,max:1e6}},c=`verse-control`,l=`verse-caps`,u=`verse-scope`,d=`verse-safety`,f=`verse-audit:`,p=500,m={key:c,fetch:e=>r(`/api/verse/control`,e)},h={key:l,fetch:e=>r(`/api/verse/caps`,e)},g={key:d,fetch:e=>r(`/api/verse/safety`,e)};function _(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}var v={key:u,fetch:e=>r(`/api/verse/scope`,e)};function y(e={}){let t=Math.min(e.limit??200,p),n=new URLSearchParams;return n.set(`limit`,String(t)),e.action&&n.set(`action`,e.action),e.result&&n.set(`result`,e.result),{key:`${f}${t}:${e.action??``}:${e.result??``}`,fetch:e=>r(`/api/verse/audit?${n.toString()}`,e)}}var b={key:`goals`,fetch:e=>r(`/api/goals`,e)};function x(e){return e==null?{items:[],absent:!0}:Array.isArray(e)?{items:e,absent:!1}:_(e)&&Array.isArray(e.items)?{items:e.items,absent:!1}:{items:[],absent:!0}}var S={key:`backlog`,fetch:e=>r(`/api/backlog`,e).then(x)},C=class extends Error{constructor(){super(`Unlock actions with the mutation token before changing the loop.`),this.name=`VerseControlLockedError`}};async function w(n,r){let a=e();if(!a)throw new C;let o=await i(n,r,a);return t(),o}function T(){n(c)}async function E(e){let t=await w(`/api/verse/caps`,e);return n(l),T(),t}async function D(e){await w(`/api/verse/scope`,e),n(u),T()}async function O(e){let t=await w(`/api/verse/daemon`,{action:e});return T(),n(`daemon`),t}async function k(){await o(),T()}async function A(){await a(),T()}export{O as a,y as c,m as d,b as f,s as h,A as i,S as l,v as m,C as n,E as o,g as p,k as r,D as s,c as t,h as u};
|
|
2
|
+
//# sourceMappingURL=control-queries-C4TG8blN.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"control-queries-BGjarMm9.js","names":[],"sources":["../../../../../../src/core/verse/control-types.ts","../../../../../../src/web-ui/routes/verse/autonomy/control-queries.ts"],"sourcesContent":["/**\n * Ashlr Verse — autonomy control-plane contract (V2).\n *\n * V1 (`./types.ts`) is frozen: it describes interactive chat sessions. This\n * module adds the shapes the Autonomy cockpit needs to run the hub's existing\n * autonomous fleet without a terminal — the CONFIGURED caps, the enrollment\n * scope, the audit trail, the kill switch, and the daemon lifecycle.\n *\n * Two hard rules are encoded here rather than left to the UI:\n *\n * 1. The global kill switch (`~/.ashlr/KILL`) is an EMERGENCY STOP, not a\n * pause. It also refuses the agent's own `mcp-native` write tools. Every\n * shape that carries it is named `killSwitch`, and the action that engages\n * it is named `emergencyStop`. It is never called \"pause\".\n *\n * V2.1 adds the control that name was being withheld from: the\n * DAEMON-SCOPED pause (`~/.ashlr/daemon.paused`, see core/daemon/pause.ts).\n * It halts autonomous dispatch and NOTHING else — `assertMayMutate`,\n * `mcp-native`, and `mcp-native-engineer` do not read it. It travels as\n * `pause: VerseDaemonPause`, always a FIELD OF ITS OWN, never merged into\n * `killSwitch`, because the whole point is that the two have different\n * blast radii and the operator must be able to see which one is on.\n * 2. Nothing in this module is a secret. Launcher commands, tokens, and env\n * values never appear in any of these shapes — the daemon lifecycle result\n * carries a pid, never an argv.\n *\n * Everything is a projection of state the hub already computes; nothing here\n * introduces a new source of truth.\n */\n\nimport type { AuditEntry } from '../types.js';\nimport type { PublicDaemonObservation } from '../daemon/public-observation.js';\nimport type { FrontierEngineUsage } from '../usage/frontier-usage.js';\nimport type { SafetyReport } from '../../cli/verify-safety.js';\n\n// ---------------------------------------------------------------------------\n// Errors\n// ---------------------------------------------------------------------------\n\n/**\n * Error codes for `/api/verse/{control,caps,scope,audit,daemon,safety}`.\n * `VERSE_INVALID` / `VERSE_TOO_LARGE` are deliberately the SAME codes V1 uses,\n * so one client error mapper covers both halves of the Verse API.\n */\nexport type VerseControlErrorCode =\n | 'VERSE_INVALID'\n | 'VERSE_REFUSED'\n | 'VERSE_TOO_LARGE'\n | 'VERSE_UNAVAILABLE';\n\n/** Every error body on these routes. */\nexport interface VerseControlError {\n error: string;\n code: VerseControlErrorCode;\n}\n\n// ---------------------------------------------------------------------------\n// Caps — the CONFIGURED limits (GET/POST /api/verse/caps)\n// ---------------------------------------------------------------------------\n\n/** One `cfg.foundry.limits[engine]` entry, flattened for transport. */\nexport interface VerseFoundryLimit {\n /** EngineId key, e.g. 'claude' | 'codex'. */\n engine: string;\n /** Rolling window label, e.g. '1m' | '5m' | '1h' | '1d'. */\n window: string;\n /** Max dispatches per window. 0 means \"no dispatches allowed\". */\n max: number;\n}\n\n/** `cfg.daemon.concurrency` — null per tier means \"not configured, using the default\". */\nexport interface VerseCapsConcurrency {\n local: number | null;\n cloud: number | null;\n total: number | null;\n}\n\n/**\n * The configured autonomy limits. These are the values the daemon actually\n * reads — no route exposed them before V2, which is why the cockpit could not\n * show a cap next to the usage burning against it.\n */\nexport interface VerseCaps {\n /** HARD daily spend ceiling (USD). 0 means the daemon is stopped by budget. */\n dailyBudgetUsd: number;\n /** Backlog items processed per tick. */\n perTickItems: number;\n /** Concurrent sandboxed swarms per tick (batch mode). */\n parallel: number;\n /** Tick interval in loop mode (ms). */\n intervalMs: number;\n /** Daemon execution mode. */\n mode: 'batch' | 'continuous';\n /** Ceiling on in-flight dispatches in continuous mode; null when unset. */\n maxConcurrent: number | null;\n /** Per-tier concurrency budgets; null per tier when unset. */\n concurrency: VerseCapsConcurrency;\n /** Subscription-window throttle percentage (1–100). */\n subscriptionMaxPercent: number;\n /** Per-engine dispatch limits, sorted by engine id. */\n foundryLimits: VerseFoundryLimit[];\n /**\n * HONEST STATE: the cap keys whose value is a built-in default rather than\n * something configured on disk. The UI renders these as \"default\", never as\n * a deliberate choice the operator made. Sorted, stable.\n */\n defaulted: VerseCapKey[];\n}\n\n/** The scalar cap keys a client may update. `foundryLimits` is handled separately. */\nexport type VerseCapKey =\n | 'dailyBudgetUsd'\n | 'perTickItems'\n | 'parallel'\n | 'intervalMs'\n | 'mode'\n | 'maxConcurrent'\n | 'concurrency'\n | 'subscriptionMaxPercent'\n | 'foundryLimits';\n\n/** POST /api/verse/caps — a partial update. Unknown keys are rejected. */\nexport interface VerseCapsUpdate {\n dailyBudgetUsd?: number;\n perTickItems?: number;\n parallel?: number;\n intervalMs?: number;\n mode?: 'batch' | 'continuous';\n maxConcurrent?: number;\n concurrency?: { local?: number; cloud?: number; total?: number };\n subscriptionMaxPercent?: number;\n /** Replaces the named engines' limits; engines not listed are left alone. */\n foundryLimits?: VerseFoundryLimit[];\n}\n\n/**\n * POST /api/verse/caps result. `live: true` is not decoration — the daemon\n * re-reads config at the top of every tick, so a saved cap binds on the next\n * tick without a restart.\n */\nexport interface VerseCapsUpdateResult {\n ok: true;\n /** The cap keys this request actually changed, in request order. */\n applied: VerseCapKey[];\n live: true;\n /** The full caps AFTER the write, re-read from disk. */\n caps: VerseCaps;\n}\n\n// ---------------------------------------------------------------------------\n// Scope — the enrollment registry (GET/POST /api/verse/scope)\n// ---------------------------------------------------------------------------\n\nexport interface VerseScopeRepo {\n path: string;\n name: string;\n /** False when the registry still lists a directory that is gone. */\n exists: boolean;\n}\n\n/**\n * GET /api/verse/scope. An EMPTY `repos` with no `degradedReason` is the\n * valid default state and means the daemon will do nothing at all — the UI\n * must say that plainly rather than rendering an innocuous empty list.\n */\nexport interface VerseScope {\n repos: VerseScopeRepo[];\n /** Present only when enrollment authority could not be read at all. */\n degradedReason?: string;\n}\n\nexport type VerseScopeAction = 'enroll' | 'unenroll';\n\n/** POST /api/verse/scope body. */\nexport interface VerseScopeRequest {\n action: VerseScopeAction;\n path: string;\n}\n\nexport interface VerseScopeResult {\n ok: boolean;\n action: VerseScopeAction;\n /** The canonical (realpath'd) registry path this acted on. */\n path: string;\n /** False when the registry already had the requested state (idempotent). */\n changed: boolean;\n /** Policy reason string, e.g. 'enrolled' | 'already-unenrolled'. */\n reason: string;\n /** The scope AFTER the mutation. */\n scope: VerseScope;\n}\n\n// ---------------------------------------------------------------------------\n// Audit (GET /api/verse/audit)\n// ---------------------------------------------------------------------------\n\nexport type VerseAuditResult = AuditEntry['result'];\n\n/** GET /api/verse/audit?limit=&action=&result= */\nexport interface VerseAuditResponse {\n /** Newest first, already filtered, at most `limit` entries. */\n entries: AuditEntry[];\n /** The effective cap applied to `entries` (never above 500). */\n limit: number;\n /** Echo of the applied filter so the UI cannot drift from what it shows. */\n filter: { action: string | null; result: VerseAuditResult | null };\n /** How many raw entries were scanned before filtering. */\n scanned: number;\n /**\n * True when the scan filled its own ceiling, so older entries MAY exist\n * beyond what was read. Conservative by design: it never claims the reader\n * reached the end of history when it cannot prove that.\n */\n truncated: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Kill switch\n// ---------------------------------------------------------------------------\n\n/**\n * The GLOBAL kill switch (`~/.ashlr/KILL`). `state: 'unknown'` is a real,\n * distinct outcome — the sentinel was uninspectable — and must never be\n * rendered as \"off\".\n */\nexport interface VerseKillSwitch {\n state: 'active' | 'inactive' | 'unknown';\n sourceState: 'healthy' | 'degraded';\n reason: string;\n /**\n * Plain-language statement of blast radius, shipped with the state so no\n * surface can show the switch without the warning.\n */\n note: string;\n}\n\n// ---------------------------------------------------------------------------\n// Daemon pause — the NARROW stop (POST /api/verse/daemon {action:'pause'})\n// ---------------------------------------------------------------------------\n\n/**\n * The DAEMON-SCOPED pause sentinel (`~/.ashlr/daemon.paused`), projected for\n * transport. Shares `VerseKillSwitch`'s shape on purpose — both are sentinels\n * read the same way, and both keep `'unknown'` as a real third state that must\n * never render as \"off\".\n *\n * `state: 'unknown'` is treated by the daemon as PAUSED (fail safe), so the UI\n * must say \"paused — the sentinel could not be read\", not \"running\".\n *\n * The sentinel PATH is deliberately absent: it is machine-local detail the\n * cockpit has no use for, the same omission `VerseKillSwitch` makes.\n */\nexport interface VerseDaemonPause {\n /** 'paused' = dispatch halted. 'running' = proven not paused. */\n state: 'paused' | 'running' | 'unknown';\n sourceState: 'healthy' | 'degraded';\n reason: string;\n /** ISO time the pause was requested; null when not paused or unknown. */\n pausedAt: string | null;\n /** Which surface paused it ('cli' | 'verse-control-plane' | 'unknown'). */\n by: string | null;\n /** Plain-language statement of the NARROW blast radius, shipped with the state. */\n note: string;\n}\n\n// ---------------------------------------------------------------------------\n// Daemon lifecycle (POST /api/verse/daemon)\n// ---------------------------------------------------------------------------\n\n/**\n * 'start' launches the autonomous loop detached (exactly `ashlr daemon start`).\n * 'once' runs a single tick the same way.\n *\n * Three stops, three honestly different scopes:\n * - 'pause' — halts autonomous DISPATCH only. Reversible with 'resume'.\n * Leaves the agent's own write tools alone. The default choice.\n * - 'stop' — the ordinary stop, which today engages the GLOBAL kill switch\n * (`stopDaemon()` is `setKill(true)`), so it is wider than a\n * pause and the result's `note` says so.\n * - emergency stop — not on this route at all (`POST /api/fleet/pause`).\n */\nexport type VerseDaemonAction = 'start' | 'stop' | 'once' | 'pause' | 'resume';\n\n/** Non-secret projection of `~/.ashlr/daemon.json`. */\nexport interface VerseDaemonStateProjection {\n running: boolean;\n pid: number | null;\n startedAt: string | null;\n lastTickAt: string | null;\n todayDate: string | null;\n todaySpentUsd: number;\n itemsProcessed: number;\n}\n\n/**\n * POST /api/verse/daemon result. `pid` is the only thing that ever escapes the\n * spawn — the argv, the env, and the resolved launcher path never do.\n */\nexport interface VerseDaemonActionResult {\n ok: boolean;\n action: VerseDaemonAction;\n /** True when a detached child was launched by this request. */\n spawned: boolean;\n /** The detached child's pid for 'start'/'once'; null otherwise. */\n pid: number | null;\n state: VerseDaemonStateProjection;\n killSwitch: VerseKillSwitch;\n /** The daemon-scoped pause AFTER this action. Never folded into killSwitch. */\n pause: VerseDaemonPause;\n /** One line of plain language about what just happened, for the UI to echo. */\n note: string;\n}\n\n// ---------------------------------------------------------------------------\n// Fleet essentials + control aggregate (GET /api/verse/control)\n// ---------------------------------------------------------------------------\n\n/**\n * The small honest slice of FleetStatus/ControlSnapshot the cockpit needs.\n * Deliberately excludes service file paths and error-log paths: the cockpit\n * does not need them, and keeping them out keeps this payload trivially\n * provable as free of machine-local command detail.\n */\nexport interface VerseFleetEssentials {\n /** Mode the autonomy control loop last directed, when known. */\n directionMode: string | null;\n directionAt: string | null;\n directionReason: string | null;\n autonomyControlLoop: boolean;\n autonomyControlMode: string;\n service: {\n registrationState: 'present' | 'absent' | 'unknown';\n installed: boolean;\n running: boolean;\n runtimeState: string | null;\n };\n /** Freshness of the shared fleet-status cache backing these values. */\n freshness: { stale: boolean; ageMs: number };\n}\n\n/** Today's spend against the configured ceiling. */\nexport interface VerseSpend {\n /** null when the daemon ledger could not be read (NOT zero). */\n todayUsd: number | null;\n /** Calendar day the counter applies to; null before the first tick. */\n todayDate: string | null;\n dailyBudgetUsd: number;\n}\n\n/**\n * GET /api/verse/control — the ONE aggregate the Autonomy view reads.\n * Every field is a projection of an existing snapshot; nothing is recomputed.\n */\nexport interface VerseControlSnapshot {\n generatedAt: string;\n daemon: PublicDaemonObservation;\n fleet: VerseFleetEssentials;\n caps: VerseCaps;\n scope: VerseScope;\n killSwitch: VerseKillSwitch;\n /**\n * The DAEMON-SCOPED pause, as its own field. A cockpit that folded this into\n * `killSwitch` would be back to one control with two names; keeping them\n * separate is what lets the Autonomy view render \"paused\" and \"kill switch\n * engaged\" as the different situations they are.\n */\n pause: VerseDaemonPause;\n /** Pending inbox proposals awaiting a human decision. */\n pendingApprovals: number;\n spend: VerseSpend;\n /** Per-engine quota usage against the configured foundry limits. */\n quota: FrontierEngineUsage[];\n /** False when this server was started without --allow-dispatch: all POSTs 404. */\n dispatchEnabled: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Safety (GET /api/verse/safety)\n// ---------------------------------------------------------------------------\n\n/** GET /api/verse/safety — the `ashlr verify-safety --json` report, unmodified. */\nexport type VerseSafetyReport = SafetyReport;\n\n// ---------------------------------------------------------------------------\n// Bounds (single source of truth — the API validator and the UI share these)\n// ---------------------------------------------------------------------------\n\nexport const VERSE_CAPS_BOUNDS = {\n dailyBudgetUsd: { min: 0, max: 1_000 },\n perTickItems: { min: 1, max: 50 },\n parallel: { min: 1, max: 16 },\n intervalMs: { min: 30_000, max: 86_400_000 },\n maxConcurrent: { min: 1, max: 32 },\n /**\n * DELIBERATE DEVIATION from VERSE-CONTRACT-V2's \"concurrency 0-32 each\".\n *\n * The daemon cannot honour a zero tier. `resolveCfg` (daemon/loop.ts) only\n * adopts a tier when it is `> 0`, and `TieredPool`'s constructor floors\n * every cap at `Math.max(1, …)` on top of that — so a stored 0 came back as\n * the built-in 2/6/8 while the cockpit displayed 0. Teaching the pool to\n * accept 0 is not a fix either: `tieredBounded` would then never start a\n * task on that tier and never resolve, i.e. a hung tick.\n *\n * A floor of 1 is therefore the only value that is true in both places. To\n * stop dispatch entirely, the honest lever is `dailyBudgetUsd: 0`, which the\n * daemon now really does obey.\n */\n concurrency: { min: 1, max: 32 },\n subscriptionMaxPercent: { min: 1, max: 100 },\n foundryLimitMax: { min: 0, max: 1_000_000 },\n} as const;\n\n/** Hard ceiling on GET /api/verse/audit `limit`. */\nexport const VERSE_AUDIT_MAX_LIMIT = 500;\n/** Default `limit` when the query omits one. */\nexport const VERSE_AUDIT_DEFAULT_LIMIT = 100;\n/**\n * Hard ceiling on how many raw audit entries a single request will read before\n * filtering. Bounds the work a filtered query can ask of the disk.\n */\nexport const VERSE_AUDIT_MAX_SCAN = 5_000;\n\n/** The kill switch's blast-radius warning — one string, used everywhere. */\nexport const VERSE_KILL_SWITCH_NOTE =\n 'Emergency stop: the global kill switch also refuses the agent\\'s own write tools, not just the daemon. Use the daemon pause action for an ordinary, reversible halt.';\n\n/**\n * The pause's blast-radius statement — the counterpart to\n * VERSE_KILL_SWITCH_NOTE, shipped with every pause projection so no surface\n * can show the state without the scope. Says what it does NOT do, because\n * that is the entire difference from the control next to it.\n */\nexport const VERSE_DAEMON_PAUSE_NOTE =\n 'Pause: halts autonomous dispatch only. Your own write tools keep working, nothing in flight is rolled back, and Resume restores the loop without a restart.';\n","/**\n * routes/verse/autonomy/control-queries.ts — every `/api/verse/{control,caps,\n * scope,audit,daemon,safety}` call the Autonomy cockpit makes, plus the two\n * existing read-only feeds it borrows (`/api/goals`, `/api/backlog`).\n *\n * Same contract as routes/verse/verse-queries.ts: reads are QueryDefs consumed\n * through useQuery (cache-backed, so a background refresh never blanks a\n * populated panel); writes pull the held mutation token from auth-store, touch\n * the hold on success, and invalidate exactly the keys they affect.\n *\n * Two deliberate choices worth knowing before editing:\n *\n * 1. **The envelopes are now fixed, so nothing is normalized.**\n * `/api/verse/scope` and `/api/verse/audit` are declared in\n * `src/core/verse/control-types.ts` (`VerseScope`, `VerseAuditResponse`)\n * and the handlers serialize exactly those, so these fetchers are typed\n * straight through. The speculative bare-array/wrapper normalization that\n * let this surface ship before the routes existed has been deleted.\n * `/api/backlog` keeps its normalizer — that route really does answer\n * `loadBacklog() ?? null` and is not part of the V2 contract.\n * 2. **Emergency stop is not a route of its own.** The global kill switch is\n * `POST /api/fleet/pause` (and `/resume` to clear), already wrapped in\n * data/mutations.ts. It is re-exported here under honest names so no call\n * site in this folder can accidentally call it \"pause\".\n */\nimport { VERSE_AUDIT_MAX_LIMIT } from './control-types.js';\nimport type { GoalSummary, QueryDef } from '../../../data/queries.js';\nimport { getMutationToken, touchMutationHold } from '../../../data/auth-store.js';\nimport { apiGet, apiPost } from '../../../data/client.js';\nimport { invalidate } from '../../../data/cache.js';\nimport { pauseFleet, resumeFleet } from '../../../data/mutations.js';\nimport type {\n VerseAuditFilters,\n VerseAuditPage,\n VerseCaps,\n VerseCapsApplyResult,\n VerseCapsPatch,\n VerseControlSnapshot,\n VerseDaemonAction,\n VerseDaemonActionResult,\n VerseSafetyReport,\n VerseScope,\n VerseScopePatch,\n} from './control-types.js';\n\nexport const VERSE_CONTROL_KEY = 'verse-control';\nexport const VERSE_CAPS_KEY = 'verse-caps';\nexport const VERSE_SCOPE_KEY = 'verse-scope';\nexport const VERSE_SAFETY_KEY = 'verse-safety';\nexport const VERSE_AUDIT_PREFIX = 'verse-audit:';\n\n/**\n * Server-side cap on `/api/verse/audit`, re-exported from the backend\n * contract module so the client cannot drift from the value the route\n * actually clamps to.\n */\nexport const VERSE_AUDIT_MAX = VERSE_AUDIT_MAX_LIMIT;\n/** Enough rows that the table is the real \"while I was asleep\" view. */\nexport const VERSE_AUDIT_DEFAULT_LIMIT = 200;\n\nexport const verseControlQuery: QueryDef<VerseControlSnapshot> = {\n key: VERSE_CONTROL_KEY,\n fetch: (signal) => apiGet<VerseControlSnapshot>('/api/verse/control', signal),\n};\n\nexport const verseCapsQuery: QueryDef<VerseCaps> = {\n key: VERSE_CAPS_KEY,\n fetch: (signal) => apiGet<VerseCaps>('/api/verse/caps', signal),\n};\n\nexport const verseSafetyQuery: QueryDef<VerseSafetyReport> = {\n key: VERSE_SAFETY_KEY,\n fetch: (signal) => apiGet<VerseSafetyReport>('/api/verse/safety', signal),\n};\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nexport const verseScopeQuery: QueryDef<VerseScope> = {\n key: VERSE_SCOPE_KEY,\n fetch: (signal) => apiGet<VerseScope>('/api/verse/scope', signal),\n};\n\nexport function verseAuditQuery(filters: VerseAuditFilters = {}): QueryDef<VerseAuditPage> {\n const limit = Math.min(filters.limit ?? VERSE_AUDIT_DEFAULT_LIMIT, VERSE_AUDIT_MAX);\n const params = new URLSearchParams();\n params.set('limit', String(limit));\n if (filters.action) params.set('action', filters.action);\n if (filters.result) params.set('result', filters.result);\n return {\n key: `${VERSE_AUDIT_PREFIX}${limit}:${filters.action ?? ''}:${filters.result ?? ''}`,\n fetch: (signal) => apiGet<VerseAuditPage>(`/api/verse/audit?${params.toString()}`, signal),\n };\n}\n\n/**\n * Read-only in V2 — the contract is explicit that no goal-mutation routes get\n * built here. `/api/goals` returns a bare array; `/api/backlog` returns\n * `loadBacklog() ?? null`, i.e. `null` is \"no backlog on disk\", not an error.\n */\nexport const verseGoalsQuery: QueryDef<GoalSummary[]> = {\n key: 'goals',\n fetch: (signal) => apiGet<GoalSummary[]>('/api/goals', signal),\n};\n\n/** One backlog row, narrowed to what the read-only summary renders. */\nexport interface VerseBacklogItem {\n id?: string;\n title?: string;\n repo?: string;\n score?: number;\n source?: string;\n}\n\nexport interface VerseBacklogSummary {\n items: VerseBacklogItem[];\n /** True when the server answered `null` — no backlog has been built yet. */\n absent: boolean;\n}\n\nexport function normalizeBacklog(raw: unknown): VerseBacklogSummary {\n if (raw === null || raw === undefined) return { items: [], absent: true };\n if (Array.isArray(raw)) return { items: raw as VerseBacklogItem[], absent: false };\n if (isRecord(raw) && Array.isArray(raw['items'])) {\n return { items: raw['items'] as VerseBacklogItem[], absent: false };\n }\n return { items: [], absent: true };\n}\n\nexport const verseBacklogQuery: QueryDef<VerseBacklogSummary> = {\n key: 'backlog',\n fetch: (signal) => apiGet<unknown>('/api/backlog', signal).then(normalizeBacklog),\n};\n\n// ---------------------------------------------------------------------------\n// Writes\n// ---------------------------------------------------------------------------\n\n/** Thrown before any network call when no mutation token is held. */\nexport class VerseControlLockedError extends Error {\n constructor() {\n super('Unlock actions with the mutation token before changing the loop.');\n this.name = 'VerseControlLockedError';\n }\n}\n\nasync function post<T>(path: string, body: unknown): Promise<T> {\n const token = getMutationToken();\n if (!token) throw new VerseControlLockedError();\n const result = await apiPost<T>(path, body, token);\n touchMutationHold();\n return result;\n}\n\n/** Every panel in the cockpit reads through one of these three. */\nexport function invalidateControl(): void {\n invalidate(VERSE_CONTROL_KEY);\n}\n\nexport async function updateVerseCaps(patch: VerseCapsPatch): Promise<VerseCapsApplyResult> {\n const result = await post<VerseCapsApplyResult>('/api/verse/caps', patch);\n invalidate(VERSE_CAPS_KEY);\n invalidateControl();\n return result;\n}\n\nexport async function updateVerseScope(patch: VerseScopePatch): Promise<void> {\n await post<unknown>('/api/verse/scope', patch);\n invalidate(VERSE_SCOPE_KEY);\n invalidateControl();\n}\n\nexport async function runDaemonAction(action: VerseDaemonAction): Promise<VerseDaemonActionResult> {\n const result = await post<VerseDaemonActionResult>('/api/verse/daemon', { action });\n invalidateControl();\n invalidate('daemon');\n return result;\n}\n\n/**\n * ENGAGE the global kill switch (`~/.ashlr/KILL`). This is `POST\n * /api/fleet/pause` under its honest name: it stops the daemon AND refuses\n * the agent's own mcp-native write tools. Never surface it as \"pause\".\n */\nexport async function engageEmergencyStop(): Promise<void> {\n await pauseFleet();\n invalidateControl();\n}\n\n/** Clear the kill switch — writes become possible again fleet-wide. */\nexport async function releaseEmergencyStop(): Promise<void> {\n await resumeFleet();\n invalidateControl();\n}\n"],"mappings":"yHAmYA,IAAa,EAAoB,CAC/B,eAAgB,CAAE,IAAK,EAAG,IAAK,GAAM,EACrC,aAAc,CAAE,IAAK,EAAG,IAAK,EAAG,EAChC,SAAU,CAAE,IAAK,EAAG,IAAK,EAAG,EAC5B,WAAY,CAAE,IAAK,IAAQ,IAAK,KAAW,EAC3C,cAAe,CAAE,IAAK,EAAG,IAAK,EAAG,EAejC,YAAa,CAAE,IAAK,EAAG,IAAK,EAAG,EAC/B,uBAAwB,CAAE,IAAK,EAAG,IAAK,GAAI,EAC3C,gBAAiB,CAAE,IAAK,EAAG,IAAK,GAAU,CAC5C,EC7Wa,EAAoB,gBACpB,EAAiB,aACjB,EAAkB,cAClB,EAAmB,eACnB,EAAqB,eAOrB,EAAA,IAIA,EAAoD,CAC/D,IAAK,EACL,MAAQ,GAAW,EAA6B,qBAAsB,CAAM,CAC9E,EAEa,EAAsC,CACjD,IAAK,EACL,MAAQ,GAAW,EAAkB,kBAAmB,CAAM,CAChE,EAEa,EAAgD,CAC3D,IAAK,EACL,MAAQ,GAAW,EAA0B,oBAAqB,CAAM,CAC1E,EAEA,SAAS,EAAS,EAAkD,CAClE,OAAO,OAAO,GAAU,YAAY,GAAkB,CAAC,MAAM,QAAQ,CAAK,CAC5E,CAEA,IAAa,EAAwC,CACnD,IAAK,EACL,MAAQ,GAAW,EAAmB,mBAAoB,CAAM,CAClE,EAEA,SAAgB,EAAgB,EAA6B,CAAC,EAA6B,CACzF,IAAM,EAAQ,KAAK,IAAI,EAAQ,OAAA,IAAoC,CAAe,EAC5E,EAAS,IAAI,gBAInB,OAHA,EAAO,IAAI,QAAS,OAAO,CAAK,CAAC,EAC7B,EAAQ,QAAQ,EAAO,IAAI,SAAU,EAAQ,MAAM,EACnD,EAAQ,QAAQ,EAAO,IAAI,SAAU,EAAQ,MAAM,EAChD,CACL,IAAK,GAAG,IAAqB,EAAM,GAAG,EAAQ,QAAU,GAAG,GAAG,EAAQ,QAAU,KAChF,MAAQ,GAAW,EAAuB,oBAAoB,EAAO,SAAS,IAAK,CAAM,CAC3F,CACF,CAOA,IAAa,EAA2C,CACtD,IAAK,QACL,MAAQ,GAAW,EAAsB,aAAc,CAAM,CAC/D,EAiBA,SAAgB,EAAiB,EAAmC,CAMlE,OALI,GAAQ,KAAkC,CAAE,MAAO,CAAC,EAAG,OAAQ,EAAK,EACpE,MAAM,QAAQ,CAAG,EAAU,CAAE,MAAO,EAA2B,OAAQ,EAAM,EAC7E,EAAS,CAAG,GAAK,MAAM,QAAQ,EAAI,KAAQ,EACtC,CAAE,MAAO,EAAI,MAAgC,OAAQ,EAAM,EAE7D,CAAE,MAAO,CAAC,EAAG,OAAQ,EAAK,CACnC,CAEA,IAAa,EAAmD,CAC9D,IAAK,UACL,MAAQ,GAAW,EAAgB,eAAgB,CAAM,CAAC,CAAC,KAAK,CAAgB,CAClF,EAOa,EAAb,cAA6C,KAAM,CACjD,aAAc,CACZ,MAAM,kEAAkE,EACxE,KAAK,KAAO,yBACd,CACF,EAEA,eAAe,EAAQ,EAAc,EAA2B,CAC9D,IAAM,EAAQ,EAAiB,EAC/B,GAAI,CAAC,EAAO,MAAM,IAAI,EACtB,IAAM,EAAS,MAAM,EAAW,EAAM,EAAM,CAAK,EAEjD,OADA,EAAkB,EACX,CACT,CAGA,SAAgB,GAA0B,CACxC,EAAW,CAAiB,CAC9B,CAEA,eAAsB,EAAgB,EAAsD,CAC1F,IAAM,EAAS,MAAM,EAA2B,kBAAmB,CAAK,EAGxE,OAFA,EAAW,CAAc,EACzB,EAAkB,EACX,CACT,CAEA,eAAsB,EAAiB,EAAuC,CAC5E,MAAM,EAAc,mBAAoB,CAAK,EAC7C,EAAW,CAAe,EAC1B,EAAkB,CACpB,CAEA,eAAsB,EAAgB,EAA6D,CACjG,IAAM,EAAS,MAAM,EAA8B,oBAAqB,CAAE,QAAO,CAAC,EAGlF,OAFA,EAAkB,EAClB,EAAW,QAAQ,EACZ,CACT,CAOA,eAAsB,GAAqC,CACzD,MAAM,EAAW,EACjB,EAAkB,CACpB,CAGA,eAAsB,GAAsC,CAC1D,MAAM,EAAY,EAClB,EAAkB,CACpB"}
|
|
1
|
+
{"version":3,"file":"control-queries-C4TG8blN.js","names":[],"sources":["../../../../../../src/core/verse/control-types.ts","../../../../../../src/web-ui/routes/verse/autonomy/control-queries.ts"],"sourcesContent":["/**\n * Ashlr Verse — autonomy control-plane contract (V2).\n *\n * V1 (`./types.ts`) is frozen: it describes interactive chat sessions. This\n * module adds the shapes the Autonomy cockpit needs to run the hub's existing\n * autonomous fleet without a terminal — the CONFIGURED caps, the enrollment\n * scope, the audit trail, the kill switch, and the daemon lifecycle.\n *\n * Two hard rules are encoded here rather than left to the UI:\n *\n * 1. The global kill switch (`~/.ashlr/KILL`) is an EMERGENCY STOP, not a\n * pause. It also refuses the agent's own `mcp-native` write tools. Every\n * shape that carries it is named `killSwitch`, and the action that engages\n * it is named `emergencyStop`. It is never called \"pause\".\n *\n * V2.1 adds the control that name was being withheld from: the\n * DAEMON-SCOPED pause (`~/.ashlr/daemon.paused`, see core/daemon/pause.ts).\n * It halts autonomous dispatch and NOTHING else — `assertMayMutate`,\n * `mcp-native`, and `mcp-native-engineer` do not read it. It travels as\n * `pause: VerseDaemonPause`, always a FIELD OF ITS OWN, never merged into\n * `killSwitch`, because the whole point is that the two have different\n * blast radii and the operator must be able to see which one is on.\n * 2. Nothing in this module is a secret. Launcher commands, tokens, and env\n * values never appear in any of these shapes — the daemon lifecycle result\n * carries a pid, never an argv.\n *\n * Everything is a projection of state the hub already computes; nothing here\n * introduces a new source of truth.\n */\n\nimport type { AuditEntry } from '../types.js';\nimport type { PublicDaemonObservation } from '../daemon/public-observation.js';\nimport type { FrontierEngineUsage } from '../usage/frontier-usage.js';\nimport type { SafetyReport } from '../../cli/verify-safety.js';\n\n// ---------------------------------------------------------------------------\n// Errors\n// ---------------------------------------------------------------------------\n\n/**\n * Error codes for `/api/verse/{control,caps,scope,audit,daemon,safety}`.\n * `VERSE_INVALID` / `VERSE_TOO_LARGE` are deliberately the SAME codes V1 uses,\n * so one client error mapper covers both halves of the Verse API.\n */\nexport type VerseControlErrorCode =\n | 'VERSE_INVALID'\n | 'VERSE_REFUSED'\n | 'VERSE_TOO_LARGE'\n | 'VERSE_UNAVAILABLE';\n\n/** Every error body on these routes. */\nexport interface VerseControlError {\n error: string;\n code: VerseControlErrorCode;\n}\n\n// ---------------------------------------------------------------------------\n// Caps — the CONFIGURED limits (GET/POST /api/verse/caps)\n// ---------------------------------------------------------------------------\n\n/** One `cfg.foundry.limits[engine]` entry, flattened for transport. */\nexport interface VerseFoundryLimit {\n /** EngineId key, e.g. 'claude' | 'codex'. */\n engine: string;\n /** Rolling window label, e.g. '1m' | '5m' | '1h' | '1d'. */\n window: string;\n /** Max dispatches per window. 0 means \"no dispatches allowed\". */\n max: number;\n}\n\n/** `cfg.daemon.concurrency` — null per tier means \"not configured, using the default\". */\nexport interface VerseCapsConcurrency {\n local: number | null;\n cloud: number | null;\n total: number | null;\n}\n\n/**\n * The configured autonomy limits. These are the values the daemon actually\n * reads — no route exposed them before V2, which is why the cockpit could not\n * show a cap next to the usage burning against it.\n */\nexport interface VerseCaps {\n /** HARD daily spend ceiling (USD). 0 means the daemon is stopped by budget. */\n dailyBudgetUsd: number;\n /** Backlog items processed per tick. */\n perTickItems: number;\n /** Concurrent sandboxed swarms per tick (batch mode). */\n parallel: number;\n /** Tick interval in loop mode (ms). */\n intervalMs: number;\n /** Daemon execution mode. */\n mode: 'batch' | 'continuous';\n /** Ceiling on in-flight dispatches in continuous mode; null when unset. */\n maxConcurrent: number | null;\n /** Per-tier concurrency budgets; null per tier when unset. */\n concurrency: VerseCapsConcurrency;\n /** Subscription-window throttle percentage (1–100). */\n subscriptionMaxPercent: number;\n /** Per-engine dispatch limits, sorted by engine id. */\n foundryLimits: VerseFoundryLimit[];\n /**\n * HONEST STATE: the cap keys whose value is a built-in default rather than\n * something configured on disk. The UI renders these as \"default\", never as\n * a deliberate choice the operator made. Sorted, stable.\n */\n defaulted: VerseCapKey[];\n}\n\n/** The scalar cap keys a client may update. `foundryLimits` is handled separately. */\nexport type VerseCapKey =\n | 'dailyBudgetUsd'\n | 'perTickItems'\n | 'parallel'\n | 'intervalMs'\n | 'mode'\n | 'maxConcurrent'\n | 'concurrency'\n | 'subscriptionMaxPercent'\n | 'foundryLimits';\n\n/** POST /api/verse/caps — a partial update. Unknown keys are rejected. */\nexport interface VerseCapsUpdate {\n dailyBudgetUsd?: number;\n perTickItems?: number;\n parallel?: number;\n intervalMs?: number;\n mode?: 'batch' | 'continuous';\n maxConcurrent?: number;\n concurrency?: { local?: number; cloud?: number; total?: number };\n subscriptionMaxPercent?: number;\n /** Replaces the named engines' limits; engines not listed are left alone. */\n foundryLimits?: VerseFoundryLimit[];\n}\n\n/**\n * POST /api/verse/caps result. `live: true` is not decoration — the daemon\n * re-reads config at the top of every tick, so a saved cap binds on the next\n * tick without a restart.\n */\nexport interface VerseCapsUpdateResult {\n ok: true;\n /** The cap keys this request actually changed, in request order. */\n applied: VerseCapKey[];\n live: true;\n /** The full caps AFTER the write, re-read from disk. */\n caps: VerseCaps;\n}\n\n// ---------------------------------------------------------------------------\n// Scope — the enrollment registry (GET/POST /api/verse/scope)\n// ---------------------------------------------------------------------------\n\nexport interface VerseScopeRepo {\n path: string;\n name: string;\n /** False when the registry still lists a directory that is gone. */\n exists: boolean;\n}\n\n/**\n * GET /api/verse/scope. An EMPTY `repos` with no `degradedReason` is the\n * valid default state and means the daemon will do nothing at all — the UI\n * must say that plainly rather than rendering an innocuous empty list.\n */\nexport interface VerseScope {\n repos: VerseScopeRepo[];\n /** Present only when enrollment authority could not be read at all. */\n degradedReason?: string;\n}\n\nexport type VerseScopeAction = 'enroll' | 'unenroll';\n\n/** POST /api/verse/scope body. */\nexport interface VerseScopeRequest {\n action: VerseScopeAction;\n path: string;\n}\n\nexport interface VerseScopeResult {\n ok: boolean;\n action: VerseScopeAction;\n /** The canonical (realpath'd) registry path this acted on. */\n path: string;\n /** False when the registry already had the requested state (idempotent). */\n changed: boolean;\n /** Policy reason string, e.g. 'enrolled' | 'already-unenrolled'. */\n reason: string;\n /** The scope AFTER the mutation. */\n scope: VerseScope;\n}\n\n// ---------------------------------------------------------------------------\n// Audit (GET /api/verse/audit)\n// ---------------------------------------------------------------------------\n\nexport type VerseAuditResult = AuditEntry['result'];\n\n/** GET /api/verse/audit?limit=&action=&result= */\nexport interface VerseAuditResponse {\n /** Newest first, already filtered, at most `limit` entries. */\n entries: AuditEntry[];\n /** The effective cap applied to `entries` (never above 500). */\n limit: number;\n /** Echo of the applied filter so the UI cannot drift from what it shows. */\n filter: { action: string | null; result: VerseAuditResult | null };\n /** How many raw entries were scanned before filtering. */\n scanned: number;\n /**\n * True when the scan filled its own ceiling, so older entries MAY exist\n * beyond what was read. Conservative by design: it never claims the reader\n * reached the end of history when it cannot prove that.\n */\n truncated: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Kill switch\n// ---------------------------------------------------------------------------\n\n/**\n * The GLOBAL kill switch (`~/.ashlr/KILL`). `state: 'unknown'` is a real,\n * distinct outcome — the sentinel was uninspectable — and must never be\n * rendered as \"off\".\n */\nexport interface VerseKillSwitch {\n state: 'active' | 'inactive' | 'unknown';\n sourceState: 'healthy' | 'degraded';\n reason: string;\n /**\n * Plain-language statement of blast radius, shipped with the state so no\n * surface can show the switch without the warning.\n */\n note: string;\n}\n\n// ---------------------------------------------------------------------------\n// Daemon pause — the NARROW stop (POST /api/verse/daemon {action:'pause'})\n// ---------------------------------------------------------------------------\n\n/**\n * The DAEMON-SCOPED pause sentinel (`~/.ashlr/daemon.paused`), projected for\n * transport. Shares `VerseKillSwitch`'s shape on purpose — both are sentinels\n * read the same way, and both keep `'unknown'` as a real third state that must\n * never render as \"off\".\n *\n * `state: 'unknown'` is treated by the daemon as PAUSED (fail safe), so the UI\n * must say \"paused — the sentinel could not be read\", not \"running\".\n *\n * The sentinel PATH is deliberately absent: it is machine-local detail the\n * cockpit has no use for, the same omission `VerseKillSwitch` makes.\n */\nexport interface VerseDaemonPause {\n /** 'paused' = dispatch halted. 'running' = proven not paused. */\n state: 'paused' | 'running' | 'unknown';\n sourceState: 'healthy' | 'degraded';\n reason: string;\n /** ISO time the pause was requested; null when not paused or unknown. */\n pausedAt: string | null;\n /** Which surface paused it ('cli' | 'verse-control-plane' | 'unknown'). */\n by: string | null;\n /** Plain-language statement of the NARROW blast radius, shipped with the state. */\n note: string;\n}\n\n// ---------------------------------------------------------------------------\n// Daemon lifecycle (POST /api/verse/daemon)\n// ---------------------------------------------------------------------------\n\n/**\n * 'start' launches the autonomous loop detached (exactly `ashlr daemon start`).\n * 'once' runs a single tick the same way.\n *\n * Three stops, three honestly different scopes:\n * - 'pause' — halts autonomous DISPATCH only. Reversible with 'resume'.\n * Leaves the agent's own write tools alone. The default choice.\n * - 'stop' — the ordinary stop, which today engages the GLOBAL kill switch\n * (`stopDaemon()` is `setKill(true)`), so it is wider than a\n * pause and the result's `note` says so.\n * - emergency stop — not on this route at all (`POST /api/fleet/pause`).\n */\nexport type VerseDaemonAction = 'start' | 'stop' | 'once' | 'pause' | 'resume';\n\n/** Non-secret projection of `~/.ashlr/daemon.json`. */\nexport interface VerseDaemonStateProjection {\n running: boolean;\n pid: number | null;\n startedAt: string | null;\n lastTickAt: string | null;\n todayDate: string | null;\n todaySpentUsd: number;\n itemsProcessed: number;\n}\n\n/**\n * POST /api/verse/daemon result. `pid` is the only thing that ever escapes the\n * spawn — the argv, the env, and the resolved launcher path never do.\n */\nexport interface VerseDaemonActionResult {\n ok: boolean;\n action: VerseDaemonAction;\n /** True when a detached child was launched by this request. */\n spawned: boolean;\n /** The detached child's pid for 'start'/'once'; null otherwise. */\n pid: number | null;\n state: VerseDaemonStateProjection;\n killSwitch: VerseKillSwitch;\n /** The daemon-scoped pause AFTER this action. Never folded into killSwitch. */\n pause: VerseDaemonPause;\n /** One line of plain language about what just happened, for the UI to echo. */\n note: string;\n}\n\n// ---------------------------------------------------------------------------\n// Fleet essentials + control aggregate (GET /api/verse/control)\n// ---------------------------------------------------------------------------\n\n/**\n * The small honest slice of FleetStatus/ControlSnapshot the cockpit needs.\n * Deliberately excludes service file paths and error-log paths: the cockpit\n * does not need them, and keeping them out keeps this payload trivially\n * provable as free of machine-local command detail.\n */\nexport interface VerseFleetEssentials {\n /** Mode the autonomy control loop last directed, when known. */\n directionMode: string | null;\n directionAt: string | null;\n directionReason: string | null;\n autonomyControlLoop: boolean;\n autonomyControlMode: string;\n service: {\n registrationState: 'present' | 'absent' | 'unknown';\n installed: boolean;\n running: boolean;\n runtimeState: string | null;\n };\n /** Freshness of the shared fleet-status cache backing these values. */\n freshness: { stale: boolean; ageMs: number };\n}\n\n/** Today's spend against the configured ceiling. */\nexport interface VerseSpend {\n /** null when the daemon ledger could not be read (NOT zero). */\n todayUsd: number | null;\n /** Calendar day the counter applies to; null before the first tick. */\n todayDate: string | null;\n dailyBudgetUsd: number;\n}\n\n/**\n * GET /api/verse/control — the ONE aggregate the Autonomy view reads.\n * Every field is a projection of an existing snapshot; nothing is recomputed.\n */\nexport interface VerseControlSnapshot {\n generatedAt: string;\n daemon: PublicDaemonObservation;\n fleet: VerseFleetEssentials;\n caps: VerseCaps;\n scope: VerseScope;\n killSwitch: VerseKillSwitch;\n /**\n * The DAEMON-SCOPED pause, as its own field. A cockpit that folded this into\n * `killSwitch` would be back to one control with two names; keeping them\n * separate is what lets the Autonomy view render \"paused\" and \"kill switch\n * engaged\" as the different situations they are.\n */\n pause: VerseDaemonPause;\n /** Pending inbox proposals awaiting a human decision. */\n pendingApprovals: number;\n spend: VerseSpend;\n /** Per-engine quota usage against the configured foundry limits. */\n quota: FrontierEngineUsage[];\n /** False when this server was started without --allow-dispatch: all POSTs 404. */\n dispatchEnabled: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Safety (GET /api/verse/safety)\n// ---------------------------------------------------------------------------\n\n/** GET /api/verse/safety — the `ashlr verify-safety --json` report, unmodified. */\nexport type VerseSafetyReport = SafetyReport;\n\n// ---------------------------------------------------------------------------\n// Bounds (single source of truth — the API validator and the UI share these)\n// ---------------------------------------------------------------------------\n\nexport const VERSE_CAPS_BOUNDS = {\n dailyBudgetUsd: { min: 0, max: 1_000 },\n perTickItems: { min: 1, max: 50 },\n parallel: { min: 1, max: 16 },\n intervalMs: { min: 30_000, max: 86_400_000 },\n maxConcurrent: { min: 1, max: 32 },\n /**\n * DELIBERATE DEVIATION from VERSE-CONTRACT-V2's \"concurrency 0-32 each\".\n *\n * The daemon cannot honour a zero tier. `resolveCfg` (daemon/loop.ts) only\n * adopts a tier when it is `> 0`, and `TieredPool`'s constructor floors\n * every cap at `Math.max(1, …)` on top of that — so a stored 0 came back as\n * the built-in 2/6/8 while the cockpit displayed 0. Teaching the pool to\n * accept 0 is not a fix either: `tieredBounded` would then never start a\n * task on that tier and never resolve, i.e. a hung tick.\n *\n * A floor of 1 is therefore the only value that is true in both places. To\n * stop dispatch entirely, the honest lever is `dailyBudgetUsd: 0`, which the\n * daemon now really does obey.\n */\n concurrency: { min: 1, max: 32 },\n subscriptionMaxPercent: { min: 1, max: 100 },\n foundryLimitMax: { min: 0, max: 1_000_000 },\n} as const;\n\n/** Hard ceiling on GET /api/verse/audit `limit`. */\nexport const VERSE_AUDIT_MAX_LIMIT = 500;\n/** Default `limit` when the query omits one. */\nexport const VERSE_AUDIT_DEFAULT_LIMIT = 100;\n/**\n * Hard ceiling on how many raw audit entries a single request will read before\n * filtering. Bounds the work a filtered query can ask of the disk.\n */\nexport const VERSE_AUDIT_MAX_SCAN = 5_000;\n\n/** The kill switch's blast-radius warning — one string, used everywhere. */\nexport const VERSE_KILL_SWITCH_NOTE =\n 'Emergency stop: the global kill switch also refuses the agent\\'s own write tools, not just the daemon. Use the daemon pause action for an ordinary, reversible halt.';\n\n/**\n * The pause's blast-radius statement — the counterpart to\n * VERSE_KILL_SWITCH_NOTE, shipped with every pause projection so no surface\n * can show the state without the scope. Says what it does NOT do, because\n * that is the entire difference from the control next to it.\n */\nexport const VERSE_DAEMON_PAUSE_NOTE =\n 'Pause: halts autonomous dispatch only. Your own write tools keep working, nothing in flight is rolled back, and Resume restores the loop without a restart.';\n","/**\n * routes/verse/autonomy/control-queries.ts — every `/api/verse/{control,caps,\n * scope,audit,daemon,safety}` call the Autonomy cockpit makes, plus the two\n * existing read-only feeds it borrows (`/api/goals`, `/api/backlog`).\n *\n * Same contract as routes/verse/verse-queries.ts: reads are QueryDefs consumed\n * through useQuery (cache-backed, so a background refresh never blanks a\n * populated panel); writes pull the held mutation token from auth-store, touch\n * the hold on success, and invalidate exactly the keys they affect.\n *\n * Two deliberate choices worth knowing before editing:\n *\n * 1. **The envelopes are now fixed, so nothing is normalized.**\n * `/api/verse/scope` and `/api/verse/audit` are declared in\n * `src/core/verse/control-types.ts` (`VerseScope`, `VerseAuditResponse`)\n * and the handlers serialize exactly those, so these fetchers are typed\n * straight through. The speculative bare-array/wrapper normalization that\n * let this surface ship before the routes existed has been deleted.\n * `/api/backlog` keeps its normalizer — that route really does answer\n * `loadBacklog() ?? null` and is not part of the V2 contract.\n * 2. **Emergency stop is not a route of its own.** The global kill switch is\n * `POST /api/fleet/pause` (and `/resume` to clear), already wrapped in\n * data/mutations.ts. It is re-exported here under honest names so no call\n * site in this folder can accidentally call it \"pause\".\n */\nimport { VERSE_AUDIT_MAX_LIMIT } from './control-types.js';\nimport type { GoalSummary, QueryDef } from '../../../data/queries.js';\nimport { getMutationToken, touchMutationHold } from '../../../data/auth-store.js';\nimport { apiGet, apiPost } from '../../../data/client.js';\nimport { invalidate } from '../../../data/cache.js';\nimport { pauseFleet, resumeFleet } from '../../../data/mutations.js';\nimport type {\n VerseAuditFilters,\n VerseAuditPage,\n VerseCaps,\n VerseCapsApplyResult,\n VerseCapsPatch,\n VerseControlSnapshot,\n VerseDaemonAction,\n VerseDaemonActionResult,\n VerseSafetyReport,\n VerseScope,\n VerseScopePatch,\n} from './control-types.js';\n\nexport const VERSE_CONTROL_KEY = 'verse-control';\nexport const VERSE_CAPS_KEY = 'verse-caps';\nexport const VERSE_SCOPE_KEY = 'verse-scope';\nexport const VERSE_SAFETY_KEY = 'verse-safety';\nexport const VERSE_AUDIT_PREFIX = 'verse-audit:';\n\n/**\n * Server-side cap on `/api/verse/audit`, re-exported from the backend\n * contract module so the client cannot drift from the value the route\n * actually clamps to.\n */\nexport const VERSE_AUDIT_MAX = VERSE_AUDIT_MAX_LIMIT;\n/** Enough rows that the table is the real \"while I was asleep\" view. */\nexport const VERSE_AUDIT_DEFAULT_LIMIT = 200;\n\nexport const verseControlQuery: QueryDef<VerseControlSnapshot> = {\n key: VERSE_CONTROL_KEY,\n fetch: (signal) => apiGet<VerseControlSnapshot>('/api/verse/control', signal),\n};\n\nexport const verseCapsQuery: QueryDef<VerseCaps> = {\n key: VERSE_CAPS_KEY,\n fetch: (signal) => apiGet<VerseCaps>('/api/verse/caps', signal),\n};\n\nexport const verseSafetyQuery: QueryDef<VerseSafetyReport> = {\n key: VERSE_SAFETY_KEY,\n fetch: (signal) => apiGet<VerseSafetyReport>('/api/verse/safety', signal),\n};\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nexport const verseScopeQuery: QueryDef<VerseScope> = {\n key: VERSE_SCOPE_KEY,\n fetch: (signal) => apiGet<VerseScope>('/api/verse/scope', signal),\n};\n\nexport function verseAuditQuery(filters: VerseAuditFilters = {}): QueryDef<VerseAuditPage> {\n const limit = Math.min(filters.limit ?? VERSE_AUDIT_DEFAULT_LIMIT, VERSE_AUDIT_MAX);\n const params = new URLSearchParams();\n params.set('limit', String(limit));\n if (filters.action) params.set('action', filters.action);\n if (filters.result) params.set('result', filters.result);\n return {\n key: `${VERSE_AUDIT_PREFIX}${limit}:${filters.action ?? ''}:${filters.result ?? ''}`,\n fetch: (signal) => apiGet<VerseAuditPage>(`/api/verse/audit?${params.toString()}`, signal),\n };\n}\n\n/**\n * Read-only in V2 — the contract is explicit that no goal-mutation routes get\n * built here. `/api/goals` returns a bare array; `/api/backlog` returns\n * `loadBacklog() ?? null`, i.e. `null` is \"no backlog on disk\", not an error.\n */\nexport const verseGoalsQuery: QueryDef<GoalSummary[]> = {\n key: 'goals',\n fetch: (signal) => apiGet<GoalSummary[]>('/api/goals', signal),\n};\n\n/** One backlog row, narrowed to what the read-only summary renders. */\nexport interface VerseBacklogItem {\n id?: string;\n title?: string;\n repo?: string;\n score?: number;\n source?: string;\n}\n\nexport interface VerseBacklogSummary {\n items: VerseBacklogItem[];\n /** True when the server answered `null` — no backlog has been built yet. */\n absent: boolean;\n}\n\nexport function normalizeBacklog(raw: unknown): VerseBacklogSummary {\n if (raw === null || raw === undefined) return { items: [], absent: true };\n if (Array.isArray(raw)) return { items: raw as VerseBacklogItem[], absent: false };\n if (isRecord(raw) && Array.isArray(raw['items'])) {\n return { items: raw['items'] as VerseBacklogItem[], absent: false };\n }\n return { items: [], absent: true };\n}\n\nexport const verseBacklogQuery: QueryDef<VerseBacklogSummary> = {\n key: 'backlog',\n fetch: (signal) => apiGet<unknown>('/api/backlog', signal).then(normalizeBacklog),\n};\n\n// ---------------------------------------------------------------------------\n// Writes\n// ---------------------------------------------------------------------------\n\n/** Thrown before any network call when no mutation token is held. */\nexport class VerseControlLockedError extends Error {\n constructor() {\n super('Unlock actions with the mutation token before changing the loop.');\n this.name = 'VerseControlLockedError';\n }\n}\n\nasync function post<T>(path: string, body: unknown): Promise<T> {\n const token = getMutationToken();\n if (!token) throw new VerseControlLockedError();\n const result = await apiPost<T>(path, body, token);\n touchMutationHold();\n return result;\n}\n\n/** Every panel in the cockpit reads through one of these three. */\nexport function invalidateControl(): void {\n invalidate(VERSE_CONTROL_KEY);\n}\n\nexport async function updateVerseCaps(patch: VerseCapsPatch): Promise<VerseCapsApplyResult> {\n const result = await post<VerseCapsApplyResult>('/api/verse/caps', patch);\n invalidate(VERSE_CAPS_KEY);\n invalidateControl();\n return result;\n}\n\nexport async function updateVerseScope(patch: VerseScopePatch): Promise<void> {\n await post<unknown>('/api/verse/scope', patch);\n invalidate(VERSE_SCOPE_KEY);\n invalidateControl();\n}\n\nexport async function runDaemonAction(action: VerseDaemonAction): Promise<VerseDaemonActionResult> {\n const result = await post<VerseDaemonActionResult>('/api/verse/daemon', { action });\n invalidateControl();\n invalidate('daemon');\n return result;\n}\n\n/**\n * ENGAGE the global kill switch (`~/.ashlr/KILL`). This is `POST\n * /api/fleet/pause` under its honest name: it stops the daemon AND refuses\n * the agent's own mcp-native write tools. Never surface it as \"pause\".\n */\nexport async function engageEmergencyStop(): Promise<void> {\n await pauseFleet();\n invalidateControl();\n}\n\n/** Clear the kill switch — writes become possible again fleet-wide. */\nexport async function releaseEmergencyStop(): Promise<void> {\n await resumeFleet();\n invalidateControl();\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
function e(e){return e?e.darkSince===void 0?e.state===`dark`?e.lastActivityAt:null:e.darkSince:null}function t(e){let t=new Date(e);return`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,`0`)}-${String(t.getDate()).padStart(2,`0`)}`}function n(e){let t=Date.parse(e);return Number.isFinite(t)?new Date(t).toLocaleDateString(`en-US`,{month:`short`,day:`numeric`}):`an unknown day`}function r(e){let n=Date.parse(e);return Number.isFinite(n)?t(n):e}function i(t){let n=e(t),i=t.stateReason?.trim()||void 0;return n!==null&&Number.isFinite(Date.parse(n))?{kind:`dark`,since:r(n),detail:i}:i?{kind:`empty`,message:`Fleet dark. ${/[.!?…]$/.test(i)?i:`${i}.`}`}:{kind:`empty`,message:`Fleet dark.`}}function a(e){return e.kind===`dark`?{kind:`empty`,message:`No fleet runs or proposals since ${n(e.since)}.`}:e}export{a as i,e as n,i as r,n as t};
|
|
2
|
+
//# sourceMappingURL=dark-since-DOMvtB_G.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dark-since-DOMvtB_G.js","names":[],"sources":["../../../../../../src/web-ui/routes/verse/fleet/dark-since.ts"],"sourcesContent":["/**\n * routes/verse/fleet/dark-since.ts — the ONE \"Fleet dark since …\" (3.10.1).\n *\n * 3.10.0 said it three ways: Command and Fleet took the live snapshot's\n * `lastActivityAt` (\"Sep 1\"), Growth took fleet history's `darkSince` (\"Aug\n * 18\"), and Command fell back to history whenever the live read was not dark.\n * Those are two different facts:\n *\n * dark since — autonomy is shut (no standing grant, or no daemon) and\n * the fleet last showed any sign of life then. Served once,\n * as `FleetLiveSnapshotV1.darkSince` (core/verse/\n * fleet-live-api.ts `fleetDarkSince`). Only this value may be\n * worded \"Fleet dark since …\".\n * quiet since — fleet history's `darkSince`: the last run started or\n * proposal filed. Worded \"No fleet runs or proposals since …\"\n * (`quietSinceStatus`), never \"dark\".\n *\n * Dates are the viewer's LOCAL calendar day everywhere (\"Sep 1\"), so the\n * verdict line and the chart beside it can never name different days.\n *\n * Framework-free; tested directly.\n */\nimport type { FleetLiveSnapshotV1 } from '../../../../core/fleet/fleet-types.js';\nimport type { ChartStatus } from '../../../components/charts/ChartFrame.js';\n\n/**\n * The fleet's dark-since instant, or null when the fleet is not dark. A\n * pre-3.10.1 server sends no `darkSince`; the same rule is applied to what it\n * does send.\n */\nexport function fleetDarkSince(live: FleetLiveSnapshotV1 | null | undefined): string | null {\n if (!live) return null;\n if (live.darkSince !== undefined) return live.darkSince;\n return live.state === 'dark' ? live.lastActivityAt : null;\n}\n\n/** The viewer's local calendar day for an instant, as `YYYY-MM-DD`. */\nfunction localDay(ms: number): string {\n const d = new Date(ms);\n return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;\n}\n\n/** \"Sep 1\" — the viewer's local day for `iso`. */\nexport function darkSinceLabel(iso: string): string {\n const ms = Date.parse(iso);\n if (!Number.isFinite(ms)) return 'an unknown day';\n return new Date(ms).toLocaleDateString('en-US', { month: 'short', day: 'numeric' });\n}\n\n/**\n * `iso` as the viewer's local `YYYY-MM-DD`, for ChartFrame's dark state:\n * ChartFrame labels the day it is GIVEN (a bare ISO instant would be cut to\n * its UTC day, which an evening west of UTC turns into tomorrow).\n */\nexport function darkSinceDay(iso: string): string {\n const ms = Date.parse(iso);\n return Number.isFinite(ms) ? localDay(ms) : iso;\n}\n\n/**\n * The chart state for a DARK live fleet, from THE dark-since instant: \"Fleet\n * dark since Sep 1\" (the viewer's local day). When the server knows the fleet\n * is dark but not since when (`darkSince: null` — a fresh install, a cleared\n * ledger and journal), it says just \"Fleet dark.\", as Command's line does —\n * never \"since <today>\" from the snapshot's own `generatedAt`.\n */\nexport function fleetDarkStatus(live: FleetLiveSnapshotV1): ChartStatus {\n const since = fleetDarkSince(live);\n const detail = live.stateReason?.trim() || undefined;\n if (since !== null && Number.isFinite(Date.parse(since))) return { kind: 'dark', since: darkSinceDay(since), detail };\n if (!detail) return { kind: 'empty', message: 'Fleet dark.' };\n return { kind: 'empty', message: `Fleet dark. ${/[.!?…]$/.test(detail) ? detail : `${detail}.`}` };\n}\n\n/**\n * A chart status built over fleet HISTORY, re-worded: history's `darkSince`\n * is \"quiet since\" (the last run or proposal), so its dark state becomes a\n * plain \"No fleet runs or proposals since Aug 18.\" Every other status passes\n * through untouched.\n */\nexport function quietSinceStatus(status: ChartStatus): ChartStatus {\n if (status.kind !== 'dark') return status;\n return { kind: 'empty', message: `No fleet runs or proposals since ${darkSinceLabel(status.since)}.` };\n}\n"],"mappings":"AA8BA,SAAgB,EAAe,EAA6D,CAG1F,OAFK,EACD,EAAK,YAAc,IAAA,GAChB,EAAK,QAAU,OAAS,EAAK,eAAiB,KADZ,EAAK,UAD5B,IAGpB,CAGA,SAAS,EAAS,EAAoB,CACpC,IAAM,EAAI,IAAI,KAAK,CAAE,EACrB,MAAO,GAAG,EAAE,YAAY,EAAE,GAAG,OAAO,EAAE,SAAS,EAAI,CAAC,CAAC,CAAC,SAAS,EAAG,GAAG,EAAE,GAAG,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,SAAS,EAAG,GAAG,GAC/G,CAGA,SAAgB,EAAe,EAAqB,CAClD,IAAM,EAAK,KAAK,MAAM,CAAG,EAEzB,OADK,OAAO,SAAS,CAAE,EAChB,IAAI,KAAK,CAAE,CAAC,CAAC,mBAAmB,QAAS,CAAE,MAAO,QAAS,IAAK,SAAU,CAAC,EADjD,gBAEnC,CAOA,SAAgB,EAAa,EAAqB,CAChD,IAAM,EAAK,KAAK,MAAM,CAAG,EACzB,OAAO,OAAO,SAAS,CAAE,EAAI,EAAS,CAAE,EAAI,CAC9C,CASA,SAAgB,EAAgB,EAAwC,CACtE,IAAM,EAAQ,EAAe,CAAI,EAC3B,EAAS,EAAK,aAAa,KAAK,GAAK,IAAA,GAG3C,OAFI,IAAU,MAAQ,OAAO,SAAS,KAAK,MAAM,CAAK,CAAC,EAAU,CAAE,KAAM,OAAQ,MAAO,EAAa,CAAK,EAAG,QAAO,EAC/G,EACE,CAAE,KAAM,QAAS,QAAS,eAAe,UAAU,KAAK,CAAM,EAAI,EAAS,GAAG,EAAO,IAAK,EAD7E,CAAE,KAAM,QAAS,QAAS,aAAc,CAE9D,CAQA,SAAgB,EAAiB,EAAkC,CAEjE,OADI,EAAO,OAAS,OACb,CAAE,KAAM,QAAS,QAAS,oCAAoC,EAAe,EAAO,KAAK,EAAE,EAAG,EADlE,CAErC"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{a as e,n as t,s as n}from"./index-Dk8R-3a7.js";import{C as r}from"./verse-model-yHPwjf-7.js";import{a as i}from"./tool-semantics-GX8ScuSH.js";var a=n(e(),1),o=[],s=(0,a.createContext)(o);function c(e){return e}function l(e){return e.replace(/\\/g,`/`).replace(/\/+$/,``).replace(/^\/private(?=\/(?:tmp|var|etc)(?:\/|$))/,``).replace(/^[A-Z](?=:)/,e=>e.toLowerCase())}function u(e,t=o){let n=l(e),r=``;for(let e of t){let t=l(e);t.length>r.length&&!/^(?:[a-z]:)?$/.test(t)&&(n===t||n.startsWith(`${t}/`))&&(r=t)}let i=e.replace(/[\\/]+$/,``);if(r){let e=n.length-r.length-1;return e>0?i.slice(i.length-e):i.slice(Math.max(i.lastIndexOf(`/`),i.lastIndexOf(`\\`))+1)}if(!/^(?:[\\/]|[A-Za-z]:[\\/])/.test(e))return e.replace(/^\.[\\/]/,``);let a=e.includes(`/`)?`/`:`\\`,s=e.replace(/^[A-Za-z]:/,``).split(/[\\/]/).filter(Boolean);return s.length>4?`…${a}${s.slice(-3).join(a)}`:e}function d(e){let t=typeof e==`object`&&e?e:null;return t&&typeof t.command!=`string`&&typeof t.cmd!=`string`?i(t)[0]??null:null}function f(){let e=(0,a.useContext)(s);return(0,a.useCallback)(t=>u(t,e),[e])}var p={live:`_live_ytrey_7`,line:`_line_ytrey_14`,pulse:`_pulse_ytrey_24`,"live-pulse":`_live-pulse_ytrey_1`,text:`_text_ytrey_34`,word:`_word_ytrey_43`,sep:`_sep_ytrey_44`,figure:`_figure_ytrey_45`,detail:`_detail_ytrey_49`,stop:`_stop_ytrey_58`,stopIcon:`_stopIcon_ytrey_75`,notice:`_notice_ytrey_82`,noticeWord:`_noticeWord_ytrey_94`,noticeText:`_noticeText_ytrey_96`},m=t();function h(e,t){for(let t=e.length-1;t>=0;--t){let n=e[t];if(n.kind===`user`)break;if(n.kind===`tool`){if(n.result===null)return{phase:`tool`,detail:r(n.input)||n.name,path:d(n.input)};break}if(n.kind===`assistant`&&n.streaming)return{phase:`writing`,detail:null};if(n.kind===`assistant`||n.kind===`thinking`)break}return t.thinking?{phase:`thinking`,detail:null}:{phase:`waiting`,detail:null}}var g={thinking:`Thinking`,tool:`Running`,writing:`Writing`,waiting:`Waiting`},_={retry:`Retrying`,preflight:`Preflight`,watchdog:`Still running`};function v(e){if(!Number.isFinite(e)||e<0)return`0s`;let t=Math.floor(e/1e3);if(t<60)return`${t}s`;let n=Math.floor(t/60);return n<60?`${n}m ${t%60}s`:`${Math.floor(n/60)}h ${n%60}m`}function y(e,t,n){let r=e.progress,i=t.phase===`tool`||t.phase===`writing`?t.phase:r?.phase??t.phase,a=r?r.elapsedMs+Math.max(0,n-r.receivedAt):e.startedAt===null?null:Math.max(0,n-e.startedAt),o=i===`tool`?t.detail??r?.tool??null:null,s=r?.tokPerSec??null,c=(i===`writing`||i===`thinking`)&&s!==null&&s>0?`${s>=10?Math.round(s):s.toFixed(1)} tok/s`:null;return{phase:i,word:g[i],elapsedMs:a,detail:o,rate:c}}var b=(0,a.memo)(function({live:e,derived:t,onStop:n,showNotice:r=!0}){let[i,o]=(0,a.useState)(()=>Date.now());(0,a.useEffect)(()=>{o(Date.now());let e=setInterval(()=>o(Date.now()),1e3);return()=>clearInterval(e)},[e.turnId]);let s=f(),c=y(e,t,i),l=c.phase===`tool`&&t.path?{...c,detail:s(t.path)}:c,u=l.detail?`${l.word}: ${l.detail}`:l.word,d=r?e.notice:null;return(0,m.jsxs)(`div`,{className:p.live,"data-phase":l.phase,children:[(0,m.jsxs)(`div`,{className:p.line,children:[(0,m.jsx)(`span`,{className:p.pulse,"aria-hidden":`true`}),(0,m.jsxs)(`span`,{className:p.text,"aria-hidden":`true`,children:[(0,m.jsx)(`span`,{className:p.word,children:l.word}),l.elapsedMs===null?null:(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)(`span`,{className:p.sep,children:`·`}),(0,m.jsx)(`span`,{className:p.figure,children:v(l.elapsedMs)})]}),l.detail?(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)(`span`,{className:p.sep,children:`·`}),(0,m.jsx)(`span`,{className:p.detail,title:t.path??l.detail,children:l.detail})]}):null,l.rate?(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)(`span`,{className:p.sep,children:`·`}),(0,m.jsx)(`span`,{className:p.figure,children:l.rate})]}):null]}),(0,m.jsx)(`span`,{className:`visually-hidden`,role:`status`,children:u}),n?(0,m.jsxs)(`button`,{type:`button`,className:p.stop,onClick:n,"aria-label":`Stop this turn`,title:`Stop this turn (⌘.)`,children:[(0,m.jsx)(`span`,{className:p.stopIcon,"aria-hidden":`true`}),`Stop`]}):null]}),d?(0,m.jsxs)(`p`,{className:p.notice,"data-kind":d.kind,role:`status`,children:[(0,m.jsx)(`span`,{className:p.noticeWord,children:_[d.kind]}),(0,m.jsx)(`span`,{className:p.noticeText,children:d.message})]}):null]})}),x=new Set,S=null,C=Date.now();function w(e){return x.add(e),S===null&&(C=Date.now(),S=setInterval(()=>{C=Date.now();for(let e of[...x])e()},1e3)),()=>{x.delete(e),x.size===0&&S!==null&&(clearInterval(S),S=null)}}function T(){return C}var E=()=>()=>{},D=()=>0;function O(e=!0){let t=(0,a.useSyncExternalStore)(e?w:E,e?T:D,e?T:D);return Math.max(t,Date.now())}function k(e){if(typeof e==`number`)return Number.isFinite(e)?e:null;if(typeof e!=`string`)return null;let t=Date.parse(e);return Number.isFinite(t)?t:null}function A({since:e,className:t,label:n}){let r=k(e),i=O(r!==null);if(r===null)return null;let a=v(Math.max(0,i-r));return(0,m.jsx)(`span`,{className:t,"data-live-timer":``,"aria-hidden":!n||void 0,"aria-label":n?`${n} ${a}`:void 0,role:n?`timer`:void 0,children:a})}function j({size:e=16,children:t,...n}){return(0,m.jsx)(`svg`,{width:e,height:e,viewBox:`0 0 16 16`,fill:`none`,stroke:`currentColor`,strokeWidth:1.5,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,focusable:`false`,...n,children:t})}function M(e){return(0,m.jsxs)(j,{...e,children:[(0,m.jsx)(`rect`,{x:`1.75`,y:`2.75`,width:`12.5`,height:`10.5`,rx:`1.5`}),(0,m.jsx)(`path`,{d:`m4.5 6.25 2 1.75-2 1.75M8.25 10h3`})]})}function N(e){return(0,m.jsxs)(j,{...e,children:[(0,m.jsx)(`rect`,{x:`1.75`,y:`2.75`,width:`12.5`,height:`10.5`,rx:`1.5`}),(0,m.jsx)(`path`,{d:`M1.75 5.75h12.5M4 4.25h.01M5.75 4.25h.01`})]})}function P(e){return(0,m.jsxs)(j,{...e,children:[(0,m.jsx)(`path`,{d:`M5 2.5v5M2.5 5h5M9 11h4.5`}),(0,m.jsx)(`path`,{d:`M11.5 2.5 4.5 13.5`})]})}function F(e){return(0,m.jsxs)(j,{...e,children:[(0,m.jsx)(`path`,{d:`M6.5 4h7M6.5 8h7M6.5 12h7`}),(0,m.jsx)(`path`,{d:`m2.25 4 1 1 1.5-2M2.5 8h1.75M2.5 12h1.75`})]})}function I(e){return(0,m.jsxs)(j,{...e,children:[(0,m.jsx)(`path`,{d:`m8 2 6 3-6 3-6-3 6-3Z`}),(0,m.jsx)(`path`,{d:`m2 8 6 3 6-3M2 11l6 3 6-3`})]})}function L(e){return(0,m.jsxs)(j,{...e,children:[(0,m.jsx)(`rect`,{x:`2.25`,y:`2.25`,width:`11.5`,height:`11.5`,rx:`1.5`}),(0,m.jsx)(`path`,{d:`M2.25 8h11.5`})]})}function R(e){return(0,m.jsx)(j,{...e,children:(0,m.jsx)(`path`,{d:`m4 4 8 8M12 4l-8 8`})})}function z(e){return(0,m.jsxs)(j,{...e,children:[(0,m.jsx)(`circle`,{cx:`3.5`,cy:`8`,r:`0.9`,fill:`currentColor`,stroke:`none`}),(0,m.jsx)(`circle`,{cx:`8`,cy:`8`,r:`0.9`,fill:`currentColor`,stroke:`none`}),(0,m.jsx)(`circle`,{cx:`12.5`,cy:`8`,r:`0.9`,fill:`currentColor`,stroke:`none`})]})}function B(e){return(0,m.jsxs)(j,{...e,children:[(0,m.jsx)(`path`,{d:`M9.75 2.25 13.75 6.25 11 7.5 8.5 10l.25 2.75-5-5L6.5 8 9 5.25Z`}),(0,m.jsx)(`path`,{d:`m5 11-2.75 2.75`})]})}function V(e){return(0,m.jsxs)(j,{...e,children:[(0,m.jsx)(`rect`,{x:`1.75`,y:`2.75`,width:`12.5`,height:`3`,rx:`1`}),(0,m.jsx)(`path`,{d:`M2.75 5.75v6.5a1 1 0 0 0 1 1h8.5a1 1 0 0 0 1-1v-6.5M6.5 8.5h3`})]})}function H(e){return(0,m.jsx)(j,{...e,children:(0,m.jsx)(`path`,{d:`M10.5 2.75 13.25 5.5 6 12.75H3.25V10Z`})})}function U(e){return(0,m.jsxs)(j,{...e,children:[(0,m.jsx)(`path`,{d:`M2.5 8h9M8.5 4.5 12 8l-3.5 3.5`}),(0,m.jsx)(`path`,{d:`M13.75 3v10`})]})}function W(e){return(0,m.jsxs)(j,{...e,children:[(0,m.jsx)(`rect`,{x:`5.25`,y:`5.25`,width:`8.5`,height:`8.5`,rx:`1.5`}),(0,m.jsx)(`path`,{d:`M10.75 5.25V3.75a1.5 1.5 0 0 0-1.5-1.5h-5.5a1.5 1.5 0 0 0-1.5 1.5v5.5a1.5 1.5 0 0 0 1.5 1.5h1.5`})]})}function G(e){return(0,m.jsx)(j,{...e,children:(0,m.jsx)(`path`,{d:`M2.75 4.25h10.5M6.25 4.25V2.75h3.5v1.5M4.25 4.25l.6 9h6.3l.6-9`})})}var K={terminal:M,preview:N,diff:P,tasks:F,context:I};export{U as a,H as c,A as d,b as f,f as g,c as h,K as i,L as l,s as m,R as n,z as o,h as p,W as r,B as s,V as t,G as u};
|
|
2
|
+
//# sourceMappingURL=dock-icons-BB7Fi6hT.js.map
|