@polderlabs/bizar 10.1.0 → 10.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -2
- package/bizar-dash/dist/assets/{ActivityFeed-Da-JCrE6.js → ActivityFeed-JjmOY2AQ.js} +1 -1
- package/bizar-dash/dist/assets/{ActivityFeed-Da-JCrE6.js.map → ActivityFeed-JjmOY2AQ.js.map} +1 -1
- package/bizar-dash/dist/assets/{ActivityView-nIRsSETb.js → ActivityView-BIIMvGWo.js} +1 -1
- package/bizar-dash/dist/assets/{ActivityView-nIRsSETb.js.map → ActivityView-BIIMvGWo.js.map} +1 -1
- package/bizar-dash/dist/assets/{AdminView-DP1NMxmG.js → AdminView-DRidJtkq.js} +1 -1
- package/bizar-dash/dist/assets/{AdminView-DP1NMxmG.js.map → AdminView-DRidJtkq.js.map} +1 -1
- package/bizar-dash/dist/assets/{AgentActivity-lSPkOr-O.js → AgentActivity-79l4SBMC.js} +1 -1
- package/bizar-dash/dist/assets/{AgentActivity-lSPkOr-O.js.map → AgentActivity-79l4SBMC.js.map} +1 -1
- package/bizar-dash/dist/assets/{AgentsView-BIWGkz7w.js → AgentsView-0vT4uPSc.js} +1 -1
- package/bizar-dash/dist/assets/{AgentsView-BIWGkz7w.js.map → AgentsView-0vT4uPSc.js.map} +1 -1
- package/bizar-dash/dist/assets/{Alert-DN0UysMg.js → Alert-CG5q_s75.js} +1 -1
- package/bizar-dash/dist/assets/{Alert-DN0UysMg.js.map → Alert-CG5q_s75.js.map} +1 -1
- package/bizar-dash/dist/assets/ArtifactsView-DM8ZmpR9.js +21 -0
- package/bizar-dash/dist/assets/ArtifactsView-DM8ZmpR9.js.map +1 -0
- package/bizar-dash/dist/assets/{AuthView-DW5cs7ma.js → AuthView-BveN94MC.js} +1 -1
- package/bizar-dash/dist/assets/{AuthView-DW5cs7ma.js.map → AuthView-BveN94MC.js.map} +1 -1
- package/bizar-dash/dist/assets/{Avatar-DZPv9pvd.js → Avatar-ulUPIYTd.js} +1 -1
- package/bizar-dash/dist/assets/{Avatar-DZPv9pvd.js.map → Avatar-ulUPIYTd.js.map} +1 -1
- package/bizar-dash/dist/assets/{BackgroundJobsView-D8O-EyMD.js → BackgroundJobsView-BWErhqFo.js} +1 -1
- package/bizar-dash/dist/assets/{BackgroundJobsView-D8O-EyMD.js.map → BackgroundJobsView-BWErhqFo.js.map} +1 -1
- package/bizar-dash/dist/assets/{BackupView-uwXkhxOV.js → BackupView-CnBjzG9N.js} +1 -1
- package/bizar-dash/dist/assets/{BackupView-uwXkhxOV.js.map → BackupView-CnBjzG9N.js.map} +1 -1
- package/bizar-dash/dist/assets/{Card-Tyboihu7.js → Card-B0imhxMk.js} +1 -1
- package/bizar-dash/dist/assets/{Card-Tyboihu7.js.map → Card-B0imhxMk.js.map} +1 -1
- package/bizar-dash/dist/assets/{ChatView-DRBHzgay.js → ChatView-D6zGytzx.js} +1 -1
- package/bizar-dash/dist/assets/{ChatView-DRBHzgay.js.map → ChatView-D6zGytzx.js.map} +1 -1
- package/bizar-dash/dist/assets/{Chip-BrXIlR9w.js → Chip-wwZSGwLE.js} +1 -1
- package/bizar-dash/dist/assets/{Chip-BrXIlR9w.js.map → Chip-wwZSGwLE.js.map} +1 -1
- package/bizar-dash/dist/assets/{ClaudeSessionsView-Ck62jaOr.js → ClaudeSessionsView-DK1sOLhi.js} +1 -1
- package/bizar-dash/dist/assets/{ClaudeSessionsView-Ck62jaOr.js.map → ClaudeSessionsView-DK1sOLhi.js.map} +1 -1
- package/bizar-dash/dist/assets/{ClipboardView-DfND6xL6.js → ClipboardView-CZ2lYPr0.js} +1 -1
- package/bizar-dash/dist/assets/{ClipboardView-DfND6xL6.js.map → ClipboardView-CZ2lYPr0.js.map} +1 -1
- package/bizar-dash/dist/assets/{ConfigView-DjJmSrOJ.js → ConfigView-D-zT9eNA.js} +1 -1
- package/bizar-dash/dist/assets/{ConfigView-DjJmSrOJ.js.map → ConfigView-D-zT9eNA.js.map} +1 -1
- package/bizar-dash/dist/assets/{DiagnosticsView-CKgItf0J.js → DiagnosticsView-BocV68Lb.js} +1 -1
- package/bizar-dash/dist/assets/{DiagnosticsView-CKgItf0J.js.map → DiagnosticsView-BocV68Lb.js.map} +1 -1
- package/bizar-dash/dist/assets/{DialogsView-DHYF8dE1.js → DialogsView-BVjPI5x7.js} +1 -1
- package/bizar-dash/dist/assets/{DialogsView-DHYF8dE1.js.map → DialogsView-BVjPI5x7.js.map} +1 -1
- package/bizar-dash/dist/assets/{DoctorView-DDLHrSKk.js → DoctorView-84gJcZwM.js} +1 -1
- package/bizar-dash/dist/assets/{DoctorView-DDLHrSKk.js.map → DoctorView-84gJcZwM.js.map} +1 -1
- package/bizar-dash/dist/assets/{EmptyState-DZ2ouKwd.js → EmptyState-BjgVOrcO.js} +1 -1
- package/bizar-dash/dist/assets/{EmptyState-DZ2ouKwd.js.map → EmptyState-BjgVOrcO.js.map} +1 -1
- package/bizar-dash/dist/assets/{EnvVarsView-D1cx83mB.js → EnvVarsView-BJ2TEzF7.js} +1 -1
- package/bizar-dash/dist/assets/{EnvVarsView-D1cx83mB.js.map → EnvVarsView-BJ2TEzF7.js.map} +1 -1
- package/bizar-dash/dist/assets/{ErrorState-DwwJ2-Jq.js → ErrorState-BE3WBFqF.js} +1 -1
- package/bizar-dash/dist/assets/{ErrorState-DwwJ2-Jq.js.map → ErrorState-BE3WBFqF.js.map} +1 -1
- package/bizar-dash/dist/assets/{EvalView-C9aYixXW.js → EvalView-BJJ2P-kK.js} +1 -1
- package/bizar-dash/dist/assets/{EvalView-C9aYixXW.js.map → EvalView-BJJ2P-kK.js.map} +1 -1
- package/bizar-dash/dist/assets/{GoalsView-DZa_mGmh.js → GoalsView-pwROHApi.js} +1 -1
- package/bizar-dash/dist/assets/{GoalsView-DZa_mGmh.js.map → GoalsView-pwROHApi.js.map} +1 -1
- package/bizar-dash/dist/assets/{Grid-CiRyhAgR.js → Grid-qcvbhx6e.js} +1 -1
- package/bizar-dash/dist/assets/{Grid-CiRyhAgR.js.map → Grid-qcvbhx6e.js.map} +1 -1
- package/bizar-dash/dist/assets/{HeadroomView-CNc-TI7u.js → HeadroomView-D1UiTYbt.js} +1 -1
- package/bizar-dash/dist/assets/{HeadroomView-CNc-TI7u.js.map → HeadroomView-D1UiTYbt.js.map} +1 -1
- package/bizar-dash/dist/assets/{HistoryView-Dv7aPxJE.js → HistoryView-C7SSOLJA.js} +1 -1
- package/bizar-dash/dist/assets/{HistoryView-Dv7aPxJE.js.map → HistoryView-C7SSOLJA.js.map} +1 -1
- package/bizar-dash/dist/assets/{Input-BbFUDQnS.js → Input-Bgzxf_Hu.js} +1 -1
- package/bizar-dash/dist/assets/{Input-BbFUDQnS.js.map → Input-Bgzxf_Hu.js.map} +1 -1
- package/bizar-dash/dist/assets/{LibrariesView-DBQ83TNa.js → LibrariesView-CbFFksox.js} +1 -1
- package/bizar-dash/dist/assets/{LibrariesView-DBQ83TNa.js.map → LibrariesView-CbFFksox.js.map} +1 -1
- package/bizar-dash/dist/assets/{LightRAGView-JYpE5u9p.js → LightRAGView-BqDrJbRq.js} +1 -1
- package/bizar-dash/dist/assets/{LightRAGView-JYpE5u9p.js.map → LightRAGView-BqDrJbRq.js.map} +1 -1
- package/bizar-dash/dist/assets/{ListHeader-BGjm11Z0.js → ListHeader-TjPJrUfY.js} +1 -1
- package/bizar-dash/dist/assets/{ListHeader-BGjm11Z0.js.map → ListHeader-TjPJrUfY.js.map} +1 -1
- package/bizar-dash/dist/assets/{MemoryView-CnOP2MvM.js → MemoryView-BlTeHLhX.js} +1 -1
- package/bizar-dash/dist/assets/{MemoryView-CnOP2MvM.js.map → MemoryView-BlTeHLhX.js.map} +1 -1
- package/bizar-dash/dist/assets/{MiscView-BBcpUbZZ.js → MiscView-B8QY82Pm.js} +1 -1
- package/bizar-dash/dist/assets/{MiscView-BBcpUbZZ.js.map → MiscView-B8QY82Pm.js.map} +1 -1
- package/bizar-dash/dist/assets/{ModsView-CU4cAmIz.js → ModsView-CvW1OWts.js} +1 -1
- package/bizar-dash/dist/assets/{ModsView-CU4cAmIz.js.map → ModsView-CvW1OWts.js.map} +1 -1
- package/bizar-dash/dist/assets/{NotificationsView-BYffvthq.js → NotificationsView-CsBt5U7x.js} +1 -1
- package/bizar-dash/dist/assets/{NotificationsView-BYffvthq.js.map → NotificationsView-CsBt5U7x.js.map} +1 -1
- package/bizar-dash/dist/assets/{ObsidianView-D3bI0row.js → ObsidianView-BCTejEj2.js} +1 -1
- package/bizar-dash/dist/assets/{ObsidianView-D3bI0row.js.map → ObsidianView-BCTejEj2.js.map} +1 -1
- package/bizar-dash/dist/assets/{OverviewView-0tq8f-Y0.js → OverviewView-BsuPxwnB.js} +1 -1
- package/bizar-dash/dist/assets/{OverviewView-0tq8f-Y0.js.map → OverviewView-BsuPxwnB.js.map} +1 -1
- package/bizar-dash/dist/assets/{ProgressBar-B-h60LKA.js → ProgressBar-DlZTETEn.js} +1 -1
- package/bizar-dash/dist/assets/{ProgressBar-B-h60LKA.js.map → ProgressBar-DlZTETEn.js.map} +1 -1
- package/bizar-dash/dist/assets/{ProjectsView-DhX5lzHS.js → ProjectsView-Soxtmpet.js} +1 -1
- package/bizar-dash/dist/assets/{ProjectsView-DhX5lzHS.js.map → ProjectsView-Soxtmpet.js.map} +1 -1
- package/bizar-dash/dist/assets/{ProvidersView-CJMX7jm9.js → ProvidersView-DaCEV9pM.js} +1 -1
- package/bizar-dash/dist/assets/{ProvidersView-CJMX7jm9.js.map → ProvidersView-DaCEV9pM.js.map} +1 -1
- package/bizar-dash/dist/assets/{SchedulesView-CcwDQ9Nm.js → SchedulesView-DY2ByBj_.js} +1 -1
- package/bizar-dash/dist/assets/{SchedulesView-CcwDQ9Nm.js.map → SchedulesView-DY2ByBj_.js.map} +1 -1
- package/bizar-dash/dist/assets/{Select-Cw7Qtz2M.js → Select-Dbd88_n8.js} +1 -1
- package/bizar-dash/dist/assets/{Select-Cw7Qtz2M.js.map → Select-Dbd88_n8.js.map} +1 -1
- package/bizar-dash/dist/assets/{SettingsRow-DgsgbPSI.js → SettingsRow-CLMNd434.js} +1 -1
- package/bizar-dash/dist/assets/{SettingsRow-DgsgbPSI.js.map → SettingsRow-CLMNd434.js.map} +1 -1
- package/bizar-dash/dist/assets/{SettingsView-DTAFdKyK.js → SettingsView-b24oFq9x.js} +1 -1
- package/bizar-dash/dist/assets/{SettingsView-DTAFdKyK.js.map → SettingsView-b24oFq9x.js.map} +1 -1
- package/bizar-dash/dist/assets/{Sheet-DH-WNJ3Y.js → Sheet-DnDid58j.js} +1 -1
- package/bizar-dash/dist/assets/{Sheet-DH-WNJ3Y.js.map → Sheet-DnDid58j.js.map} +1 -1
- package/bizar-dash/dist/assets/{Slider-DXbJuQ7y.js → Slider-BJCvKI1I.js} +1 -1
- package/bizar-dash/dist/assets/{Slider-DXbJuQ7y.js.map → Slider-BJCvKI1I.js.map} +1 -1
- package/bizar-dash/dist/assets/{Sparkline-CYtAn82j.js → Sparkline-ktqQLdyb.js} +1 -1
- package/bizar-dash/dist/assets/{Sparkline-CYtAn82j.js.map → Sparkline-ktqQLdyb.js.map} +1 -1
- package/bizar-dash/dist/assets/{Switch-BItGrPqr.js → Switch-CwXfsDii.js} +1 -1
- package/bizar-dash/dist/assets/{Switch-BItGrPqr.js.map → Switch-CwXfsDii.js.map} +1 -1
- package/bizar-dash/dist/assets/{TasksView-i5tm6CkX.js → TasksView-BHmpzFCf.js} +1 -1
- package/bizar-dash/dist/assets/{TasksView-i5tm6CkX.js.map → TasksView-BHmpzFCf.js.map} +1 -1
- package/bizar-dash/dist/assets/{Textarea-qYDf3yds.js → Textarea-BzIAwSlp.js} +1 -1
- package/bizar-dash/dist/assets/{Textarea-qYDf3yds.js.map → Textarea-BzIAwSlp.js.map} +1 -1
- package/bizar-dash/dist/assets/{UpdateView-Xln-ej26.js → UpdateView-CSmX9NN0.js} +1 -1
- package/bizar-dash/dist/assets/{UpdateView-Xln-ej26.js.map → UpdateView-CSmX9NN0.js.map} +1 -1
- package/bizar-dash/dist/assets/{UsageView-BGuZSUZ6.js → UsageView-COyr_Gfi.js} +1 -1
- package/bizar-dash/dist/assets/{UsageView-BGuZSUZ6.js.map → UsageView-COyr_Gfi.js.map} +1 -1
- package/bizar-dash/dist/assets/{ViewHeader-BqIPVKuG.js → ViewHeader-BzYZ3m-e.js} +1 -1
- package/bizar-dash/dist/assets/{ViewHeader-BqIPVKuG.js.map → ViewHeader-BzYZ3m-e.js.map} +1 -1
- package/bizar-dash/dist/assets/{VoiceView-CWUOCQs_.js → VoiceView-D33_xG5t.js} +1 -1
- package/bizar-dash/dist/assets/{VoiceView-CWUOCQs_.js.map → VoiceView-D33_xG5t.js.map} +1 -1
- package/bizar-dash/dist/assets/{brain-fsBSDl2g.js → brain-CM-Vls8r.js} +1 -1
- package/bizar-dash/dist/assets/{brain-fsBSDl2g.js.map → brain-CM-Vls8r.js.map} +1 -1
- package/bizar-dash/dist/assets/{calendar-DsHTZoCU.js → calendar-C0lgZk69.js} +1 -1
- package/bizar-dash/dist/assets/{calendar-DsHTZoCU.js.map → calendar-C0lgZk69.js.map} +1 -1
- package/bizar-dash/dist/assets/{circle-CqSxqqeJ.js → circle-Bx_hQEBC.js} +1 -1
- package/bizar-dash/dist/assets/{circle-CqSxqqeJ.js.map → circle-Bx_hQEBC.js.map} +1 -1
- package/bizar-dash/dist/assets/{circle-alert-BsLl001l.js → circle-alert-DHvsqpw5.js} +1 -1
- package/bizar-dash/dist/assets/{circle-alert-BsLl001l.js.map → circle-alert-DHvsqpw5.js.map} +1 -1
- package/bizar-dash/dist/assets/{circle-check-rVKMQmZp.js → circle-check-Bx8wmXR9.js} +1 -1
- package/bizar-dash/dist/assets/{circle-check-rVKMQmZp.js.map → circle-check-Bx8wmXR9.js.map} +1 -1
- package/bizar-dash/dist/assets/{clock-OuficfgH.js → clock-CejGoecX.js} +1 -1
- package/bizar-dash/dist/assets/{clock-OuficfgH.js.map → clock-CejGoecX.js.map} +1 -1
- package/bizar-dash/dist/assets/{download-BdHlstUY.js → download-4cEUWue1.js} +1 -1
- package/bizar-dash/dist/assets/{download-BdHlstUY.js.map → download-4cEUWue1.js.map} +1 -1
- package/bizar-dash/dist/assets/{external-link-DA3_VGm-.js → external-link-0NqUhI4i.js} +1 -1
- package/bizar-dash/dist/assets/{external-link-DA3_VGm-.js.map → external-link-0NqUhI4i.js.map} +1 -1
- package/bizar-dash/dist/assets/{file-text-B-g8H227.js → file-text-yEMkrSoZ.js} +1 -1
- package/bizar-dash/dist/assets/{file-text-B-g8H227.js.map → file-text-yEMkrSoZ.js.map} +1 -1
- package/bizar-dash/dist/assets/{filter-D9B8s5CG.js → filter-BXt60zAS.js} +1 -1
- package/bizar-dash/dist/assets/{filter-D9B8s5CG.js.map → filter-BXt60zAS.js.map} +1 -1
- package/bizar-dash/dist/assets/{git-pull-request-ByH0YuVz.js → git-pull-request-BjG5lNtR.js} +1 -1
- package/bizar-dash/dist/assets/{git-pull-request-ByH0YuVz.js.map → git-pull-request-BjG5lNtR.js.map} +1 -1
- package/bizar-dash/dist/assets/{main-CdOm1Pj9.js → main-CokHAXWM.js} +2 -2
- package/bizar-dash/dist/assets/{main-CdOm1Pj9.js.map → main-CokHAXWM.js.map} +1 -1
- package/bizar-dash/dist/assets/{pause-C9hgd8wX.js → pause-CAmulU0F.js} +1 -1
- package/bizar-dash/dist/assets/{pause-C9hgd8wX.js.map → pause-CAmulU0F.js.map} +1 -1
- package/bizar-dash/dist/assets/{pencil-BanqaMw7.js → pencil-eq5IqBmU.js} +1 -1
- package/bizar-dash/dist/assets/{pencil-BanqaMw7.js.map → pencil-eq5IqBmU.js.map} +1 -1
- package/bizar-dash/dist/assets/{play-CawUZ02_.js → play-BoLrrcBA.js} +1 -1
- package/bizar-dash/dist/assets/{play-CawUZ02_.js.map → play-BoLrrcBA.js.map} +1 -1
- package/bizar-dash/dist/assets/{plug-Cmv0-T_e.js → plug-DBrVM9lK.js} +1 -1
- package/bizar-dash/dist/assets/{plug-Cmv0-T_e.js.map → plug-DBrVM9lK.js.map} +1 -1
- package/bizar-dash/dist/assets/{plus-M6LYiKX8.js → plus-DENm0hqV.js} +1 -1
- package/bizar-dash/dist/assets/{plus-M6LYiKX8.js.map → plus-DENm0hqV.js.map} +1 -1
- package/bizar-dash/dist/assets/{power-DP5x_SRF.js → power-DLjXF7zE.js} +1 -1
- package/bizar-dash/dist/assets/{power-DP5x_SRF.js.map → power-DLjXF7zE.js.map} +1 -1
- package/bizar-dash/dist/assets/{power-off-NOJRLAIs.js → power-off-D47cOnG_.js} +1 -1
- package/bizar-dash/dist/assets/{power-off-NOJRLAIs.js.map → power-off-D47cOnG_.js.map} +1 -1
- package/bizar-dash/dist/assets/{refresh-ccw-CHMprsm5.js → refresh-ccw-DNrXX9gB.js} +1 -1
- package/bizar-dash/dist/assets/{refresh-ccw-CHMprsm5.js.map → refresh-ccw-DNrXX9gB.js.map} +1 -1
- package/bizar-dash/dist/assets/{refresh-cw-1RBqDKgJ.js → refresh-cw-CoAHDpeS.js} +1 -1
- package/bizar-dash/dist/assets/{refresh-cw-1RBqDKgJ.js.map → refresh-cw-CoAHDpeS.js.map} +1 -1
- package/bizar-dash/dist/assets/{rotate-ccw-DJRekBbi.js → rotate-ccw-ObX5awaG.js} +1 -1
- package/bizar-dash/dist/assets/{rotate-ccw-DJRekBbi.js.map → rotate-ccw-ObX5awaG.js.map} +1 -1
- package/bizar-dash/dist/assets/{rotate-cw-DHooc0Tx.js → rotate-cw-xpPJw9LP.js} +1 -1
- package/bizar-dash/dist/assets/{rotate-cw-DHooc0Tx.js.map → rotate-cw-xpPJw9LP.js.map} +1 -1
- package/bizar-dash/dist/assets/{save-DQKCKyZK.js → save-D1iKiJJl.js} +1 -1
- package/bizar-dash/dist/assets/{save-DQKCKyZK.js.map → save-D1iKiJJl.js.map} +1 -1
- package/bizar-dash/dist/assets/{send-xgy3RAi7.js → send-BRcAhZUm.js} +1 -1
- package/bizar-dash/dist/assets/{send-xgy3RAi7.js.map → send-BRcAhZUm.js.map} +1 -1
- package/bizar-dash/dist/assets/{shield-alert-CWV2F5ab.js → shield-alert-C6vEcK-g.js} +1 -1
- package/bizar-dash/dist/assets/{shield-alert-CWV2F5ab.js.map → shield-alert-C6vEcK-g.js.map} +1 -1
- package/bizar-dash/dist/assets/{trash-2-CL4khSRv.js → trash-2-DZDjEnNh.js} +1 -1
- package/bizar-dash/dist/assets/{trash-2-CL4khSRv.js.map → trash-2-DZDjEnNh.js.map} +1 -1
- package/bizar-dash/dist/assets/{trending-up-CSJJhWTS.js → trending-up-DMj-AJVf.js} +1 -1
- package/bizar-dash/dist/assets/{trending-up-CSJJhWTS.js.map → trending-up-DMj-AJVf.js.map} +1 -1
- package/bizar-dash/dist/assets/{triangle-alert-BPLb7uLt.js → triangle-alert-_2xe_tl4.js} +1 -1
- package/bizar-dash/dist/assets/{triangle-alert-BPLb7uLt.js.map → triangle-alert-_2xe_tl4.js.map} +1 -1
- package/bizar-dash/dist/assets/{upload-Cww6MvFq.js → upload-DdDljAeV.js} +1 -1
- package/bizar-dash/dist/assets/{upload-Cww6MvFq.js.map → upload-DdDljAeV.js.map} +1 -1
- package/bizar-dash/dist/assets/{zap-DmhVUzRA.js → zap-mHsgh0FP.js} +1 -1
- package/bizar-dash/dist/assets/{zap-DmhVUzRA.js.map → zap-mHsgh0FP.js.map} +1 -1
- package/bizar-dash/dist/index.html +1 -1
- package/bizar-dash/skills/headroom/SKILL.md +94 -0
- package/bizar-dash/src/server/api.mjs +11 -0
- package/bizar-dash/src/server/artifacts-store.mjs +120 -14
- package/bizar-dash/src/server/claude-artifacts.mjs +281 -0
- package/bizar-dash/src/server/headroom.mjs +649 -0
- package/bizar-dash/src/server/routes/artifacts.mjs +42 -6
- package/bizar-dash/src/server/routes/audit.mjs +78 -0
- package/bizar-dash/src/server/routes/decisions.mjs +78 -0
- package/bizar-dash/src/server/routes/headroom.mjs +126 -0
- package/bizar-dash/src/server/routes/model-router.mjs +55 -0
- package/bizar-dash/src/server/routes/model-router.test.mjs +28 -0
- package/bizar-dash/src/server/routes/sessions.mjs +80 -0
- package/bizar-dash/src/server/sprint.test.mjs +163 -0
- package/bizar-dash/src/web/v8/views/Artifacts/ArtifactsView.tsx +232 -21
- package/bizar-dash/src/web/v8/views/Artifacts/ClaudeArtifactView.tsx +179 -0
- package/bizar-dash/tests/headroom-install.test.mjs +173 -0
- package/bizar-dash/tests/headroom-settings.test.mjs +126 -0
- package/bizar-dash/tests/headroom-status.test.mjs +117 -0
- package/cli/commands/headroom.mjs +204 -0
- package/package.json +1 -1
- package/packages/sdk/dist/agent/cron.d.ts +44 -0
- package/packages/sdk/dist/agent/cron.js +79 -0
- package/packages/sdk/dist/agent/heartbeat.d.ts +43 -0
- package/packages/sdk/dist/agent/heartbeat.js +116 -0
- package/packages/sdk/dist/index.d.ts +3 -0
- package/packages/sdk/dist/index.js +5 -0
- package/packages/sdk/dist/learning/decisions.d.ts +46 -0
- package/packages/sdk/dist/learning/decisions.js +132 -0
- package/packages/sdk/dist/learning/index.d.ts +8 -0
- package/packages/sdk/dist/learning/index.js +8 -0
- package/packages/sdk/dist/learning/instincts.d.ts +38 -0
- package/packages/sdk/dist/learning/instincts.js +139 -0
- package/packages/sdk/dist/mcp/server.js +119 -1
- package/packages/sdk/dist/router/agent-model-registry.d.ts +89 -0
- package/packages/sdk/dist/router/agent-model-registry.js +144 -0
- package/packages/sdk/dist/router/index.d.ts +1 -0
- package/packages/sdk/dist/router/index.js +1 -0
- package/packages/sdk/package.json +3 -2
- package/scripts/audit.mjs +388 -0
- package/scripts/audit.test.mjs +149 -0
- package/scripts/eval-gate.mjs +134 -0
- package/scripts/eval-gate.test.mjs +138 -0
- package/scripts/feature-state-machine.mjs +178 -0
- package/scripts/feature-state-machine.test.mjs +135 -0
- package/scripts/spec-dedup.test.mjs +124 -0
- package/scripts/sprint.mjs +213 -0
- package/scripts/worktree-setup.sh +79 -0
- package/bizar-dash/dist/assets/ArtifactsView-CDIhPBAx.js +0 -6
- package/bizar-dash/dist/assets/ArtifactsView-CDIhPBAx.js.map +0 -1
|
@@ -51,18 +51,54 @@ export function createArtifactsRouter({ state, broadcast, projectRoot }) {
|
|
|
51
51
|
res.json(artifact);
|
|
52
52
|
}));
|
|
53
53
|
|
|
54
|
-
// v3.21.0 — Compile the artifact
|
|
55
|
-
// glyph
|
|
56
|
-
//
|
|
57
|
-
//
|
|
54
|
+
// v3.21.0 — Compile the artifact for the renderer. Dispatches by kind:
|
|
55
|
+
// mdx → JSON glyph blocks (legacy v3-v9 renderer)
|
|
56
|
+
// claude-html → iframe-ready HTML envelope
|
|
57
|
+
// claude-svg → iframe-ready HTML envelope (SVG body)
|
|
58
|
+
// claude-react → iframe-ready HTML envelope (Babel-standalone JSX)
|
|
59
|
+
// The Claude-* envelope is ready to drop into an iframe `srcdoc`; the
|
|
60
|
+
// client previewer component (`<ClaudeArtifactView>`) picks the
|
|
61
|
+
// correct renderer based on `kind` and `html`.
|
|
58
62
|
router.get('/artifacts/:slug/render', wrap(async (req, res) => {
|
|
59
63
|
const artifact = artifactsStore.get(req.params.slug, projectRoot);
|
|
60
64
|
if (!artifact) {
|
|
61
65
|
res.status(404).json({ error: 'not_found' });
|
|
62
66
|
return;
|
|
63
67
|
}
|
|
64
|
-
const
|
|
65
|
-
|
|
68
|
+
const kind = artifact.kind || 'mdx';
|
|
69
|
+
if (kind === 'mdx') {
|
|
70
|
+
const compiled = await compileGlyphMdx(artifact.planMdx || '');
|
|
71
|
+
res.json({ slug: req.params.slug, kind, ...compiled });
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const compiled = artifactsStore.compile(req.params.slug, projectRoot);
|
|
75
|
+
if (!compiled) {
|
|
76
|
+
res.status(404).json({ error: 'not_found' });
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
res.json({
|
|
80
|
+
slug: req.params.slug,
|
|
81
|
+
kind: compiled.kind,
|
|
82
|
+
html: compiled.html,
|
|
83
|
+
warnings: compiled.warnings || [],
|
|
84
|
+
safeMode: !!compiled.safeMode,
|
|
85
|
+
compiledAt: compiled.compiledAt,
|
|
86
|
+
});
|
|
87
|
+
}));
|
|
88
|
+
|
|
89
|
+
// v10.1.0 — Full-screen viewer. Returns the compiled HTML envelope
|
|
90
|
+
// with `Content-Type: text/html` so a `<a target="_blank">` opens
|
|
91
|
+
// the artifact on its own page (no dashboard chrome). The iframe
|
|
92
|
+
// `sandbox` rules from the envelope apply — the page is just the
|
|
93
|
+
// envelope served as `text/html`.
|
|
94
|
+
router.get('/artifacts/:slug/view', wrap(async (req, res) => {
|
|
95
|
+
const compiled = artifactsStore.compile(req.params.slug, projectRoot);
|
|
96
|
+
if (!compiled) {
|
|
97
|
+
res.status(404).type('html').send('<h1>Not found</h1>');
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
res.set('Content-Security-Policy', "default-src 'self'; script-src 'unsafe-inline' 'unsafe-eval' https://unpkg.com https://cdn.jsdelivr.net https://cdnjs.cloudflare.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src https://fonts.gstatic.com; img-src * data:;");
|
|
101
|
+
res.type('html').send(compiled.html);
|
|
66
102
|
}));
|
|
67
103
|
|
|
68
104
|
// v3.1.0 — Full artifact CRUD + canvas editing.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/server/routes/audit.mjs
|
|
3
|
+
*
|
|
4
|
+
* GET /api/audit — returns latest harness audit score + diff vs prior.
|
|
5
|
+
*
|
|
6
|
+
* Reads .harness/audit/latest.json (written by `node scripts/audit.mjs --write`).
|
|
7
|
+
* Returns the full audit result plus a `diff` object showing which categories
|
|
8
|
+
* changed score vs the prior run (if any).
|
|
9
|
+
*/
|
|
10
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
import { wrap } from './_shared.mjs';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {object} deps
|
|
16
|
+
* @param {string} deps.projectRoot
|
|
17
|
+
* @returns {import('express').Router}
|
|
18
|
+
*/
|
|
19
|
+
export function createAuditRouter({ projectRoot }) {
|
|
20
|
+
const router = require('express').Router();
|
|
21
|
+
const LATEST = join(projectRoot, '.harness', 'audit', 'latest.json');
|
|
22
|
+
|
|
23
|
+
router.get('/audit', wrap(async (_req, res) => {
|
|
24
|
+
if (!existsSync(LATEST)) {
|
|
25
|
+
res.status(404).json({
|
|
26
|
+
error: 'not_ready',
|
|
27
|
+
message: 'No audit run yet. Run `node scripts/audit.mjs --write` first.',
|
|
28
|
+
});
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let latest;
|
|
33
|
+
try {
|
|
34
|
+
latest = JSON.parse(readFileSync(LATEST, 'utf8'));
|
|
35
|
+
} catch (err) {
|
|
36
|
+
res.status(500).json({ error: 'parse_error', message: err.message });
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Compute diff vs the run before last (if we have history)
|
|
41
|
+
/** @type {Record<string, {current: number, prior: number, delta: number} | null>} */
|
|
42
|
+
const diff = {};
|
|
43
|
+
const priorPath = join(projectRoot, '.harness', 'audit', 'previous.json');
|
|
44
|
+
|
|
45
|
+
if (existsSync(priorPath)) {
|
|
46
|
+
try {
|
|
47
|
+
const prior = JSON.parse(readFileSync(priorPath, 'utf8'));
|
|
48
|
+
const priorScores = prior.scores ?? {};
|
|
49
|
+
for (const [key, weight] of Object.entries(latest.weights ?? {})) {
|
|
50
|
+
const current = latest.scores?.[key] ?? 0;
|
|
51
|
+
const priorScore = priorScores[key] ?? 0;
|
|
52
|
+
if (current !== priorScore) {
|
|
53
|
+
diff[key] = { current, prior: priorScore, delta: current - priorScore };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Also diff total
|
|
57
|
+
const currentTotal = latest.total ?? 0;
|
|
58
|
+
const priorTotal = prior.total ?? 0;
|
|
59
|
+
if (currentTotal !== priorTotal) {
|
|
60
|
+
diff._total = { current: currentTotal, prior: priorTotal, delta: currentTotal - priorTotal };
|
|
61
|
+
}
|
|
62
|
+
} catch {
|
|
63
|
+
// prior file unreadable — no diff
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
res.json({
|
|
68
|
+
latest,
|
|
69
|
+
diff,
|
|
70
|
+
_links: {
|
|
71
|
+
run: 'node scripts/audit.mjs --write',
|
|
72
|
+
latestPath: LATEST,
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
}));
|
|
76
|
+
|
|
77
|
+
return router;
|
|
78
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/server/routes/decisions.mjs
|
|
3
|
+
*
|
|
4
|
+
* v10.1.1 — Pillar D: Decisions log API.
|
|
5
|
+
*
|
|
6
|
+
* GET /api/decisions
|
|
7
|
+
* Returns list of decisions + tamper-evidence status.
|
|
8
|
+
*
|
|
9
|
+
* Storage: <projectRoot>/.bizar/learning/decisions.jsonl
|
|
10
|
+
* Falls back to BIZAR_HOME if projectRoot is not set.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
import { Router } from 'express';
|
|
16
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
17
|
+
import { join } from 'node:path';
|
|
18
|
+
import { homedir } from 'node:os';
|
|
19
|
+
import { wrap } from './_shared.mjs';
|
|
20
|
+
|
|
21
|
+
/** Default root: projectRoot > cwd > $HOME */
|
|
22
|
+
function resolveRoot(projectRoot) {
|
|
23
|
+
if (projectRoot && projectRoot !== '.') return projectRoot;
|
|
24
|
+
try { return process.cwd(); } catch { return homedir(); }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {object} deps
|
|
29
|
+
* @param {string} [deps.projectRoot]
|
|
30
|
+
* @returns {import('express').Router}
|
|
31
|
+
*/
|
|
32
|
+
export function createDecisionsRouter({ projectRoot } = {}) {
|
|
33
|
+
const router = Router();
|
|
34
|
+
const root = projectRoot ?? resolveRoot(projectRoot);
|
|
35
|
+
|
|
36
|
+
// Lazy-import SDK to avoid hard coupling at boot time.
|
|
37
|
+
async function getDecisionsMod() {
|
|
38
|
+
// Try the published SDK first, then fall back to monorepo dist.
|
|
39
|
+
try {
|
|
40
|
+
return await import('@polderlabs/bizar-sdk/learning');
|
|
41
|
+
} catch {
|
|
42
|
+
return await import('../../../packages/sdk/dist/learning/index.js');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// GET /decisions — list + tamper status
|
|
47
|
+
router.get('/decisions', wrap(async (_req, res) => {
|
|
48
|
+
let mod;
|
|
49
|
+
try {
|
|
50
|
+
mod = await getDecisionsMod();
|
|
51
|
+
} catch (err) {
|
|
52
|
+
res.status(503).json({ error: 'unavailable', message: 'SDK not loaded', detail: err?.message });
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const decisions = mod.listDecisions ? mod.listDecisions({ project: root }) : [];
|
|
57
|
+
const verified = mod.verifyChain ? mod.verifyChain({ project: root }) : { ok: true };
|
|
58
|
+
|
|
59
|
+
// Apply datamark to all string fields to sanitise surrogates.
|
|
60
|
+
const sanitised = decisions.map((entry) => {
|
|
61
|
+
const out = {};
|
|
62
|
+
for (const [k, v] of Object.entries(entry)) {
|
|
63
|
+
out[k] = mod.datamark ? mod.datamark(v) : v;
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
res.json({
|
|
69
|
+
decisions: sanitised,
|
|
70
|
+
tamperEvidence: {
|
|
71
|
+
status: verified.ok ? 'intact' : 'broken',
|
|
72
|
+
at: verified.at ?? null,
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
}));
|
|
76
|
+
|
|
77
|
+
return router;
|
|
78
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/server/routes/headroom.mjs
|
|
3
|
+
*
|
|
4
|
+
* v1.0.0 — REST endpoints for Headroom management.
|
|
5
|
+
*
|
|
6
|
+
* Mounted at /api/headroom/* by api.mjs.
|
|
7
|
+
*/
|
|
8
|
+
import { Router } from 'express';
|
|
9
|
+
import {
|
|
10
|
+
getHeadroomStatus,
|
|
11
|
+
getHeadroomStats,
|
|
12
|
+
installHeadroom,
|
|
13
|
+
wrapCline,
|
|
14
|
+
unwrapCline,
|
|
15
|
+
startProxy,
|
|
16
|
+
stopProxy,
|
|
17
|
+
getClineConfig,
|
|
18
|
+
headroomStartupHook,
|
|
19
|
+
} from '../headroom.mjs';
|
|
20
|
+
import { wrap } from './_shared.mjs';
|
|
21
|
+
|
|
22
|
+
export function createHeadroomRouter() {
|
|
23
|
+
const router = Router();
|
|
24
|
+
|
|
25
|
+
// GET /api/headroom/status
|
|
26
|
+
router.get('/status', wrap(async (_req, res) => {
|
|
27
|
+
const status = await getHeadroomStatus();
|
|
28
|
+
res.json(status);
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
// GET /api/headroom/stats?hours=24
|
|
32
|
+
router.get('/stats', wrap(async (req, res) => {
|
|
33
|
+
const hours = Math.max(1, Math.min(parseInt(req.query.hours, 10) || 24, 720));
|
|
34
|
+
const stats = await getHeadroomStats({ hours });
|
|
35
|
+
res.json(stats);
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
// POST /api/headroom/install
|
|
39
|
+
router.post('/install', wrap(async (req, res) => {
|
|
40
|
+
const { force = false } = req.body || {};
|
|
41
|
+
const result = await installHeadroom({ force: Boolean(force) });
|
|
42
|
+
res.json(result);
|
|
43
|
+
}));
|
|
44
|
+
|
|
45
|
+
// POST /api/headroom/wrap
|
|
46
|
+
router.post('/wrap', wrap(async (req, res) => {
|
|
47
|
+
const { port } = req.body || {};
|
|
48
|
+
const result = await wrapCline({
|
|
49
|
+
port: port ? Math.max(1, Math.min(parseInt(port, 10), 65535)) : 8787,
|
|
50
|
+
});
|
|
51
|
+
res.json(result);
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
// POST /api/headroom/unwrap
|
|
55
|
+
router.post('/unwrap', wrap(async (_req, res) => {
|
|
56
|
+
const result = await unwrapCline();
|
|
57
|
+
res.json(result);
|
|
58
|
+
}));
|
|
59
|
+
|
|
60
|
+
// POST /api/headroom/proxy/start
|
|
61
|
+
router.post('/proxy/start', wrap(async (req, res) => {
|
|
62
|
+
const { port, host } = req.body || {};
|
|
63
|
+
const result = await startProxy({
|
|
64
|
+
port: port ? Math.max(1, Math.min(parseInt(port, 10), 65535)) : 8787,
|
|
65
|
+
host: typeof host === 'string' && host ? host : '127.0.0.1',
|
|
66
|
+
});
|
|
67
|
+
res.json(result);
|
|
68
|
+
}));
|
|
69
|
+
|
|
70
|
+
// POST /api/headroom/proxy/stop
|
|
71
|
+
router.post('/proxy/stop', wrap(async (_req, res) => {
|
|
72
|
+
const result = await stopProxy();
|
|
73
|
+
res.json(result);
|
|
74
|
+
}));
|
|
75
|
+
|
|
76
|
+
// GET /api/headroom/cline-config
|
|
77
|
+
router.get('/cline-config', wrap(async (_req, res) => {
|
|
78
|
+
const config = await getClineConfig();
|
|
79
|
+
res.json(config);
|
|
80
|
+
}));
|
|
81
|
+
|
|
82
|
+
// POST /api/headroom/auto-route — configure all providers to route through Headroom
|
|
83
|
+
router.post('/auto-route', wrap(async (_req, res) => {
|
|
84
|
+
const { loadConfig, saveConfig } = await import('../providers-store.mjs');
|
|
85
|
+
const cfg = loadConfig();
|
|
86
|
+
const headroomPort = 8787;
|
|
87
|
+
const proxyUrl = `http://127.0.0.1:${headroomPort}/v1`;
|
|
88
|
+
|
|
89
|
+
// For each provider, prepend the headroom proxy URL to baseURL
|
|
90
|
+
const providers = cfg.provider || {};
|
|
91
|
+
let changed = 0;
|
|
92
|
+
for (const [id, p] of Object.entries(providers)) {
|
|
93
|
+
if (!p || typeof p !== 'object') continue;
|
|
94
|
+
const baseURL = p.baseURL || p.options?.baseURL || '';
|
|
95
|
+
// Only update if not already routed
|
|
96
|
+
if (baseURL && !baseURL.includes('127.0.0.1:8787') && !baseURL.includes('localhost:8787')) {
|
|
97
|
+
const newBaseURL = `${proxyUrl}/${baseURL.replace(/^https?:\/\//, '')}`;
|
|
98
|
+
if (p.options) {
|
|
99
|
+
providers[id] = { ...p, options: { ...p.options, baseURL: newBaseURL } };
|
|
100
|
+
} else {
|
|
101
|
+
providers[id] = { ...p, baseURL: newBaseURL };
|
|
102
|
+
}
|
|
103
|
+
changed++;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
cfg.provider = providers;
|
|
107
|
+
saveConfig(cfg);
|
|
108
|
+
|
|
109
|
+
res.json({ ok: true, changed, message: `Updated ${changed} provider(s) to route through Headroom proxy.` });
|
|
110
|
+
}));
|
|
111
|
+
|
|
112
|
+
// POST /api/headroom/startup-hook — trigger the startup hook programmatically
|
|
113
|
+
router.post('/startup-hook', wrap(async (_req, res) => {
|
|
114
|
+
const { readSettings } = await import('./_shared.mjs');
|
|
115
|
+
const settings = readSettings();
|
|
116
|
+
const headroomSettings = settings?.data?.headroom;
|
|
117
|
+
if (!headroomSettings) {
|
|
118
|
+
res.status(400).json({ error: 'bad_request', message: 'Headroom settings not found in settings.json' });
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const result = await headroomStartupHook(headroomSettings);
|
|
122
|
+
res.json(result);
|
|
123
|
+
}));
|
|
124
|
+
|
|
125
|
+
return router;
|
|
126
|
+
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* GET /api/model-router — full policy + agent→model map
|
|
7
7
|
* GET /api/model-router/models — live model list from the local router
|
|
8
8
|
* GET /api/model-router/agents — agent→model map only
|
|
9
|
+
* GET /api/model-router/resolve/:agent — concrete model + endpoint for an agent
|
|
9
10
|
*
|
|
10
11
|
* The local router URL defaults to ANTHROPIC_BASE_URL or
|
|
11
12
|
* BIZAR_MODEL_ROUTER_URL or `http://localhost:20128/v1`.
|
|
@@ -67,6 +68,45 @@ async function fetchLiveModels(endpoint) {
|
|
|
67
68
|
}
|
|
68
69
|
}
|
|
69
70
|
|
|
71
|
+
/**
|
|
72
|
+
* v10.1.0 — Resolve the concrete model + endpoint for a named agent
|
|
73
|
+
* directly from the on-disk policy. No SDK import needed (keeps the
|
|
74
|
+
* dashboard route surface lightweight). Used by the front-end's
|
|
75
|
+
* "agent fleet" view, which renders one card per agent with its
|
|
76
|
+
* modelId + endpoint badge.
|
|
77
|
+
*/
|
|
78
|
+
function resolveAgentFromPolicy(policy, agent, endpoint) {
|
|
79
|
+
const agents = policy?.agents || {};
|
|
80
|
+
const entry = agents[agent];
|
|
81
|
+
if (entry && typeof entry.model === 'string') {
|
|
82
|
+
return {
|
|
83
|
+
agent,
|
|
84
|
+
modelId: entry.model,
|
|
85
|
+
tier: typeof entry.tier === 'string' ? entry.tier : 'default',
|
|
86
|
+
endpoint,
|
|
87
|
+
rationale: typeof entry.rationale === 'string' ? entry.rationale : '',
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
// Fallback: first known entry by name, otherwise the default tier.
|
|
91
|
+
const fallback = Object.values(agents).find((e) => e && typeof e.model === 'string');
|
|
92
|
+
if (fallback) {
|
|
93
|
+
return {
|
|
94
|
+
agent,
|
|
95
|
+
modelId: fallback.model,
|
|
96
|
+
tier: typeof fallback.tier === 'string' ? fallback.tier : 'default',
|
|
97
|
+
endpoint,
|
|
98
|
+
rationale: 'fallback (agent not in policy)',
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
agent,
|
|
103
|
+
modelId: 'bizar/MiniMax-M3',
|
|
104
|
+
tier: 'default',
|
|
105
|
+
endpoint,
|
|
106
|
+
rationale: 'fallback (no policy)',
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
70
110
|
/**
|
|
71
111
|
* @param {object} deps
|
|
72
112
|
* @param {object} deps.state
|
|
@@ -99,5 +139,20 @@ export function createModelRouterRouter({ state }) {
|
|
|
99
139
|
res.json({ agents: policy.agents || {}, tiers: policy.tiers || {} });
|
|
100
140
|
}));
|
|
101
141
|
|
|
142
|
+
// v10.1.0 — Resolve one agent to its concrete model + endpoint.
|
|
143
|
+
// The front-end's ModelRouter page calls this so it doesn't need to
|
|
144
|
+
// duplicate the registry logic client-side.
|
|
145
|
+
router.get('/model-router/resolve/:agent', wrap(async (req, res) => {
|
|
146
|
+
const agent = String(req.params.agent || '').trim();
|
|
147
|
+
if (!/^[a-z][a-z0-9_-]{0,63}$/i.test(agent)) {
|
|
148
|
+
res.status(400).json({ error: 'bad_slug', message: 'agent must match /^[a-z][a-z0-9_-]{0,63}$/' });
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const policy = await loadPolicy();
|
|
152
|
+
const endpoint = resolveEndpoint(state);
|
|
153
|
+
const resolved = resolveAgentFromPolicy(policy, agent, endpoint);
|
|
154
|
+
res.json(resolved);
|
|
155
|
+
}));
|
|
156
|
+
|
|
102
157
|
return router;
|
|
103
158
|
}
|
|
@@ -73,4 +73,32 @@ test('GET /api/model-router/models probe handles unreachable endpoint gracefully
|
|
|
73
73
|
if (prev === undefined) delete process.env.BIZAR_MODEL_ROUTER_URL;
|
|
74
74
|
else process.env.BIZAR_MODEL_ROUTER_URL = prev;
|
|
75
75
|
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('GET /api/model-router/resolve/:agent returns the concrete model + endpoint', async () => {
|
|
79
|
+
const app = buildApp();
|
|
80
|
+
const { status, body } = await getJson(app, '/api/model-router/resolve/tyr');
|
|
81
|
+
assert.equal(status, 200);
|
|
82
|
+
assert.equal(body.agent, 'tyr');
|
|
83
|
+
assert.match(body.modelId, /gpt-5\.6-terra/, 'tyr uses high-tier terra');
|
|
84
|
+
assert.match(body.endpoint, /^http/, 'endpoint is a URL');
|
|
85
|
+
assert.equal(typeof body.tier, 'string');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('GET /api/model-router/resolve/:agent falls back for unknown agents', async () => {
|
|
89
|
+
const app = buildApp();
|
|
90
|
+
const { status, body } = await getJson(app, '/api/model-router/resolve/nonexistent-agent-xyz');
|
|
91
|
+
assert.equal(status, 200);
|
|
92
|
+
assert.equal(body.agent, 'nonexistent-agent-xyz');
|
|
93
|
+
assert.match(body.modelId, /[a-z0-9/_.-]+/, 'returns a model id');
|
|
94
|
+
assert.match(body.endpoint, /^http/, 'endpoint is a URL');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('GET /api/model-router/resolve/:agent rejects bad slugs', async () => {
|
|
98
|
+
const app = buildApp();
|
|
99
|
+
// Express decodes percent-encoded path segments before the handler runs.
|
|
100
|
+
// Use a slug that's syntactically illegal per the route's regex.
|
|
101
|
+
const { status, body } = await getJson(app, '/api/model-router/resolve/has spaces');
|
|
102
|
+
assert.equal(status, 400);
|
|
103
|
+
assert.equal(body.error, 'bad_slug');
|
|
76
104
|
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/server/routes/sessions.mjs
|
|
3
|
+
*
|
|
4
|
+
* Pillar A — Session heartbeat list + stop.
|
|
5
|
+
*
|
|
6
|
+
* GET /api/sessions — list live sessions from .harness/traces/heartbeat.jsonl
|
|
7
|
+
* POST /api/sessions/:id/stop — mark a session as done
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Router } from "express";
|
|
11
|
+
import { existsSync, readFileSync, appendFileSync, mkdirSync } from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { wrap } from "./_shared.mjs";
|
|
14
|
+
|
|
15
|
+
const HEARTBEAT_FILE = ".harness/traces/heartbeat.jsonl";
|
|
16
|
+
|
|
17
|
+
function resolveHeartbeatPath(repoRoot) {
|
|
18
|
+
return join(repoRoot ?? process.cwd(), HEARTBEAT_FILE);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {string} repoRoot
|
|
23
|
+
* @returns {Array<{session_id: string, started_at: string, last_heartbeat_at: string, status: string}>}
|
|
24
|
+
*/
|
|
25
|
+
function readSessions(repoRoot) {
|
|
26
|
+
const fp = resolveHeartbeatPath(repoRoot);
|
|
27
|
+
if (!existsSync(fp)) return [];
|
|
28
|
+
try {
|
|
29
|
+
const raw = readFileSync(fp, "utf-8");
|
|
30
|
+
const lines = raw.split("\n").filter(Boolean);
|
|
31
|
+
// Deduplicate by session_id, keeping the last record per session
|
|
32
|
+
const map = new Map();
|
|
33
|
+
for (const line of lines) {
|
|
34
|
+
try {
|
|
35
|
+
const rec = JSON.parse(line);
|
|
36
|
+
if (rec?.session_id) map.set(rec.session_id, rec);
|
|
37
|
+
} catch {
|
|
38
|
+
// skip malformed lines
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return Array.from(map.values());
|
|
42
|
+
} catch {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function createSessionsRouter({ projectRoot = process.cwd() } = {}) {
|
|
48
|
+
const router = Router();
|
|
49
|
+
|
|
50
|
+
// GET /api/sessions
|
|
51
|
+
router.get("/sessions", wrap(async (_req, res) => {
|
|
52
|
+
const sessions = readSessions(projectRoot);
|
|
53
|
+
res.json({ sessions });
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
// POST /api/sessions/:id/stop
|
|
57
|
+
router.post("/sessions/:id/stop", wrap(async (req, res) => {
|
|
58
|
+
const id = String(req.params?.id ?? "").trim();
|
|
59
|
+
if (!id) {
|
|
60
|
+
res.status(400).json({ error: "bad_request", message: "session id is required" });
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const fp = resolveHeartbeatPath(projectRoot);
|
|
64
|
+
// Ensure parent dir exists
|
|
65
|
+
const dir = join(fp, "..");
|
|
66
|
+
if (!existsSync(dir)) {
|
|
67
|
+
mkdirSync(dir, { recursive: true });
|
|
68
|
+
}
|
|
69
|
+
const record = {
|
|
70
|
+
session_id: id,
|
|
71
|
+
started_at: new Date().toISOString(),
|
|
72
|
+
last_heartbeat_at: new Date().toISOString(),
|
|
73
|
+
status: "done",
|
|
74
|
+
};
|
|
75
|
+
appendFileSync(fp, JSON.stringify(record) + "\n", "utf-8");
|
|
76
|
+
res.json({ session_id: id, status: "done" });
|
|
77
|
+
}));
|
|
78
|
+
|
|
79
|
+
return router;
|
|
80
|
+
}
|