@ashlr/hub 3.13.0 → 3.14.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.
Files changed (625) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/dist/authority-surface.json +204 -124
  3. package/dist/build-identity.json +2 -2
  4. package/dist/cli/authority.js +112 -18
  5. package/dist/cli/authority.js.map +1 -1
  6. package/dist/cli/comms.d.ts +33 -19
  7. package/dist/cli/comms.js +128 -151
  8. package/dist/cli/comms.js.map +1 -1
  9. package/dist/cli/completions.js +1 -1
  10. package/dist/cli/completions.js.map +1 -1
  11. package/dist/cli/daemon.js +29 -0
  12. package/dist/cli/daemon.js.map +1 -1
  13. package/dist/cli/director.d.ts +8 -12
  14. package/dist/cli/director.js +17 -52
  15. package/dist/cli/director.js.map +1 -1
  16. package/dist/cli/fleet.js +4 -1
  17. package/dist/cli/fleet.js.map +1 -1
  18. package/dist/cli/help.js +1 -0
  19. package/dist/cli/help.js.map +1 -1
  20. package/dist/cli/leader.d.ts +27 -3
  21. package/dist/cli/leader.js +253 -4
  22. package/dist/cli/leader.js.map +1 -1
  23. package/dist/core/authority/server-enforcement.d.ts +110 -0
  24. package/dist/core/authority/server-enforcement.js +260 -0
  25. package/dist/core/authority/server-enforcement.js.map +1 -0
  26. package/dist/core/authority/standing-grant.d.ts +24 -4
  27. package/dist/core/authority/standing-grant.js +19 -3
  28. package/dist/core/authority/standing-grant.js.map +1 -1
  29. package/dist/core/cloud/timeline.js +13 -7
  30. package/dist/core/cloud/timeline.js.map +1 -1
  31. package/dist/core/comms/change-digest.d.ts +130 -0
  32. package/dist/core/comms/change-digest.js +451 -0
  33. package/dist/core/comms/change-digest.js.map +1 -0
  34. package/dist/core/comms/director-prompt.d.ts +4 -5
  35. package/dist/core/comms/director-prompt.js +4 -71
  36. package/dist/core/comms/director-prompt.js.map +1 -1
  37. package/dist/core/comms/director.d.ts +24 -61
  38. package/dist/core/comms/director.js +29 -382
  39. package/dist/core/comms/director.js.map +1 -1
  40. package/dist/core/comms/dispatch.d.ts +37 -12
  41. package/dist/core/comms/dispatch.js +244 -177
  42. package/dist/core/comms/dispatch.js.map +1 -1
  43. package/dist/core/comms/elon-dialogue.d.ts +23 -15
  44. package/dist/core/comms/elon-dialogue.js +36 -307
  45. package/dist/core/comms/elon-dialogue.js.map +1 -1
  46. package/dist/core/comms/fleet-pulse.js +4 -7
  47. package/dist/core/comms/fleet-pulse.js.map +1 -1
  48. package/dist/core/comms/handlers.d.ts +10 -6
  49. package/dist/core/comms/handlers.js +14 -48
  50. package/dist/core/comms/handlers.js.map +1 -1
  51. package/dist/core/comms/migrations.d.ts +47 -0
  52. package/dist/core/comms/migrations.js +125 -0
  53. package/dist/core/comms/migrations.js.map +1 -0
  54. package/dist/core/comms/requests.d.ts +32 -5
  55. package/dist/core/comms/requests.js +101 -5
  56. package/dist/core/comms/requests.js.map +1 -1
  57. package/dist/core/comms/telegram-channel.d.ts +104 -0
  58. package/dist/core/comms/telegram-channel.js +622 -0
  59. package/dist/core/comms/telegram-channel.js.map +1 -0
  60. package/dist/core/comms/telegram-thread-map.d.ts +56 -0
  61. package/dist/core/comms/telegram-thread-map.js +144 -0
  62. package/dist/core/comms/telegram-thread-map.js.map +1 -0
  63. package/dist/core/daemon/capacity-publisher.d.ts +8 -1
  64. package/dist/core/daemon/capacity-publisher.js +40 -2
  65. package/dist/core/daemon/capacity-publisher.js.map +1 -1
  66. package/dist/core/daemon/loop.js +18 -4
  67. package/dist/core/daemon/loop.js.map +1 -1
  68. package/dist/core/daemon/tick-deadline.d.ts +44 -0
  69. package/dist/core/daemon/tick-deadline.js +83 -0
  70. package/dist/core/daemon/tick-deadline.js.map +1 -0
  71. package/dist/core/daemon/tick-hooks.d.ts +7 -0
  72. package/dist/core/daemon/tick-hooks.js.map +1 -1
  73. package/dist/core/daemon/tick-progress.d.ts +39 -0
  74. package/dist/core/daemon/tick-progress.js +194 -0
  75. package/dist/core/daemon/tick-progress.js.map +1 -0
  76. package/dist/core/dashboard.js +3 -1
  77. package/dist/core/dashboard.js.map +1 -1
  78. package/dist/core/fleet/fleet-merge-state.d.ts +6 -0
  79. package/dist/core/fleet/fleet-merge-state.js.map +1 -1
  80. package/dist/core/fleet/host-merge.d.ts +9 -0
  81. package/dist/core/fleet/host-merge.js +13 -2
  82. package/dist/core/fleet/host-merge.js.map +1 -1
  83. package/dist/core/fleet/merge-gates.d.ts +13 -0
  84. package/dist/core/fleet/merge-gates.js +14 -0
  85. package/dist/core/fleet/merge-gates.js.map +1 -1
  86. package/dist/core/fleet/mirrors.d.ts +162 -6
  87. package/dist/core/fleet/mirrors.js +1051 -49
  88. package/dist/core/fleet/mirrors.js.map +1 -1
  89. package/dist/core/fleet/standing-merge-pass.d.ts +8 -0
  90. package/dist/core/fleet/standing-merge-pass.js +72 -1
  91. package/dist/core/fleet/standing-merge-pass.js.map +1 -1
  92. package/dist/core/fleet/status.d.ts +17 -0
  93. package/dist/core/fleet/status.js +22 -0
  94. package/dist/core/fleet/status.js.map +1 -1
  95. package/dist/core/fleet/tick-hooks-live.d.ts +1 -1
  96. package/dist/core/fleet/tick-hooks-live.js +13 -3
  97. package/dist/core/fleet/tick-hooks-live.js.map +1 -1
  98. package/dist/core/git.d.ts +17 -0
  99. package/dist/core/git.js +124 -25
  100. package/dist/core/git.js.map +1 -1
  101. package/dist/core/goals/open-goals.d.ts +47 -0
  102. package/dist/core/goals/open-goals.js +33 -0
  103. package/dist/core/goals/open-goals.js.map +1 -0
  104. package/dist/core/inbox/merge.d.ts +5 -0
  105. package/dist/core/inbox/merge.js +58 -2
  106. package/dist/core/inbox/merge.js.map +1 -1
  107. package/dist/core/integrations/locus.d.ts +28 -0
  108. package/dist/core/integrations/locus.js +155 -30
  109. package/dist/core/integrations/locus.js.map +1 -1
  110. package/dist/core/integrations/telegram-format.d.ts +40 -0
  111. package/dist/core/integrations/telegram-format.js +123 -0
  112. package/dist/core/integrations/telegram-format.js.map +1 -0
  113. package/dist/core/integrations/telegram.d.ts +60 -4
  114. package/dist/core/integrations/telegram.js +161 -28
  115. package/dist/core/integrations/telegram.js.map +1 -1
  116. package/dist/core/mcp-registry.d.ts +12 -0
  117. package/dist/core/mcp-registry.js +70 -0
  118. package/dist/core/mcp-registry.js.map +1 -1
  119. package/dist/core/observability/rollup.d.ts +12 -10
  120. package/dist/core/observability/rollup.js +44 -17
  121. package/dist/core/observability/rollup.js.map +1 -1
  122. package/dist/core/resources/connection-monitor.d.ts +9 -0
  123. package/dist/core/resources/connection-monitor.js +19 -9
  124. package/dist/core/resources/connection-monitor.js.map +1 -1
  125. package/dist/core/resources/quota-refresh-lease.d.ts +17 -0
  126. package/dist/core/resources/quota-refresh-lease.js +34 -1
  127. package/dist/core/resources/quota-refresh-lease.js.map +1 -1
  128. package/dist/core/routing/budget-api.d.ts +7 -1
  129. package/dist/core/routing/budget-api.js +56 -2
  130. package/dist/core/routing/budget-api.js.map +1 -1
  131. package/dist/core/routing/readiness-types.d.ts +82 -0
  132. package/dist/core/routing/readiness-types.js +23 -0
  133. package/dist/core/routing/readiness-types.js.map +1 -0
  134. package/dist/core/routing/readiness.d.ts +103 -0
  135. package/dist/core/routing/readiness.js +480 -0
  136. package/dist/core/routing/readiness.js.map +1 -0
  137. package/dist/core/sandbox/confine.js +22 -1
  138. package/dist/core/sandbox/confine.js.map +1 -1
  139. package/dist/core/sandbox/policy.d.ts +23 -0
  140. package/dist/core/sandbox/policy.js +100 -0
  141. package/dist/core/sandbox/policy.js.map +1 -1
  142. package/dist/core/types.d.ts +13 -0
  143. package/dist/core/types.js.map +1 -1
  144. package/dist/core/universe/builtins/preparation/manifest.json +1 -1
  145. package/dist/core/universe/builtins/preparation/preparation-bridge.mjs +1204 -939
  146. package/dist/core/universe/builtins/preparation/preparation-verification-fixtures.mjs +1319 -1054
  147. package/dist/core/usage/frontier-usage.d.ts +20 -2
  148. package/dist/core/usage/frontier-usage.js +75 -17
  149. package/dist/core/usage/frontier-usage.js.map +1 -1
  150. package/dist/core/verse/accounts.d.ts +45 -0
  151. package/dist/core/verse/accounts.js +246 -17
  152. package/dist/core/verse/accounts.js.map +1 -1
  153. package/dist/core/verse/apps-api.js +23 -11
  154. package/dist/core/verse/apps-api.js.map +1 -1
  155. package/dist/core/verse/apps.d.ts +11 -9
  156. package/dist/core/verse/apps.js +2 -2
  157. package/dist/core/verse/apps.js.map +1 -1
  158. package/dist/core/verse/authority-api.d.ts +8 -2
  159. package/dist/core/verse/authority-api.js +40 -5
  160. package/dist/core/verse/authority-api.js.map +1 -1
  161. package/dist/core/verse/autonomy-ladder.d.ts +172 -0
  162. package/dist/core/verse/autonomy-ladder.js +251 -0
  163. package/dist/core/verse/autonomy-ladder.js.map +1 -0
  164. package/dist/core/verse/context-fit.js +7 -5
  165. package/dist/core/verse/context-fit.js.map +1 -1
  166. package/dist/core/verse/control-api.d.ts +23 -1
  167. package/dist/core/verse/control-api.js +117 -10
  168. package/dist/core/verse/control-api.js.map +1 -1
  169. package/dist/core/verse/fleet-live-api.js +13 -5
  170. package/dist/core/verse/fleet-live-api.js.map +1 -1
  171. package/dist/core/verse/folder-io.d.ts +50 -0
  172. package/dist/core/verse/folder-io.js +142 -0
  173. package/dist/core/verse/folder-io.js.map +1 -0
  174. package/dist/core/verse/git-api.js +5 -4
  175. package/dist/core/verse/git-api.js.map +1 -1
  176. package/dist/core/verse/git-ops.d.ts +10 -1
  177. package/dist/core/verse/git-ops.js +18 -2
  178. package/dist/core/verse/git-ops.js.map +1 -1
  179. package/dist/core/verse/github-api.d.ts +1 -1
  180. package/dist/core/verse/github-api.js +5 -4
  181. package/dist/core/verse/github-api.js.map +1 -1
  182. package/dist/core/verse/github-repo.js +118 -14
  183. package/dist/core/verse/github-repo.js.map +1 -1
  184. package/dist/core/verse/leader-api.d.ts +5 -4
  185. package/dist/core/verse/leader-api.js +237 -10
  186. package/dist/core/verse/leader-api.js.map +1 -1
  187. package/dist/core/verse/local-models.d.ts +10 -0
  188. package/dist/core/verse/local-models.js +10 -1
  189. package/dist/core/verse/local-models.js.map +1 -1
  190. package/dist/core/verse/login-path.d.ts +73 -0
  191. package/dist/core/verse/login-path.js +123 -14
  192. package/dist/core/verse/login-path.js.map +1 -1
  193. package/dist/core/verse/mcp-control-api.js +4 -4
  194. package/dist/core/verse/mcp-control-api.js.map +1 -1
  195. package/dist/core/verse/mcp-scope.d.ts +22 -0
  196. package/dist/core/verse/mcp-scope.js +41 -1
  197. package/dist/core/verse/mcp-scope.js.map +1 -1
  198. package/dist/core/verse/mcp-seat-view.d.ts +10 -0
  199. package/dist/core/verse/mcp-seat-view.js +30 -4
  200. package/dist/core/verse/mcp-seat-view.js.map +1 -1
  201. package/dist/core/verse/overnight-api.js +20 -2
  202. package/dist/core/verse/overnight-api.js.map +1 -1
  203. package/dist/core/verse/path-guard.d.ts +24 -0
  204. package/dist/core/verse/path-guard.js +91 -22
  205. package/dist/core/verse/path-guard.js.map +1 -1
  206. package/dist/core/verse/preview-api.js +22 -0
  207. package/dist/core/verse/preview-api.js.map +1 -1
  208. package/dist/core/verse/projects.d.ts +21 -0
  209. package/dist/core/verse/projects.js +82 -0
  210. package/dist/core/verse/projects.js.map +1 -1
  211. package/dist/core/verse/session-controls-api.js +5 -3
  212. package/dist/core/verse/session-controls-api.js.map +1 -1
  213. package/dist/core/verse/session-handoff.d.ts +26 -0
  214. package/dist/core/verse/session-handoff.js +90 -12
  215. package/dist/core/verse/session-handoff.js.map +1 -1
  216. package/dist/core/verse/terminal-api.js +7 -4
  217. package/dist/core/verse/terminal-api.js.map +1 -1
  218. package/dist/core/verse/terminal.js +9 -5
  219. package/dist/core/verse/terminal.js.map +1 -1
  220. package/dist/core/verse/verse-api.js +81 -27
  221. package/dist/core/verse/verse-api.js.map +1 -1
  222. package/dist/core/verse/workspaces.d.ts +51 -0
  223. package/dist/core/verse/workspaces.js +177 -47
  224. package/dist/core/verse/workspaces.js.map +1 -1
  225. package/dist/core/vision/leader-apply.d.ts +40 -0
  226. package/dist/core/vision/leader-apply.js +101 -30
  227. package/dist/core/vision/leader-apply.js.map +1 -1
  228. package/dist/core/vision/leader-cadence.d.ts +114 -0
  229. package/dist/core/vision/leader-cadence.js +127 -0
  230. package/dist/core/vision/leader-cadence.js.map +1 -0
  231. package/dist/core/vision/leader-goal-evidence.d.ts +25 -0
  232. package/dist/core/vision/leader-goal-evidence.js +36 -0
  233. package/dist/core/vision/leader-goal-evidence.js.map +1 -0
  234. package/dist/core/vision/leader-health.d.ts +53 -0
  235. package/dist/core/vision/leader-health.js +92 -0
  236. package/dist/core/vision/leader-health.js.map +1 -0
  237. package/dist/core/vision/leader-operator.d.ts +176 -0
  238. package/dist/core/vision/leader-operator.js +355 -0
  239. package/dist/core/vision/leader-operator.js.map +1 -0
  240. package/dist/core/vision/leader-run-chain.d.ts +41 -0
  241. package/dist/core/vision/leader-run-chain.js +54 -0
  242. package/dist/core/vision/leader-run-chain.js.map +1 -0
  243. package/dist/core/vision/leader-seat-plan.d.ts +68 -0
  244. package/dist/core/vision/leader-seat-plan.js +93 -0
  245. package/dist/core/vision/leader-seat-plan.js.map +1 -0
  246. package/dist/core/vision/leader-seat.d.ts +71 -5
  247. package/dist/core/vision/leader-seat.js +235 -33
  248. package/dist/core/vision/leader-seat.js.map +1 -1
  249. package/dist/core/vision/leader-thread-types.d.ts +104 -0
  250. package/dist/core/vision/leader-thread-types.js +9 -0
  251. package/dist/core/vision/leader-thread-types.js.map +1 -0
  252. package/dist/core/vision/leader-thread.d.ts +138 -0
  253. package/dist/core/vision/leader-thread.js +926 -0
  254. package/dist/core/vision/leader-thread.js.map +1 -0
  255. package/dist/core/vision/leader-types.d.ts +75 -1
  256. package/dist/core/vision/leader.d.ts +88 -18
  257. package/dist/core/vision/leader.js +269 -69
  258. package/dist/core/vision/leader.js.map +1 -1
  259. package/dist/core/web/public/next/assets/{ActionMenu-Dwjk6rUQ.js → ActionMenu-CIiX2X8w.js} +2 -2
  260. package/dist/core/web/public/next/assets/{ActionMenu-Dwjk6rUQ.js.map → ActionMenu-CIiX2X8w.js.map} +1 -1
  261. package/dist/core/web/public/next/assets/Advanced-DHsysoIL.js +2 -0
  262. package/dist/core/web/public/next/assets/{Advanced-cVYTAsfB.js.map → Advanced-DHsysoIL.js.map} +1 -1
  263. package/dist/core/web/public/next/assets/{App-IsaWDsdF.js → App-CMeAocmW.js} +5 -5
  264. package/dist/core/web/public/next/assets/{App-IsaWDsdF.js.map → App-CMeAocmW.js.map} +1 -1
  265. package/dist/core/web/public/next/assets/AppsSection-DISYnMnc.js +2 -0
  266. package/dist/core/web/public/next/assets/{AppsSection-DDqeSrty.js.map → AppsSection-DISYnMnc.js.map} +1 -1
  267. package/dist/core/web/public/next/assets/AreaTrend-DK2Y2TbZ.js +2 -0
  268. package/dist/core/web/public/next/assets/AreaTrend-DK2Y2TbZ.js.map +1 -0
  269. package/dist/core/web/public/next/assets/{AutonomyOffState-CpFYS3tQ.js → AutonomyOffState-CEPSZToP.js} +3 -3
  270. package/dist/core/web/public/next/assets/{AutonomyOffState-CpFYS3tQ.js.map → AutonomyOffState-CEPSZToP.js.map} +1 -1
  271. package/dist/core/web/public/next/assets/AutonomySection-DWc3ciAD.js +2 -0
  272. package/dist/core/web/public/next/assets/{AutonomySection-Dwn8UtOy.js.map → AutonomySection-DWc3ciAD.js.map} +1 -1
  273. package/dist/core/web/public/next/assets/AutonomyStatus-CYaHjJ9U.js +2 -0
  274. package/dist/core/web/public/next/assets/AutonomyStatus-CYaHjJ9U.js.map +1 -0
  275. package/dist/core/web/public/next/assets/{BranchBar-ISZ7xUCj.js → BranchBar-BxhdiIpx.js} +2 -2
  276. package/dist/core/web/public/next/assets/{BranchBar-ISZ7xUCj.js.map → BranchBar-BxhdiIpx.js.map} +1 -1
  277. package/dist/core/web/public/next/assets/{BudgetControl-BARVp-En.js → BudgetControl-C-0KhjRS.js} +2 -2
  278. package/dist/core/web/public/next/assets/{BudgetControl-BARVp-En.js.map → BudgetControl-C-0KhjRS.js.map} +1 -1
  279. package/dist/core/web/public/next/assets/{Button-DtE0rlWD.js → Button-D90dzI_o.js} +2 -2
  280. package/dist/core/web/public/next/assets/{Button-DtE0rlWD.js.map → Button-D90dzI_o.js.map} +1 -1
  281. package/dist/core/web/public/next/assets/{CapacityStrip-DKbKA_Aa.js → CapacityStrip-qKDBC65C.js} +2 -2
  282. package/dist/core/web/public/next/assets/{CapacityStrip-DKbKA_Aa.js.map → CapacityStrip-qKDBC65C.js.map} +1 -1
  283. package/dist/core/web/public/next/assets/ChartFrame-DY-GVUb3.js +2 -0
  284. package/dist/core/web/public/next/assets/ChartFrame-DY-GVUb3.js.map +1 -0
  285. package/dist/core/web/public/next/assets/ChartFrame-DyjmyV8N.css +1 -0
  286. package/dist/core/web/public/next/assets/ChatSection-DlT4QU6a.js +3 -0
  287. package/dist/core/web/public/next/assets/{ChatSection-C9myY4ei.js.map → ChatSection-DlT4QU6a.js.map} +1 -1
  288. package/dist/core/web/public/next/assets/{CloudBudgetForm-CvjK4Oh6.js → CloudBudgetForm-BrNun_Yu.js} +2 -2
  289. package/dist/core/web/public/next/assets/{CloudBudgetForm-CvjK4Oh6.js.map → CloudBudgetForm-BrNun_Yu.js.map} +1 -1
  290. package/dist/core/web/public/next/assets/CommandPalette-C5lDDKN0.js +2 -0
  291. package/dist/core/web/public/next/assets/CommandPalette-C5lDDKN0.js.map +1 -0
  292. package/dist/core/web/public/next/assets/CommandSection-B0xuU-yZ.css +1 -0
  293. package/dist/core/web/public/next/assets/CommandSection-DXFvPgKp.js +3 -0
  294. package/dist/core/web/public/next/assets/CommandSection-DXFvPgKp.js.map +1 -0
  295. package/dist/core/web/public/next/assets/{ConfirmDialog-CLUhzaBj.js → ConfirmDialog-BR4nZQ2D.js} +2 -2
  296. package/dist/core/web/public/next/assets/{ConfirmDialog-CLUhzaBj.js.map → ConfirmDialog-BR4nZQ2D.js.map} +1 -1
  297. package/dist/core/web/public/next/assets/{DeleteChatDialog-C_eDhV8b.js → DeleteChatDialog-BCfP12GH.js} +2 -2
  298. package/dist/core/web/public/next/assets/{DeleteChatDialog-C_eDhV8b.js.map → DeleteChatDialog-BCfP12GH.js.map} +1 -1
  299. package/dist/core/web/public/next/assets/{Dialog-BRd_B9OE.js → Dialog-D3msQlCj.js} +2 -2
  300. package/dist/core/web/public/next/assets/{Dialog-BRd_B9OE.js.map → Dialog-D3msQlCj.js.map} +1 -1
  301. package/dist/core/web/public/next/assets/{DiffPane-Cpr9soB2.js → DiffPane-Ix6hvXlY.js} +2 -2
  302. package/dist/core/web/public/next/assets/{DiffPane-Cpr9soB2.js.map → DiffPane-Ix6hvXlY.js.map} +1 -1
  303. package/dist/core/web/public/next/assets/{DiffViewer-CNwd0W3Q.js → DiffViewer-C4WKsCIe.js} +2 -2
  304. package/dist/core/web/public/next/assets/{DiffViewer-CNwd0W3Q.js.map → DiffViewer-C4WKsCIe.js.map} +1 -1
  305. package/dist/core/web/public/next/assets/DockHost-aWP7liri.js +2 -0
  306. package/dist/core/web/public/next/assets/{DockHost-BcEq2qaS.js.map → DockHost-aWP7liri.js.map} +1 -1
  307. package/dist/core/web/public/next/assets/{EmptyState-DsN1H6MC.js → EmptyState-D2IRy3Cg.js} +2 -2
  308. package/dist/core/web/public/next/assets/{EmptyState-DsN1H6MC.js.map → EmptyState-D2IRy3Cg.js.map} +1 -1
  309. package/dist/core/web/public/next/assets/{Epistemic-fycQTBRO.js → Epistemic-DMr18muX.js} +2 -2
  310. package/dist/core/web/public/next/assets/{Epistemic-fycQTBRO.js.map → Epistemic-DMr18muX.js.map} +1 -1
  311. package/dist/core/web/public/next/assets/{EvidenceTimeline-sCCAdnBK.js → EvidenceTimeline-DXmmz6Fi.js} +2 -2
  312. package/dist/core/web/public/next/assets/{EvidenceTimeline-sCCAdnBK.js.map → EvidenceTimeline-DXmmz6Fi.js.map} +1 -1
  313. package/dist/core/web/public/next/assets/FleetSection-BfplcwFU.js +3 -0
  314. package/dist/core/web/public/next/assets/FleetSection-BfplcwFU.js.map +1 -0
  315. package/dist/core/web/public/next/assets/Funnel-Dh2yVHws.js +2 -0
  316. package/dist/core/web/public/next/assets/Funnel-Dh2yVHws.js.map +1 -0
  317. package/dist/core/web/public/next/assets/{GearTray-CpDt0xwj.js → GearTray-Dt-ee32-.js} +2 -2
  318. package/dist/core/web/public/next/assets/{GearTray-CpDt0xwj.js.map → GearTray-Dt-ee32-.js.map} +1 -1
  319. package/dist/core/web/public/next/assets/GrowthSection-P9EUfGeA.js +2 -0
  320. package/dist/core/web/public/next/assets/{GrowthSection-BvZezOsX.js.map → GrowthSection-P9EUfGeA.js.map} +1 -1
  321. package/dist/core/web/public/next/assets/{HandoffDialog-B1G6gQyk.js → HandoffDialog-CLJQLe7M.js} +2 -2
  322. package/dist/core/web/public/next/assets/{HandoffDialog-B1G6gQyk.js.map → HandoffDialog-CLJQLe7M.js.map} +1 -1
  323. package/dist/core/web/public/next/assets/{Input-CI0yBHmn.js → Input-D_KRJA9V.js} +2 -2
  324. package/dist/core/web/public/next/assets/{Input-CI0yBHmn.js.map → Input-D_KRJA9V.js.map} +1 -1
  325. package/dist/core/web/public/next/assets/LeaderCard-D-7DlJuR.js +2 -0
  326. package/dist/core/web/public/next/assets/LeaderCard-D-7DlJuR.js.map +1 -0
  327. package/dist/core/web/public/next/assets/LeaderCard-DNsbLwY3.css +1 -0
  328. package/dist/core/web/public/next/assets/LeaderConversation-DGZjWI-E.js +2 -0
  329. package/dist/core/web/public/next/assets/LeaderConversation-DGZjWI-E.js.map +1 -0
  330. package/dist/core/web/public/next/assets/LeaderConversation-DPHUTq6W.css +1 -0
  331. package/dist/core/web/public/next/assets/MatrixHeatmap-C7BCG2d7.js +2 -0
  332. package/dist/core/web/public/next/assets/MatrixHeatmap-C7BCG2d7.js.map +1 -0
  333. package/dist/core/web/public/next/assets/McpSection-DGbRcyv8.js +2 -0
  334. package/dist/core/web/public/next/assets/{McpSection-nuXer4K-.js.map → McpSection-DGbRcyv8.js.map} +1 -1
  335. package/dist/core/web/public/next/assets/{MessageMarkdown-BCe4TsXk.js → MessageMarkdown-DFvk06qG.js} +3 -3
  336. package/dist/core/web/public/next/assets/{MessageMarkdown-BCe4TsXk.js.map → MessageMarkdown-DFvk06qG.js.map} +1 -1
  337. package/dist/core/web/public/next/assets/{MessageMarkdownRenderer-DL3LU5kL.js → MessageMarkdownRenderer-BAv4oEUf.js} +2 -2
  338. package/dist/core/web/public/next/assets/{MessageMarkdownRenderer-DL3LU5kL.js.map → MessageMarkdownRenderer-BAv4oEUf.js.map} +1 -1
  339. package/dist/core/web/public/next/assets/{Meter-b69LM0mU.js → Meter-CMj-VafN.js} +2 -2
  340. package/dist/core/web/public/next/assets/{Meter-b69LM0mU.js.map → Meter-CMj-VafN.js.map} +1 -1
  341. package/dist/core/web/public/next/assets/MindSection-C6kLPIjt.js +3 -0
  342. package/dist/core/web/public/next/assets/MindSection-C6kLPIjt.js.map +1 -0
  343. package/dist/core/web/public/next/assets/{MutationTokenDialog-Bm34BNVV.js → MutationTokenDialog-DbnO8aYi.js} +2 -2
  344. package/dist/core/web/public/next/assets/{MutationTokenDialog-Bm34BNVV.js.map → MutationTokenDialog-DbnO8aYi.js.map} +1 -1
  345. package/dist/core/web/public/next/assets/NeedsYouDrawer-crsiX-Nc.js +3 -0
  346. package/dist/core/web/public/next/assets/NeedsYouDrawer-crsiX-Nc.js.map +1 -0
  347. package/dist/core/web/public/next/assets/{NeedsYouList-D5Kk549g.js → NeedsYouList-CCsijdWt.js} +2 -2
  348. package/dist/core/web/public/next/assets/{NeedsYouList-D5Kk549g.js.map → NeedsYouList-CCsijdWt.js.map} +1 -1
  349. package/dist/core/web/public/next/assets/NewChatDialog-BlCHtOIr.js +3 -0
  350. package/dist/core/web/public/next/assets/{NewChatDialog-BqFTWgHU.js.map → NewChatDialog-BlCHtOIr.js.map} +1 -1
  351. package/dist/core/web/public/next/assets/{OnboardingFlow-DcDsguAM.js → OnboardingFlow-Cy9BA9eR.js} +3 -3
  352. package/dist/core/web/public/next/assets/{OnboardingFlow-DcDsguAM.js.map → OnboardingFlow-Cy9BA9eR.js.map} +1 -1
  353. package/dist/core/web/public/next/assets/OnboardingGate-BXpNFoLt.js +3 -0
  354. package/dist/core/web/public/next/assets/{OnboardingGate-BZafM1Vf.js.map → OnboardingGate-BXpNFoLt.js.map} +1 -1
  355. package/dist/core/web/public/next/assets/PreviewPane-COid5A0f.js +2 -0
  356. package/dist/core/web/public/next/assets/{PreviewPane-zT3gK9TD.js.map → PreviewPane-COid5A0f.js.map} +1 -1
  357. package/dist/core/web/public/next/assets/{ProviderLogo-AwHn2sKm.js → ProviderLogo-Cq-CYIwT.js} +2 -2
  358. package/dist/core/web/public/next/assets/{ProviderLogo-AwHn2sKm.js.map → ProviderLogo-Cq-CYIwT.js.map} +1 -1
  359. package/dist/core/web/public/next/assets/{RailStatus-xHrovWvI.js → RailStatus-Co5UB6We.js} +2 -2
  360. package/dist/core/web/public/next/assets/{RailStatus-xHrovWvI.js.map → RailStatus-Co5UB6We.js.map} +1 -1
  361. package/dist/core/web/public/next/assets/{RefreshIndicator-SEFZlMB2.js → RefreshIndicator-DO9ibnSq.js} +2 -2
  362. package/dist/core/web/public/next/assets/{RefreshIndicator-SEFZlMB2.js.map → RefreshIndicator-DO9ibnSq.js.map} +1 -1
  363. package/dist/core/web/public/next/assets/ResourcePoolConsoleApp-Dr2WRgkq.js +7 -0
  364. package/dist/core/web/public/next/assets/{ResourcePoolConsoleApp-CAxPQ4sZ.js.map → ResourcePoolConsoleApp-Dr2WRgkq.js.map} +1 -1
  365. package/dist/core/web/public/next/assets/ResourcesChrome-CwMnbqI1.js +3 -0
  366. package/dist/core/web/public/next/assets/ResourcesChrome-CwMnbqI1.js.map +1 -0
  367. package/dist/core/web/public/next/assets/ResourcesDrawer-B_Xp_JNc.js +2 -0
  368. package/dist/core/web/public/next/assets/ResourcesDrawer-B_Xp_JNc.js.map +1 -0
  369. package/dist/core/web/public/next/assets/ResourcesDrawer-DrGoO8KG.css +1 -0
  370. package/dist/core/web/public/next/assets/{RouteErrorBoundary-C8WGiixc.js → RouteErrorBoundary-SkNhhUbt.js} +2 -2
  371. package/dist/core/web/public/next/assets/{RouteErrorBoundary-C8WGiixc.js.map → RouteErrorBoundary-SkNhhUbt.js.map} +1 -1
  372. package/dist/core/web/public/next/assets/{RunInCloudAction-1-IPLYhf.js → RunInCloudAction-BXPlZwJQ.js} +2 -2
  373. package/dist/core/web/public/next/assets/{RunInCloudAction-1-IPLYhf.js.map → RunInCloudAction-BXPlZwJQ.js.map} +1 -1
  374. package/dist/core/web/public/next/assets/{SeatSelector-DpwNiX8F.js → SeatSelector-J5R3zDkB.js} +3 -3
  375. package/dist/core/web/public/next/assets/{SeatSelector-DpwNiX8F.js.map → SeatSelector-J5R3zDkB.js.map} +1 -1
  376. package/dist/core/web/public/next/assets/{Segmented-Wt1ATod9.js → Segmented-CGh_-tBb.js} +2 -2
  377. package/dist/core/web/public/next/assets/{Segmented-Wt1ATod9.js.map → Segmented-CGh_-tBb.js.map} +1 -1
  378. package/dist/core/web/public/next/assets/{Select-By-V9lMc.js → Select-CWnmUzs3.js} +2 -2
  379. package/dist/core/web/public/next/assets/{Select-By-V9lMc.js.map → Select-CWnmUzs3.js.map} +1 -1
  380. package/dist/core/web/public/next/assets/{SessionGate-WROS02tK.js → SessionGate-sp4KsnUo.js} +2 -2
  381. package/dist/core/web/public/next/assets/{SessionGate-WROS02tK.js.map → SessionGate-sp4KsnUo.js.map} +1 -1
  382. package/dist/core/web/public/next/assets/{SessionInsightChip-DNUcruwp.js → SessionInsightChip-DLFAzdbi.js} +2 -2
  383. package/dist/core/web/public/next/assets/{SessionInsightChip-DNUcruwp.js.map → SessionInsightChip-DLFAzdbi.js.map} +1 -1
  384. package/dist/core/web/public/next/assets/{SessionSearch-UHtt7a8f.js → SessionSearch-DbFjwR1h.js} +2 -2
  385. package/dist/core/web/public/next/assets/{SessionSearch-UHtt7a8f.js.map → SessionSearch-DbFjwR1h.js.map} +1 -1
  386. package/dist/core/web/public/next/assets/{SettingsSection-Bf74l0Gj.js → SettingsSection-Ch23msJ5.js} +2 -2
  387. package/dist/core/web/public/next/assets/SettingsSection-Ch23msJ5.js.map +1 -0
  388. package/dist/core/web/public/next/assets/{SetupChecklist-CzpEcfOl.js → SetupChecklist-Bd76MBAR.js} +2 -2
  389. package/dist/core/web/public/next/assets/{SetupChecklist-CzpEcfOl.js.map → SetupChecklist-Bd76MBAR.js.map} +1 -1
  390. package/dist/core/web/public/next/assets/ShadowDecisions-C5RAY91f.js +3 -0
  391. package/dist/core/web/public/next/assets/ShadowDecisions-C5RAY91f.js.map +1 -0
  392. package/dist/core/web/public/next/assets/{Sheet-HkBOBMTT.js → Sheet-BZ2AG4Eo.js} +2 -2
  393. package/dist/core/web/public/next/assets/{Sheet-HkBOBMTT.js.map → Sheet-BZ2AG4Eo.js.map} +1 -1
  394. package/dist/core/web/public/next/assets/{ShortcutsOverlay-DiigiT8H.js → ShortcutsOverlay-C8n8y3zf.js} +2 -2
  395. package/dist/core/web/public/next/assets/{ShortcutsOverlay-DiigiT8H.js.map → ShortcutsOverlay-C8n8y3zf.js.map} +1 -1
  396. package/dist/core/web/public/next/assets/Sidebar-DN--cPYN.js +3 -0
  397. package/dist/core/web/public/next/assets/{Sidebar-C9YtBYVZ.js.map → Sidebar-DN--cPYN.js.map} +1 -1
  398. package/dist/core/web/public/next/assets/{Slider-DaEDws-b.js → Slider-KVoJLDD6.js} +2 -2
  399. package/dist/core/web/public/next/assets/{Slider-DaEDws-b.js.map → Slider-KVoJLDD6.js.map} +1 -1
  400. package/dist/core/web/public/next/assets/StatTile-C4KgjltA.js +2 -0
  401. package/dist/core/web/public/next/assets/StatTile-C4KgjltA.js.map +1 -0
  402. package/dist/core/web/public/next/assets/StatTile-CNCqDoPO.css +1 -0
  403. package/dist/core/web/public/next/assets/{StatusBadge-CXG3iNL4.js → StatusBadge-CXsYqmdU.js} +2 -2
  404. package/dist/core/web/public/next/assets/{StatusBadge-CXG3iNL4.js.map → StatusBadge-CXsYqmdU.js.map} +1 -1
  405. package/dist/core/web/public/next/assets/StepBand-BMtVOFpK.js +2 -0
  406. package/dist/core/web/public/next/assets/StepBand-BMtVOFpK.js.map +1 -0
  407. package/dist/core/web/public/next/assets/{Surface-5bNA08tK.js → Surface-BfDmQcKe.js} +2 -2
  408. package/dist/core/web/public/next/assets/{Surface-5bNA08tK.js.map → Surface-BfDmQcKe.js.map} +1 -1
  409. package/dist/core/web/public/next/assets/Swimlane-B9Exxekk.js +2 -0
  410. package/dist/core/web/public/next/assets/Swimlane-B9Exxekk.js.map +1 -0
  411. package/dist/core/web/public/next/assets/Swimlane-Bf4Rjoy9.css +1 -0
  412. package/dist/core/web/public/next/assets/{Switch-BmCzfFnu.js → Switch-9NtO_6Lj.js} +2 -2
  413. package/dist/core/web/public/next/assets/{Switch-BmCzfFnu.js.map → Switch-9NtO_6Lj.js.map} +1 -1
  414. package/dist/core/web/public/next/assets/{Tag-WSUIaMVy.js → Tag-DscCRFcN.js} +2 -2
  415. package/dist/core/web/public/next/assets/{Tag-WSUIaMVy.js.map → Tag-DscCRFcN.js.map} +1 -1
  416. package/dist/core/web/public/next/assets/{TerminalPane-BVFgYX6K.js → TerminalPane-B668-DJW.js} +5 -5
  417. package/dist/core/web/public/next/assets/{TerminalPane-BVFgYX6K.js.map → TerminalPane-B668-DJW.js.map} +1 -1
  418. package/dist/core/web/public/next/assets/UniverseConsoleApp-BUV_brY7.js +2 -0
  419. package/dist/core/web/public/next/assets/{UniverseConsoleApp-BpphLqoz.js.map → UniverseConsoleApp-BUV_brY7.js.map} +1 -1
  420. package/dist/core/web/public/next/assets/{UniverseView-DbpbSMwO.js → UniverseView-DG70zH0W.js} +4 -4
  421. package/dist/core/web/public/next/assets/{UniverseView-DbpbSMwO.js.map → UniverseView-DG70zH0W.js.map} +1 -1
  422. package/dist/core/web/public/next/assets/UsageSection-DY7uOTm1.js +3 -0
  423. package/dist/core/web/public/next/assets/UsageSection-DY7uOTm1.js.map +1 -0
  424. package/dist/core/web/public/next/assets/VerseConsoleApp-CWU6R3By.js +3 -0
  425. package/dist/core/web/public/next/assets/VerseConsoleApp-CWU6R3By.js.map +1 -0
  426. package/dist/core/web/public/next/assets/Workspace-D1nP9BhD.js +10 -0
  427. package/dist/core/web/public/next/assets/{Workspace-RXy0pqp6.js.map → Workspace-D1nP9BhD.js.map} +1 -1
  428. package/dist/core/web/public/next/assets/actions-CY-PjpGc.js +2 -0
  429. package/dist/core/web/public/next/assets/{actions-BZTidCXZ.js.map → actions-CY-PjpGc.js.map} +1 -1
  430. package/dist/core/web/public/next/assets/actions-DkWhhWIg.css +1 -0
  431. package/dist/core/web/public/next/assets/{apps-model-CHO133qt.js → apps-model-Du02KhAf.js} +2 -2
  432. package/dist/core/web/public/next/assets/{apps-model-CHO133qt.js.map → apps-model-Du02KhAf.js.map} +1 -1
  433. package/dist/core/web/public/next/assets/{authority-model-8NeAEwAF.js → authority-model-79SeuKlJ.js} +2 -2
  434. package/dist/core/web/public/next/assets/{authority-model-8NeAEwAF.js.map → authority-model-79SeuKlJ.js.map} +1 -1
  435. package/dist/core/web/public/next/assets/autonomy-ladder-BGWmZzP7.css +1 -0
  436. package/dist/core/web/public/next/assets/autonomy-ladder.module-ArDv4bkr.js +2 -0
  437. package/dist/core/web/public/next/assets/autonomy-ladder.module-ArDv4bkr.js.map +1 -0
  438. package/dist/core/web/public/next/assets/budget-queries-D8QV5XtH.js +2 -0
  439. package/dist/core/web/public/next/assets/{budget-queries-DFF-3ICg.js.map → budget-queries-D8QV5XtH.js.map} +1 -1
  440. package/dist/core/web/public/next/assets/{capacity-strip-model-5ViUgUlP.js → capacity-strip-model-BwZ1ZbkU.js} +2 -2
  441. package/dist/core/web/public/next/assets/{capacity-strip-model-5ViUgUlP.js.map → capacity-strip-model-BwZ1ZbkU.js.map} +1 -1
  442. package/dist/core/web/public/next/assets/charts-BZ50mZD_.js +2 -0
  443. package/dist/core/web/public/next/assets/charts-BZ50mZD_.js.map +1 -0
  444. package/dist/core/web/public/next/assets/charts-ClCB18la.css +1 -0
  445. package/dist/core/web/public/next/assets/{cloud-model-CfRydq8N.js → cloud-model-BDivRMqv.js} +2 -2
  446. package/dist/core/web/public/next/assets/{cloud-model-CfRydq8N.js.map → cloud-model-BDivRMqv.js.map} +1 -1
  447. package/dist/core/web/public/next/assets/{cloud-queries-DmptbjS8.js → cloud-queries-BPQQ5_pJ.js} +2 -2
  448. package/dist/core/web/public/next/assets/{cloud-queries-DmptbjS8.js.map → cloud-queries-BPQQ5_pJ.js.map} +1 -1
  449. package/dist/core/web/public/next/assets/{colors-BZ3a9wFf.js → colors-DvK0wMHH.js} +2 -2
  450. package/dist/core/web/public/next/assets/{colors-BZ3a9wFf.js.map → colors-DvK0wMHH.js.map} +1 -1
  451. package/dist/core/web/public/next/assets/{command-catalog-ffCAijVY.js → command-catalog-BIntSa9l.js} +2 -2
  452. package/dist/core/web/public/next/assets/command-catalog-BIntSa9l.js.map +1 -0
  453. package/dist/core/web/public/next/assets/{context-model-CDeyP0cA.js → context-model-Br8t9jgG.js} +2 -2
  454. package/dist/core/web/public/next/assets/{context-model-CDeyP0cA.js.map → context-model-Br8t9jgG.js.map} +1 -1
  455. package/dist/core/web/public/next/assets/{context-model-Bb15chFt.js → context-model-CokrgEUq.js} +3 -3
  456. package/dist/core/web/public/next/assets/{context-model-Bb15chFt.js.map → context-model-CokrgEUq.js.map} +1 -1
  457. package/dist/core/web/public/next/assets/context-queries-eL3caKVG.js +2 -0
  458. package/dist/core/web/public/next/assets/{context-queries-_9bFWb9v.js.map → context-queries-eL3caKVG.js.map} +1 -1
  459. package/dist/core/web/public/next/assets/{control-queries-CXtmZZH6.js → control-queries-B9K-wmcB.js} +2 -2
  460. package/dist/core/web/public/next/assets/{control-queries-CXtmZZH6.js.map → control-queries-B9K-wmcB.js.map} +1 -1
  461. package/dist/core/web/public/next/assets/{copy-setup-BIp0iaiZ.js → copy-setup-XMgCC5f8.js} +2 -2
  462. package/dist/core/web/public/next/assets/{copy-setup-BIp0iaiZ.js.map → copy-setup-XMgCC5f8.js.map} +1 -1
  463. package/dist/core/web/public/next/assets/{dock-icons-Cc43W7r4.js → dock-icons-BJ6ZqB2t.js} +2 -2
  464. package/dist/core/web/public/next/assets/{dock-icons-Cc43W7r4.js.map → dock-icons-BJ6ZqB2t.js.map} +1 -1
  465. package/dist/core/web/public/next/assets/{fleet-model-DOK8ZtEt.js → fleet-model-BvDR3gjp.js} +2 -2
  466. package/dist/core/web/public/next/assets/{fleet-model-DOK8ZtEt.js.map → fleet-model-BvDR3gjp.js.map} +1 -1
  467. package/dist/core/web/public/next/assets/{fleet-queries-BeEAqD6y.js → fleet-queries-W2UFVMZJ.js} +2 -2
  468. package/dist/core/web/public/next/assets/{fleet-queries-BeEAqD6y.js.map → fleet-queries-W2UFVMZJ.js.map} +1 -1
  469. package/dist/core/web/public/next/assets/{focus-trap-ChZ5EFfM.js → focus-trap-CtI1SlMX.js} +2 -2
  470. package/dist/core/web/public/next/assets/{focus-trap-ChZ5EFfM.js.map → focus-trap-CtI1SlMX.js.map} +1 -1
  471. package/dist/core/web/public/next/assets/{format-BmH2yWL6.js → format-CH4g_sl1.js} +2 -2
  472. package/dist/core/web/public/next/assets/{format-BmH2yWL6.js.map → format-CH4g_sl1.js.map} +1 -1
  473. package/dist/core/web/public/next/assets/{format-DKrNjdwD.js → format-D7nBg2bn.js} +2 -2
  474. package/dist/core/web/public/next/assets/{format-DKrNjdwD.js.map → format-D7nBg2bn.js.map} +1 -1
  475. package/dist/core/web/public/next/assets/{git-model-BFwAVbxF.js → git-model-Dnkd1TnK.js} +2 -2
  476. package/dist/core/web/public/next/assets/{git-model-BFwAVbxF.js.map → git-model-Dnkd1TnK.js.map} +1 -1
  477. package/dist/core/web/public/next/assets/global-CMNW_dqn.js +2 -0
  478. package/dist/core/web/public/next/assets/{global-y_exIyAP.js.map → global-CMNW_dqn.js.map} +1 -1
  479. package/dist/core/web/public/next/assets/guarded-runners-YeAc9wLQ.js +2 -0
  480. package/dist/core/web/public/next/assets/{guarded-runners-Cd4POBtI.js.map → guarded-runners-YeAc9wLQ.js.map} +1 -1
  481. package/dist/core/web/public/next/assets/{icons-CygCEFWs.js → icons-D1sYbCTQ.js} +2 -2
  482. package/dist/core/web/public/next/assets/{icons-CygCEFWs.js.map → icons-D1sYbCTQ.js.map} +1 -1
  483. package/dist/core/web/public/next/assets/{index-CyK31YRm.js → index-DZ-UQ7v8.js} +3 -3
  484. package/dist/core/web/public/next/assets/{index-CyK31YRm.js.map → index-DZ-UQ7v8.js.map} +1 -1
  485. package/dist/core/web/public/next/assets/leader-focus-BjeJv7CW.js +2 -0
  486. package/dist/core/web/public/next/assets/leader-focus-BjeJv7CW.js.map +1 -0
  487. package/dist/core/web/public/next/assets/leader-thread-types-CDb-VPPO.js +2 -0
  488. package/dist/core/web/public/next/assets/leader-thread-types-CDb-VPPO.js.map +1 -0
  489. package/dist/core/web/public/next/assets/{live-model-D7hilYFk.js → live-model-D_32RAOR.js} +2 -2
  490. package/dist/core/web/public/next/assets/{live-model-D7hilYFk.js.map → live-model-D_32RAOR.js.map} +1 -1
  491. package/dist/core/web/public/next/assets/{mind.module-B15k0Y9q.js → mind.module-7hA92hRg.js} +2 -2
  492. package/dist/core/web/public/next/assets/{mind.module-B15k0Y9q.js.map → mind.module-7hA92hRg.js.map} +1 -1
  493. package/dist/core/web/public/next/assets/mutations-DQPbFdRJ.js +2 -0
  494. package/dist/core/web/public/next/assets/{mutations-CWlxEMEm.js.map → mutations-DQPbFdRJ.js.map} +1 -1
  495. package/dist/core/web/public/next/assets/{nav-mimleI0R.js → nav-0dOYe2rJ.js} +2 -2
  496. package/dist/core/web/public/next/assets/{nav-mimleI0R.js.map → nav-0dOYe2rJ.js.map} +1 -1
  497. package/dist/core/web/public/next/assets/{overnight-queries-DJ4mZKSj.js → overnight-queries-C4wOnFtE.js} +2 -2
  498. package/dist/core/web/public/next/assets/{overnight-queries-DJ4mZKSj.js.map → overnight-queries-C4wOnFtE.js.map} +1 -1
  499. package/dist/core/web/public/next/assets/primitives-2A5Eq8FQ.js +1 -0
  500. package/dist/core/web/public/next/assets/{queries-Bw1McHTj.js → queries-BTXWdZzU.js} +2 -2
  501. package/dist/core/web/public/next/assets/{queries-Bw1McHTj.js.map → queries-BTXWdZzU.js.map} +1 -1
  502. package/dist/core/web/public/next/assets/{reasoning-pref-KkhI_5-j.js → reasoning-pref-Ca6lN6x_.js} +2 -2
  503. package/dist/core/web/public/next/assets/{reasoning-pref-KkhI_5-j.js.map → reasoning-pref-Ca6lN6x_.js.map} +1 -1
  504. package/dist/core/web/public/next/assets/{reveal-anchor-CCCx_kfT.js → reveal-anchor-DfNLgK_I.js} +2 -2
  505. package/dist/core/web/public/next/assets/{reveal-anchor-CCCx_kfT.js.map → reveal-anchor-DfNLgK_I.js.map} +1 -1
  506. package/dist/core/web/public/next/assets/seat-subscription-C5uPHBsN.js +2 -0
  507. package/dist/core/web/public/next/assets/{seat-subscription-BhUUz7VL.js.map → seat-subscription-C5uPHBsN.js.map} +1 -1
  508. package/dist/core/web/public/next/assets/{sidebar-model-Dcd437cf.js → sidebar-model-CLCdqwkR.js} +2 -2
  509. package/dist/core/web/public/next/assets/{sidebar-model-Dcd437cf.js.map → sidebar-model-CLCdqwkR.js.map} +1 -1
  510. package/dist/core/web/public/next/assets/{slots-BL_J5mmV.js → slots-C-qBeu-8.js} +3 -3
  511. package/dist/core/web/public/next/assets/{slots-BL_J5mmV.js.map → slots-C-qBeu-8.js.map} +1 -1
  512. package/dist/core/web/public/next/assets/{surface-data-C3UAQ4QX.js → surface-data-Dj71tdD-.js} +2 -2
  513. package/dist/core/web/public/next/assets/{surface-data-C3UAQ4QX.js.map → surface-data-Dj71tdD-.js.map} +1 -1
  514. package/dist/core/web/public/next/assets/surface-prefetch-B0WdJOJG.js +2 -0
  515. package/dist/core/web/public/next/assets/surface-prefetch-B0WdJOJG.js.map +1 -0
  516. package/dist/core/web/public/next/assets/{tasks-model-CzHvclVm.js → tasks-model-O8yR5YXp.js} +2 -2
  517. package/dist/core/web/public/next/assets/{tasks-model-CzHvclVm.js.map → tasks-model-O8yR5YXp.js.map} +1 -1
  518. package/dist/core/web/public/next/assets/{terminal-icons-BwgQTQP0.js → terminal-icons-CoRZq9iO.js} +2 -2
  519. package/dist/core/web/public/next/assets/{terminal-icons-BwgQTQP0.js.map → terminal-icons-CoRZq9iO.js.map} +1 -1
  520. package/dist/core/web/public/next/assets/thread-data-DQB_g66d.js +2 -0
  521. package/dist/core/web/public/next/assets/thread-data-DQB_g66d.js.map +1 -0
  522. package/dist/core/web/public/next/assets/{usage-queries-pXxvy54x.js → usage-queries-AdjEyEtn.js} +2 -2
  523. package/dist/core/web/public/next/assets/{usage-queries-pXxvy54x.js.map → usage-queries-AdjEyEtn.js.map} +1 -1
  524. package/dist/core/web/public/next/assets/{use-guarded-action-EgVWjM5c.js → use-guarded-action-CuuMHeDX.js} +2 -2
  525. package/dist/core/web/public/next/assets/{use-guarded-action-EgVWjM5c.js.map → use-guarded-action-CuuMHeDX.js.map} +1 -1
  526. package/dist/core/web/public/next/assets/{use-ticker-BoqFxRFG.js → use-ticker-BBcp02SZ.js} +2 -2
  527. package/dist/core/web/public/next/assets/{use-ticker-BoqFxRFG.js.map → use-ticker-BBcp02SZ.js.map} +1 -1
  528. package/dist/core/web/public/next/assets/{use-token-gate-DSNIIpI6.js → use-token-gate-CbXVYQYI.js} +2 -2
  529. package/dist/core/web/public/next/assets/{use-token-gate-DSNIIpI6.js.map → use-token-gate-CbXVYQYI.js.map} +1 -1
  530. package/dist/core/web/public/next/assets/{useOnboarding-Bk0VnkxQ.js → useOnboarding-C_Fpe_sH.js} +2 -2
  531. package/dist/core/web/public/next/assets/{useOnboarding-Bk0VnkxQ.js.map → useOnboarding-C_Fpe_sH.js.map} +1 -1
  532. package/dist/core/web/public/next/assets/{useSeatsRefresh-q7Id9RcW.js → useSeatsRefresh-Co1CnxLc.js} +2 -2
  533. package/dist/core/web/public/next/assets/{useSeatsRefresh-q7Id9RcW.js.map → useSeatsRefresh-Co1CnxLc.js.map} +1 -1
  534. package/dist/core/web/public/next/assets/{useVerseTranscript-CqH51rt9.js → useVerseTranscript-DgbigUlV.js} +2 -2
  535. package/dist/core/web/public/next/assets/{useVerseTranscript-CqH51rt9.js.map → useVerseTranscript-DgbigUlV.js.map} +1 -1
  536. package/dist/core/web/public/next/assets/{verse-icons-DsG1dOQx.js → verse-icons-S56hIiXS.js} +2 -2
  537. package/dist/core/web/public/next/assets/{verse-icons-DsG1dOQx.js.map → verse-icons-S56hIiXS.js.map} +1 -1
  538. package/dist/core/web/public/next/assets/verse-model-DAVevkNe.js +2 -0
  539. package/dist/core/web/public/next/assets/{verse-model-JTuvRBVK.js.map → verse-model-DAVevkNe.js.map} +1 -1
  540. package/dist/core/web/public/next/assets/{warmup-Bv1RumVq.js → warmup-B_RAtNZ_.js} +3 -3
  541. package/dist/core/web/public/next/assets/{warmup-Bv1RumVq.js.map → warmup-B_RAtNZ_.js.map} +1 -1
  542. package/dist/core/web/public/next/assets/{why-seat-model-Cau8qG6O.js → why-seat-model-M_r0wX3b.js} +2 -2
  543. package/dist/core/web/public/next/assets/{why-seat-model-Cau8qG6O.js.map → why-seat-model-M_r0wX3b.js.map} +1 -1
  544. package/dist/core/web/public/next/assets/{workspace-model-D-SLfads.js → workspace-model-dji5-Al3.js} +2 -2
  545. package/dist/core/web/public/next/assets/{workspace-model-D-SLfads.js.map → workspace-model-dji5-Al3.js.map} +1 -1
  546. package/dist/core/web/public/next/index.html +1 -1
  547. package/dist/core/web/read-projection-worker.js +2 -1
  548. package/dist/core/web/read-projection-worker.js.map +1 -1
  549. package/dist/core/web/read-projections.d.ts +3 -0
  550. package/dist/core/web/read-projections.js +2 -2
  551. package/dist/core/web/read-projections.js.map +1 -1
  552. package/dist/core/web/visibility.js +3 -4
  553. package/dist/core/web/visibility.js.map +1 -1
  554. package/dist/release-dependency-inventory.json +1 -1
  555. package/package.json +3 -2
  556. package/dist/core/web/public/next/assets/Advanced-cVYTAsfB.js +0 -2
  557. package/dist/core/web/public/next/assets/AppsSection-DDqeSrty.js +0 -2
  558. package/dist/core/web/public/next/assets/AreaTrend-C32rUaxm.js +0 -2
  559. package/dist/core/web/public/next/assets/AreaTrend-C32rUaxm.js.map +0 -1
  560. package/dist/core/web/public/next/assets/AutonomySection-Dwn8UtOy.js +0 -2
  561. package/dist/core/web/public/next/assets/ChartFrame-BVuBe6RP.css +0 -1
  562. package/dist/core/web/public/next/assets/ChartFrame-DK_BDXCt.js +0 -2
  563. package/dist/core/web/public/next/assets/ChartFrame-DK_BDXCt.js.map +0 -1
  564. package/dist/core/web/public/next/assets/ChatSection-C9myY4ei.js +0 -3
  565. package/dist/core/web/public/next/assets/CommandPalette-DzPsth0u.js +0 -2
  566. package/dist/core/web/public/next/assets/CommandPalette-DzPsth0u.js.map +0 -1
  567. package/dist/core/web/public/next/assets/CommandSection-Brp73iox.js +0 -3
  568. package/dist/core/web/public/next/assets/CommandSection-Brp73iox.js.map +0 -1
  569. package/dist/core/web/public/next/assets/CommandSection-CvAVd1w4.css +0 -1
  570. package/dist/core/web/public/next/assets/DockHost-BcEq2qaS.js +0 -2
  571. package/dist/core/web/public/next/assets/FleetSection-BG8iGraG.js +0 -3
  572. package/dist/core/web/public/next/assets/FleetSection-BG8iGraG.js.map +0 -1
  573. package/dist/core/web/public/next/assets/Funnel-B88_wpA4.js +0 -2
  574. package/dist/core/web/public/next/assets/Funnel-B88_wpA4.js.map +0 -1
  575. package/dist/core/web/public/next/assets/GrowthSection-BvZezOsX.js +0 -2
  576. package/dist/core/web/public/next/assets/LeaderCard-BxxiEg4e.js +0 -2
  577. package/dist/core/web/public/next/assets/LeaderCard-BxxiEg4e.js.map +0 -1
  578. package/dist/core/web/public/next/assets/MatrixHeatmap-iPFEU2Tv.js +0 -2
  579. package/dist/core/web/public/next/assets/MatrixHeatmap-iPFEU2Tv.js.map +0 -1
  580. package/dist/core/web/public/next/assets/McpSection-nuXer4K-.js +0 -2
  581. package/dist/core/web/public/next/assets/MindSection-Dc5v5rH3.js +0 -2
  582. package/dist/core/web/public/next/assets/MindSection-Dc5v5rH3.js.map +0 -1
  583. package/dist/core/web/public/next/assets/NeedsYouDrawer-N1cWJjpz.js +0 -3
  584. package/dist/core/web/public/next/assets/NeedsYouDrawer-N1cWJjpz.js.map +0 -1
  585. package/dist/core/web/public/next/assets/NewChatDialog-BqFTWgHU.js +0 -3
  586. package/dist/core/web/public/next/assets/OnboardingGate-BZafM1Vf.js +0 -3
  587. package/dist/core/web/public/next/assets/PreviewPane-zT3gK9TD.js +0 -2
  588. package/dist/core/web/public/next/assets/ResourcePoolConsoleApp-CAxPQ4sZ.js +0 -7
  589. package/dist/core/web/public/next/assets/ResourcesChrome-BnhnEdJe.js +0 -3
  590. package/dist/core/web/public/next/assets/ResourcesChrome-BnhnEdJe.js.map +0 -1
  591. package/dist/core/web/public/next/assets/ResourcesDrawer-BAFGodny.js +0 -2
  592. package/dist/core/web/public/next/assets/ResourcesDrawer-BAFGodny.js.map +0 -1
  593. package/dist/core/web/public/next/assets/ResourcesDrawer-BCFJ2Uq8.css +0 -1
  594. package/dist/core/web/public/next/assets/SettingsSection-Bf74l0Gj.js.map +0 -1
  595. package/dist/core/web/public/next/assets/Sidebar-C9YtBYVZ.js +0 -3
  596. package/dist/core/web/public/next/assets/StatTile-BuLMsJMx.css +0 -1
  597. package/dist/core/web/public/next/assets/StatTile-CTX41O6y.js +0 -2
  598. package/dist/core/web/public/next/assets/StatTile-CTX41O6y.js.map +0 -1
  599. package/dist/core/web/public/next/assets/StepBand-jJTqYIdX.js +0 -2
  600. package/dist/core/web/public/next/assets/StepBand-jJTqYIdX.js.map +0 -1
  601. package/dist/core/web/public/next/assets/Swimlane-LrslsGxw.js +0 -2
  602. package/dist/core/web/public/next/assets/Swimlane-LrslsGxw.js.map +0 -1
  603. package/dist/core/web/public/next/assets/Swimlane-o6_Zsrnr.css +0 -1
  604. package/dist/core/web/public/next/assets/UniverseConsoleApp-BpphLqoz.js +0 -2
  605. package/dist/core/web/public/next/assets/UsageSection-CUlaKtws.js +0 -3
  606. package/dist/core/web/public/next/assets/UsageSection-CUlaKtws.js.map +0 -1
  607. package/dist/core/web/public/next/assets/VerseConsoleApp-DXDv1aOi.js +0 -3
  608. package/dist/core/web/public/next/assets/VerseConsoleApp-DXDv1aOi.js.map +0 -1
  609. package/dist/core/web/public/next/assets/Workspace-RXy0pqp6.js +0 -10
  610. package/dist/core/web/public/next/assets/actions-BZTidCXZ.js +0 -2
  611. package/dist/core/web/public/next/assets/actions-BmIf9avl.css +0 -1
  612. package/dist/core/web/public/next/assets/budget-queries-DFF-3ICg.js +0 -2
  613. package/dist/core/web/public/next/assets/charts-CzxWrZn7.js +0 -2
  614. package/dist/core/web/public/next/assets/charts-CzxWrZn7.js.map +0 -1
  615. package/dist/core/web/public/next/assets/charts-DhKTrQLw.css +0 -1
  616. package/dist/core/web/public/next/assets/command-catalog-ffCAijVY.js.map +0 -1
  617. package/dist/core/web/public/next/assets/context-queries-_9bFWb9v.js +0 -2
  618. package/dist/core/web/public/next/assets/global-y_exIyAP.js +0 -2
  619. package/dist/core/web/public/next/assets/guarded-runners-Cd4POBtI.js +0 -2
  620. package/dist/core/web/public/next/assets/mutations-CWlxEMEm.js +0 -2
  621. package/dist/core/web/public/next/assets/primitives-DMijqcu0.js +0 -1
  622. package/dist/core/web/public/next/assets/seat-subscription-BhUUz7VL.js +0 -2
  623. package/dist/core/web/public/next/assets/surface-prefetch-BXKjPsV5.js +0 -2
  624. package/dist/core/web/public/next/assets/surface-prefetch-BXKjPsV5.js.map +0 -1
  625. package/dist/core/web/public/next/assets/verse-model-JTuvRBVK.js +0 -2
@@ -1 +1 @@
1
- {"version":3,"file":"fleet-model-DOK8ZtEt.js","names":[],"sources":["../../../../../../src/web-ui/routes/verse/autonomy/fleet-model.ts"],"sourcesContent":["/**\n * routes/verse/autonomy/fleet-model.ts — every derivation the local-fleet\n * surfaces make, as pure functions. No React, no I/O, so the rules that decide\n * what the operator is told are unit-testable without a DOM.\n *\n * The one rule this file exists to enforce:\n *\n * **A concurrency number the runtime will not honour is a lie, and the UI\n * must print the truth instead of the configuration.**\n *\n * That is not a hypothetical. Measured on this machine (docs/LOCAL-FLEET.md),\n * four concurrent Qwen3.8 requests to Ollama returned at 3.7 / 7.6 / 11.4 /\n * 15.2s — a four-second stagger, which is a queue. The same weights on\n * llama-server returned at 8.6 / 8.9 / 9.0 / 9.0s: continuous batching across\n * four slots sharing one 27 GB copy. Ollama's log calls it a refusal\n * (`model architecture does not currently support parallel requests`) and\n * `OLLAMA_NUM_PARALLEL` does not move it.\n *\n * So `runtimeCapacity()` never reports `slotsTotal` as capacity unless the\n * runtime says it batches. When a runtime that serializes is configured with\n * four slots, the capacity is ONE and `overstated` is true, and the panel says\n * so in words. A queue that looks like slowness is the exact failure this\n * surface is here to prevent.\n *\n * The bodies these functions narrow come from routes that are landing in\n * parallel (see fleet-contract.ts), so everything is projected STRUCTURALLY\n * rather than cast: a field-name drift degrades to an honest \"unknown\" at\n * render time instead of throwing.\n */\nimport { UNKNOWN, formatDuration, percentText } from './format.js';\nimport type {\n FleetAgent,\n FleetAgentState,\n FleetSnapshot,\n LocalOnlyPolicy,\n LocalOnlyRefusal,\n LocalOnlySource,\n ServingRuntimeKind,\n ServingRuntimeParallelism,\n ServingRuntimeSnapshot,\n ServingRuntimeState,\n} from './fleet-contract.js';\n\n// ---------------------------------------------------------------------------\n// Structural narrowing\n// ---------------------------------------------------------------------------\n\nfunction record(value: unknown): Record<string, unknown> | null {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : null;\n}\n\nfunction str(source: Record<string, unknown>, key: string): string | null {\n const v = source[key];\n return typeof v === 'string' && v.length > 0 ? v : null;\n}\n\nfunction num(source: Record<string, unknown>, key: string): number | null {\n const v = source[key];\n return typeof v === 'number' && Number.isFinite(v) ? v : null;\n}\n\n/** A count that is meaningful at zero — so 0 stays 0 and absent stays null. */\nfunction count(source: Record<string, unknown>, key: string): number | null {\n const v = num(source, key);\n return v === null ? null : Math.max(0, Math.round(v));\n}\n\nfunction bool(source: Record<string, unknown>, key: string): boolean | null {\n const v = source[key];\n return typeof v === 'boolean' ? v : null;\n}\n\nfunction strings(source: Record<string, unknown>, key: string): string[] {\n const v = source[key];\n return Array.isArray(v) ? v.filter((entry): entry is string => typeof entry === 'string') : [];\n}\n\nconst RUNTIME_KINDS: readonly ServingRuntimeKind[] = ['llama-server', 'ollama', 'vllm', 'unknown'];\nconst RUNTIME_STATES: readonly ServingRuntimeState[] = [\n 'running',\n 'starting',\n 'stopping',\n 'stopped',\n 'unknown',\n];\nconst AGENT_STATES: readonly FleetAgentState[] = ['queued', 'running', 'finishing'];\nconst LOCAL_ONLY_SOURCES: readonly LocalOnlySource[] = [\n 'off',\n 'config',\n 'env',\n 'config+env',\n 'latch',\n 'unknown',\n];\n\nfunction oneOf<T extends string>(\n source: Record<string, unknown>,\n key: string,\n allowed: readonly T[],\n fallback: T,\n): T {\n const v = source[key];\n return typeof v === 'string' && (allowed as readonly string[]).includes(v) ? (v as T) : fallback;\n}\n\nfunction projectParallelism(raw: unknown): ServingRuntimeParallelism {\n const r = record(raw);\n if (!r) return { capable: null, refusal: null, slots: null };\n return { capable: bool(r, 'capable'), refusal: str(r, 'refusal'), slots: count(r, 'slots') };\n}\n\n/**\n * Owner R's `LlamaRuntimeSnapshot` (`src/core/local-runtime/llama/types.ts`)\n * spells the same facts differently, and the route that serves it is not\n * written yet — so rather than betting on one envelope, this reads both.\n *\n * The mapping, with the two judgement calls stated:\n *\n * - `state: 'up' | 'down' | 'loading' | 'unknown'` → running / stopped /\n * starting / unknown.\n * - `contextPerSlot` wins over `contextTotal`. `-c 65536 --parallel 4` gives\n * each slot 16k, and the number an agent's turn actually runs in is the\n * per-slot one. Quoting the total would overstate every agent's context\n * fourfold — the same class of error as quoting slots as concurrency.\n * - `managed` → `supervised`. R is explicit that an unmanaged server may be\n * adopted but is \"never killed on a guess\", which is exactly the condition\n * under which this panel must not offer a stop button.\n */\nconst LLAMA_STATE: Readonly<Record<string, ServingRuntimeState>> = {\n up: 'running',\n down: 'stopped',\n loading: 'starting',\n unknown: 'unknown',\n};\n\nfunction projectLlamaShape(r: Record<string, unknown>): ServingRuntimeSnapshot | null {\n const rawState = r['state'];\n if (typeof rawState !== 'string' || !(rawState in LLAMA_STATE)) return null;\n const slots = record(r['slots']);\n const host = str(r, 'host');\n const port = count(r, 'port');\n const configured = slots ? count(slots, 'configured') : null;\n const busy = slots ? count(slots, 'busy') : null;\n // `slots.source` is R's own trust marker: 'unknown' means \"do not trust a\n // number\", so a slot count carrying it is dropped rather than quoted.\n const slotSource = slots ? str(slots, 'source') : null;\n const trusted = slotSource !== null && slotSource !== 'unknown';\n const total = trusted ? configured : null;\n\n const refusal = str(r, 'parallelRefusal');\n const identity = str(r, 'runtimeKind');\n const contextPerSlot = count(r, 'contextPerSlot');\n const contextTotal = count(r, 'contextTotal');\n\n return {\n // IDENTIFIED where the producer says so. A base URL pointed at Ollama's\n // /v1 answers /v1/models — enough to keep the engine routable — while\n // 404ing /health and /props, and asserting 'llama-server' for it would\n // hide the one fact that matters: that runtime serialises this model.\n kind: identity === 'ollama' ? 'ollama' : identity === 'unknown' ? 'unknown' : 'llama-server',\n state: LLAMA_STATE[rawState] ?? 'unknown',\n endpoint: host !== null && port !== null ? `${host}:${port}` : (str(r, 'origin') ?? null),\n // A digest is not a name. `modelName` falls back to `model_path`'s basename\n // at the producer, which for a blob out of Ollama's content-addressed store\n // is `sha256-…`: it identifies the machine's storage layout and names\n // nothing. Null renders as \"unknown\", which is the honest answer.\n model: humaneModelName(str(r, 'modelName')) ?? humaneModelName(str(r, 'model')),\n slotsTotal: total,\n slotsBusy: trusted ? busy : null,\n // PER-SLOT, or nothing. `-c 65536 --parallel 4` gives each agent 16k, so\n // the `-c` total is usable only divided by a slot count we trust.\n contextTokens:\n contextPerSlot ??\n (total !== null && total > 0 && contextTotal !== null && contextTotal > 0\n ? Math.floor(contextTotal / total)\n : null),\n startedAt: str(r, 'startedAt'),\n parallel: {\n // llama-server's slots ARE its continuous-batching slots: a count read\n // back off the live server (`/props.total_slots`, `/slots`) is a count\n // of requests it will run at the same time, which is what the\n // measurement in docs/LOCAL-FLEET.md showed. An untrusted or absent\n // reading claims nothing — `null`, not `true`.\n capable: refusal !== null ? false : trusted && total !== null ? true : null,\n // The runtime's own words when it refuses, carried verbatim as evidence.\n refusal,\n slots: total,\n },\n reason: str(r, 'lastError'),\n supervised: bool(r, 'managed') ?? false,\n sampledAt: str(r, 'checkedAt'),\n };\n}\n\n/** A displayable model name, or null for an absent or digest-shaped one. */\nfunction humaneModelName(raw: string | null): string | null {\n if (raw === null || raw.length === 0) return null;\n const base = raw.split('/').pop() ?? raw;\n if (base.length === 0) return null;\n if (/^sha256[-:][0-9a-f]{16,}$/i.test(base)) return null;\n return base;\n}\n\nexport function projectServingRuntime(raw: unknown): ServingRuntimeSnapshot | null {\n const r = record(raw);\n if (!r) return null;\n // A body already in this surface's own shape wins; otherwise try owner R's.\n if (!('kind' in r) && 'slots' in r && 'state' in r) {\n const llama = projectLlamaShape(r);\n if (llama !== null) return llama;\n }\n return {\n kind: oneOf(r, 'kind', RUNTIME_KINDS, 'unknown'),\n state: oneOf(r, 'state', RUNTIME_STATES, 'unknown'),\n endpoint: str(r, 'endpoint'),\n model: str(r, 'model'),\n slotsTotal: count(r, 'slotsTotal'),\n slotsBusy: count(r, 'slotsBusy'),\n contextTokens: count(r, 'contextTokens'),\n startedAt: str(r, 'startedAt'),\n parallel: projectParallelism(r['parallel']),\n reason: str(r, 'reason'),\n // A runtime this server cannot supervise must not be offered buttons, so\n // an unreported flag reads as NOT supervised. The conservative default is\n // \"no controls\" rather than controls that 404 on the first click.\n supervised: bool(r, 'supervised') ?? false,\n sampledAt: str(r, 'sampledAt'),\n };\n}\n\nfunction projectAgent(raw: unknown): FleetAgent | null {\n const r = record(raw);\n if (!r) return null;\n const id = str(r, 'id');\n if (id === null) return null;\n return {\n id,\n task: str(r, 'task'),\n repo: str(r, 'repo'),\n engine: str(r, 'engine'),\n model: str(r, 'model'),\n state: oneOf(r, 'state', AGENT_STATES, 'running'),\n startedAt: str(r, 'startedAt'),\n slot: count(r, 'slot'),\n };\n}\n\nexport function projectFleet(raw: unknown): FleetSnapshot | null {\n const r = record(raw);\n if (!r) return null;\n const rawAgents = Array.isArray(r['agents']) ? (r['agents'] as unknown[]) : [];\n return {\n agents: rawAgents.map(projectAgent).filter((a): a is FleetAgent => a !== null),\n queueDepth: count(r, 'queueDepth'),\n slotsTotal: count(r, 'slotsTotal'),\n slotsBusy: count(r, 'slotsBusy'),\n notes: strings(r, 'notes'),\n sampledAt: str(r, 'sampledAt'),\n };\n}\n\nfunction projectRefusal(raw: unknown): LocalOnlyRefusal | null {\n const r = record(raw);\n if (!r) return null;\n const engine = str(r, 'engine');\n if (engine === null) return null;\n return { engine, reason: str(r, 'reason') ?? 'The dispatcher refuses this engine while local-only is on.' };\n}\n\n/**\n * `source` is carried through as the resolver's own value — it is no longer\n * flattened onto a smaller union here.\n *\n * The flattening this replaced mapped `'latch'` onto `'config'`, which tells\n * an operator the mode is stored in the config and editable when in fact the\n * process has pinned it for its own lifetime. Only the separate `mutable`\n * flag kept that from reaching the screen as a working switch that silently\n * does nothing. An unrecognised value lands on `'unknown'`, which makes no\n * claim at all.\n */\n/** Sources that pin the mode to this server process, whatever the config says. */\nconst PINNED_SOURCES: ReadonlySet<LocalOnlySource> = new Set<LocalOnlySource>([\n 'env',\n 'config+env',\n 'latch',\n 'unknown',\n]);\n\nexport function projectLocalOnly(raw: unknown): LocalOnlyPolicy | null {\n const r = record(raw);\n if (!r) return null;\n const rawRefusals = Array.isArray(r['refuses']) ? (r['refuses'] as unknown[]) : [];\n const source = oneOf(r, 'source', LOCAL_ONLY_SOURCES, 'unknown');\n return {\n // An unreported `enabled` is NOT \"on\": claiming cloud is unreachable when\n // the server never said so is the one error this policy cannot afford.\n enabled: bool(r, 'enabled') ?? false,\n source,\n refuses: rawRefusals.map(projectRefusal).filter((x): x is LocalOnlyRefusal => x !== null),\n // A policy pinned by the server's own process — an env var, or a latch\n // this process already took — is never editable from here, whatever the\n // body claims: a write would be silently overridden on the next read.\n // `'unknown'` is treated as pinned too, because offering a switch for a\n // policy this client cannot identify is the more expensive mistake.\n mutable: PINNED_SOURCES.has(source) ? false : (bool(r, 'mutable') ?? false),\n detail: str(r, 'detail'),\n sampledAt: str(r, 'sampledAt') ?? str(r, 'checkedAt'),\n };\n}\n\n// ---------------------------------------------------------------------------\n// The capacity verdict — the point of this whole file\n// ---------------------------------------------------------------------------\n\nexport type CapacityVerdict = 'batched' | 'serialized' | 'unknown' | 'offline';\n\nexport interface RuntimeCapacity {\n verdict: CapacityVerdict;\n /**\n * How many agents can genuinely be in flight at once. `null` means it\n * cannot be known from this reading — which is NOT the same as 0 and must\n * never render as a number.\n */\n effectiveConcurrency: number | null;\n /** What the runtime was configured with, honoured or not. */\n configuredSlots: number | null;\n /**\n * True when the configured slot count is larger than what the runtime will\n * actually run in parallel. While this is true, the configured number must\n * not be presented as capacity anywhere on the screen.\n */\n overstated: boolean;\n headline: string;\n detail: string;\n tone: 'success' | 'warning' | 'danger' | 'neutral' | 'unknown';\n}\n\n/** Human name for a runtime, used in sentences. */\nexport function runtimeLabel(kind: ServingRuntimeKind): string {\n switch (kind) {\n case 'llama-server':\n return 'llama-server';\n case 'ollama':\n return 'Ollama';\n case 'vllm':\n return 'vLLM';\n default:\n return 'the serving runtime';\n }\n}\n\nexport function runtimeCapacity(runtime: ServingRuntimeSnapshot | null): RuntimeCapacity {\n if (runtime === null) {\n return {\n verdict: 'unknown',\n effectiveConcurrency: null,\n configuredSlots: null,\n overstated: false,\n headline: 'No serving runtime reported',\n detail:\n 'Serving runtime unavailable — concurrency unknown.',\n tone: 'unknown',\n };\n }\n\n const configured = runtime.slotsTotal ?? runtime.parallel.slots;\n const name = runtimeLabel(runtime.kind);\n\n if (runtime.state !== 'running') {\n return {\n verdict: 'offline',\n effectiveConcurrency: null,\n configuredSlots: configured,\n overstated: false,\n headline: runtime.state === 'stopped' ? 'Not serving' : `Serving runtime is ${runtime.state}`,\n detail:\n runtime.state === 'stopped'\n ? `${name} is not running, so no local agent can take a turn. Start it before dispatching a fleet.`\n : `${name} is ${runtime.state}; capacity is not settled until it reports running.`,\n tone: runtime.state === 'stopped' ? 'neutral' : 'warning',\n };\n }\n\n if (runtime.parallel.capable === false) {\n // The measured case. The configured number is real — it is simply not a\n // concurrency, and printing it as one is the failure mode this exists for.\n const overstated = configured !== null && configured > 1;\n return {\n verdict: 'serialized',\n effectiveConcurrency: 1,\n configuredSlots: configured,\n overstated,\n headline: `${name} serializes requests`,\n detail: overstated\n ? `${name} has ${configured} slots configured but runs one request at a time for this model, so the real capacity is one agent. The other ${configured - 1} would queue, and a queue looks like slowness rather than a wait.`\n : `${name} runs one request at a time for this model. A second agent waits for the first to finish.`,\n tone: 'danger',\n };\n }\n\n if (runtime.parallel.capable === null) {\n return {\n verdict: 'unknown',\n effectiveConcurrency: null,\n configuredSlots: configured,\n overstated: false,\n headline: 'Batching not confirmed',\n detail: `${name} is running${\n configured === null ? '' : ` with ${configured} slots configured`\n }, but it has not reported whether it batches concurrent requests for this model. Until it does, this panel will not quote a concurrency it cannot stand behind.`,\n tone: 'unknown',\n };\n }\n\n if (configured === null) {\n return {\n verdict: 'batched',\n effectiveConcurrency: null,\n configuredSlots: null,\n overstated: false,\n headline: `${name} batches concurrent requests`,\n detail: `${name} batches, but reported no slot count, so the ceiling on concurrent agents is unknown.`,\n tone: 'unknown',\n };\n }\n\n return {\n verdict: 'batched',\n effectiveConcurrency: configured,\n configuredSlots: configured,\n overstated: false,\n headline: `${configured} agent${configured === 1 ? '' : 's'} in parallel`,\n detail: `${name} batches across ${configured} slot${\n configured === 1 ? '' : 's'\n } sharing one copy of the weights, so ${configured} agent${\n configured === 1 ? '' : 's'\n } can be mid-turn at once.`,\n tone: 'success',\n };\n}\n\n/**\n * The measured comparison, kept as data so the panel renders it as evidence\n * rather than as an assertion in prose. Source: docs/LOCAL-FLEET.md, four\n * concurrent requests against the same Qwen3.8 GGUF on this machine.\n */\nexport interface ConcurrencyEvidenceRow {\n runtime: string;\n /** Per-request completion times, in seconds, in the order they returned. */\n perRequestSeconds: readonly number[];\n wallSeconds: number;\n verdict: string;\n}\n\nexport const CONCURRENCY_EVIDENCE: readonly ConcurrencyEvidenceRow[] = [\n {\n runtime: 'Ollama',\n perRequestSeconds: [3.7, 7.6, 11.4, 15.2],\n wallSeconds: 15.2,\n verdict: 'staggered — the requests queued',\n },\n {\n runtime: 'llama-server',\n perRequestSeconds: [8.6, 8.9, 9.0, 9.0],\n wallSeconds: 9.1,\n verdict: 'together — the requests batched',\n },\n] as const;\n\n// ---------------------------------------------------------------------------\n// Utilisation\n// ---------------------------------------------------------------------------\n\nexport interface SlotUtilisation {\n busy: number | null;\n total: number | null;\n /** 0–100, or null when either half is unknown. */\n percent: number | null;\n /** The share in words, by the one percent rule (\"<1%\", \"99%\"); null when unknown. */\n percentText: string | null;\n /** Every slot is taken: the next turn waits. */\n saturated: boolean;\n /** One line, safe to render on its own. */\n label: string;\n}\n\n/**\n * Slot occupancy.\n *\n * `total` is the EFFECTIVE concurrency, not the configured slot count — on a\n * runtime that serializes, one busy slot out of four configured is full, and\n * drawing it as 25% would tell the operator there is room when there is none.\n */\nexport function slotUtilisation(busy: number | null, effectiveTotal: number | null): SlotUtilisation {\n if (busy === null || effectiveTotal === null || effectiveTotal <= 0) {\n return {\n busy,\n total: effectiveTotal,\n percent: null,\n percentText: null,\n saturated: false,\n label:\n busy === null\n ? 'Slot occupancy is not reported.'\n : `${busy} in flight; the number of slots is not known, so occupancy cannot be drawn.`,\n };\n }\n const clamped = Math.min(busy, effectiveTotal);\n const share = (clamped / effectiveTotal) * 100;\n const percent = Math.round(share);\n const saturated = busy >= effectiveTotal;\n return {\n busy,\n total: effectiveTotal,\n percent,\n percentText: percentText(share),\n saturated,\n label: saturated\n ? `All ${effectiveTotal} slot${effectiveTotal === 1 ? '' : 's'} busy — new turns wait for one to free up.`\n : `${busy} of ${effectiveTotal} slot${effectiveTotal === 1 ? '' : 's'} busy.`,\n };\n}\n\n/**\n * What the operator is actually waiting on.\n *\n * Saturation alone is not bad news — a fleet at full occupancy is a fleet\n * working. Saturation WITH a queue is the thing to say plainly, because from\n * the outside it is indistinguishable from the machine being slow.\n */\nexport interface FleetPressure {\n state: 'idle' | 'working' | 'saturated' | 'queued' | 'unknown';\n headline: string;\n detail: string;\n tone: 'success' | 'running' | 'warning' | 'neutral' | 'unknown';\n}\n\nexport function fleetPressure(\n fleet: FleetSnapshot | null,\n utilisation: SlotUtilisation,\n capacity: RuntimeCapacity,\n): FleetPressure {\n if (fleet === null) {\n return {\n state: 'unknown',\n headline: 'Fleet not reported',\n // A missing reading — never shown as an empty fleet.\n detail: 'Fleet status unavailable.',\n tone: 'unknown',\n };\n }\n\n const running = fleet.agents.filter((a) => a.state !== 'queued').length;\n const queued = fleet.queueDepth ?? fleet.agents.filter((a) => a.state === 'queued').length;\n\n if (queued > 0) {\n return {\n state: 'queued',\n headline: `${queued} waiting for a slot`,\n detail:\n capacity.verdict === 'serialized'\n ? `${running} turn${running === 1 ? '' : 's'} in flight and ${queued} queued. This runtime serializes, so the queue drains one at a time — the wait is a wait, not slow inference.`\n : `${running} turn${running === 1 ? '' : 's'} in flight and ${queued} queued behind ${\n utilisation.total === null ? 'the available slots' : `${utilisation.total} slots`\n }. Queued turns have not started; their wall time is queueing, not work.`,\n tone: 'warning',\n };\n }\n\n if (running === 0) {\n return {\n state: 'idle',\n headline: 'Nothing in flight',\n detail: 'No agent is mid-turn.',\n tone: 'neutral',\n };\n }\n\n if (utilisation.saturated) {\n return {\n state: 'saturated',\n headline: `All slots busy · ${running} in flight`,\n detail: `Every slot is working and nothing is queued behind them. The next turn will wait, but none is waiting yet.`,\n tone: 'running',\n };\n }\n\n return {\n state: 'working',\n headline: `${running} in flight`,\n detail: utilisation.label,\n tone: 'running',\n };\n}\n\n// ---------------------------------------------------------------------------\n// Local-only impact\n// ---------------------------------------------------------------------------\n\nexport interface SeatLike {\n id: string;\n engine: string;\n label: string;\n}\n\nexport interface LocalOnlyImpact {\n /** Seats that become unreachable while the policy is on. */\n blocked: SeatLike[];\n /** Seats unaffected — the local ones. */\n allowed: SeatLike[];\n /** The named refusal per blocked engine, from the server when it sent one. */\n refusalFor: (engine: string) => string;\n /** One line summarising the blast radius against the CURRENT seat roster. */\n summary: string;\n}\n\nconst DEFAULT_REFUSAL = 'Refused: local-only is on and this engine is not local.';\n\n/**\n * What local-only would block, measured against the seats that actually exist\n * on this machine right now.\n *\n * Deliberately computed from the live roster rather than from a fixed list of\n * cloud engines: the sentence \"this would block 3 of your 5 seats\" is only\n * trustworthy if the 5 is the operator's own number.\n */\nexport function localOnlyImpact(\n policy: LocalOnlyPolicy | null,\n seats: readonly SeatLike[],\n): LocalOnlyImpact {\n const blocked = seats.filter((s) => s.engine !== 'local');\n const allowed = seats.filter((s) => s.engine === 'local');\n const byEngine = new Map<string, string>();\n for (const refusal of policy?.refuses ?? []) byEngine.set(refusal.engine, refusal.reason);\n\n const summary =\n seats.length === 0\n ? 'No seats are configured, so there is nothing for this policy to block yet.'\n : blocked.length === 0\n ? `Every one of your ${seats.length} seat${seats.length === 1 ? '' : 's'} is local, so this policy would block nothing today.`\n : `${blocked.length} of ${seats.length} seat${seats.length === 1 ? '' : 's'} would become unreachable; ${allowed.length} local seat${allowed.length === 1 ? '' : 's'} would keep working.`;\n\n return {\n blocked,\n allowed,\n refusalFor: (engine: string) => byEngine.get(engine) ?? DEFAULT_REFUSAL,\n summary,\n };\n}\n\n/** Where the policy came from, as a sentence an operator can act on. */\nexport function localOnlySourceNote(policy: LocalOnlyPolicy | null): string | null {\n if (policy === null) return null;\n // The policy's own sentence, when it wrote one. It knows things this client\n // does not — e.g. that an env var tried and failed to clear a persisted\n // local-only — and a template would erase exactly that.\n if (policy.detail !== null) return policy.detail;\n switch (policy.source) {\n case 'env':\n case 'config+env':\n return 'This policy is pinned by an environment variable on the server process, so it cannot be changed from here — restart the server without it to take the switch back.';\n case 'latch':\n return 'This process already observed a persisted local-only and has pinned it for its own lifetime, so turning it off takes effect for new dispatches but not for the seams that latched. Restart the server to clear it.';\n case 'config':\n return 'Stored in the hub config and applied on the next dispatch.';\n case 'off':\n return 'Not configured; cloud engines are reachable.';\n default:\n return 'The server did not say where this setting came from.';\n }\n}\n\n// ---------------------------------------------------------------------------\n// Small formatters\n// ---------------------------------------------------------------------------\n\n/** \"4m 12s\" since an ISO instant, against a live clock. Absent stays absent. */\nexport function elapsedSince(iso: string | null, now: number): string {\n if (!iso) return UNKNOWN;\n const at = Date.parse(iso);\n if (Number.isNaN(at)) return UNKNOWN;\n return formatDuration(Math.max(0, now - at));\n}\n\n/** Uptime for the runtime header. Same rules, different words. */\nexport function formatUptime(startedAt: string | null, now: number): string {\n if (!startedAt) return UNKNOWN;\n const at = Date.parse(startedAt);\n if (Number.isNaN(at)) return UNKNOWN;\n return `up ${formatDuration(Math.max(0, now - at))}`;\n}\n\n/** Agent rows, ordered so the reader's first question is answered first. */\nexport function orderAgents(agents: readonly FleetAgent[]): FleetAgent[] {\n const rank: Record<FleetAgentState, number> = { running: 0, finishing: 1, queued: 2 };\n return [...agents].sort((a, b) => {\n const byState = rank[a.state] - rank[b.state];\n if (byState !== 0) return byState;\n // Longest-running first: the one most likely to be stuck is the one an\n // operator scanning this table is looking for.\n const at = a.startedAt ? Date.parse(a.startedAt) : Number.NaN;\n const bt = b.startedAt ? Date.parse(b.startedAt) : Number.NaN;\n if (Number.isNaN(at) && Number.isNaN(bt)) return a.id.localeCompare(b.id);\n if (Number.isNaN(at)) return 1;\n if (Number.isNaN(bt)) return -1;\n return at - bt;\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"fleet-model-BvDR3gjp.js","names":[],"sources":["../../../../../../src/web-ui/routes/verse/autonomy/fleet-model.ts"],"sourcesContent":["/**\n * routes/verse/autonomy/fleet-model.ts — every derivation the local-fleet\n * surfaces make, as pure functions. No React, no I/O, so the rules that decide\n * what the operator is told are unit-testable without a DOM.\n *\n * The one rule this file exists to enforce:\n *\n * **A concurrency number the runtime will not honour is a lie, and the UI\n * must print the truth instead of the configuration.**\n *\n * That is not a hypothetical. Measured on this machine (docs/LOCAL-FLEET.md),\n * four concurrent Qwen3.8 requests to Ollama returned at 3.7 / 7.6 / 11.4 /\n * 15.2s — a four-second stagger, which is a queue. The same weights on\n * llama-server returned at 8.6 / 8.9 / 9.0 / 9.0s: continuous batching across\n * four slots sharing one 27 GB copy. Ollama's log calls it a refusal\n * (`model architecture does not currently support parallel requests`) and\n * `OLLAMA_NUM_PARALLEL` does not move it.\n *\n * So `runtimeCapacity()` never reports `slotsTotal` as capacity unless the\n * runtime says it batches. When a runtime that serializes is configured with\n * four slots, the capacity is ONE and `overstated` is true, and the panel says\n * so in words. A queue that looks like slowness is the exact failure this\n * surface is here to prevent.\n *\n * The bodies these functions narrow come from routes that are landing in\n * parallel (see fleet-contract.ts), so everything is projected STRUCTURALLY\n * rather than cast: a field-name drift degrades to an honest \"unknown\" at\n * render time instead of throwing.\n */\nimport { UNKNOWN, formatDuration, percentText } from './format.js';\nimport type {\n FleetAgent,\n FleetAgentState,\n FleetSnapshot,\n LocalOnlyPolicy,\n LocalOnlyRefusal,\n LocalOnlySource,\n ServingRuntimeKind,\n ServingRuntimeParallelism,\n ServingRuntimeSnapshot,\n ServingRuntimeState,\n} from './fleet-contract.js';\n\n// ---------------------------------------------------------------------------\n// Structural narrowing\n// ---------------------------------------------------------------------------\n\nfunction record(value: unknown): Record<string, unknown> | null {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : null;\n}\n\nfunction str(source: Record<string, unknown>, key: string): string | null {\n const v = source[key];\n return typeof v === 'string' && v.length > 0 ? v : null;\n}\n\nfunction num(source: Record<string, unknown>, key: string): number | null {\n const v = source[key];\n return typeof v === 'number' && Number.isFinite(v) ? v : null;\n}\n\n/** A count that is meaningful at zero — so 0 stays 0 and absent stays null. */\nfunction count(source: Record<string, unknown>, key: string): number | null {\n const v = num(source, key);\n return v === null ? null : Math.max(0, Math.round(v));\n}\n\nfunction bool(source: Record<string, unknown>, key: string): boolean | null {\n const v = source[key];\n return typeof v === 'boolean' ? v : null;\n}\n\nfunction strings(source: Record<string, unknown>, key: string): string[] {\n const v = source[key];\n return Array.isArray(v) ? v.filter((entry): entry is string => typeof entry === 'string') : [];\n}\n\nconst RUNTIME_KINDS: readonly ServingRuntimeKind[] = ['llama-server', 'ollama', 'vllm', 'unknown'];\nconst RUNTIME_STATES: readonly ServingRuntimeState[] = [\n 'running',\n 'starting',\n 'stopping',\n 'stopped',\n 'unknown',\n];\nconst AGENT_STATES: readonly FleetAgentState[] = ['queued', 'running', 'finishing'];\nconst LOCAL_ONLY_SOURCES: readonly LocalOnlySource[] = [\n 'off',\n 'config',\n 'env',\n 'config+env',\n 'latch',\n 'unknown',\n];\n\nfunction oneOf<T extends string>(\n source: Record<string, unknown>,\n key: string,\n allowed: readonly T[],\n fallback: T,\n): T {\n const v = source[key];\n return typeof v === 'string' && (allowed as readonly string[]).includes(v) ? (v as T) : fallback;\n}\n\nfunction projectParallelism(raw: unknown): ServingRuntimeParallelism {\n const r = record(raw);\n if (!r) return { capable: null, refusal: null, slots: null };\n return { capable: bool(r, 'capable'), refusal: str(r, 'refusal'), slots: count(r, 'slots') };\n}\n\n/**\n * Owner R's `LlamaRuntimeSnapshot` (`src/core/local-runtime/llama/types.ts`)\n * spells the same facts differently, and the route that serves it is not\n * written yet — so rather than betting on one envelope, this reads both.\n *\n * The mapping, with the two judgement calls stated:\n *\n * - `state: 'up' | 'down' | 'loading' | 'unknown'` → running / stopped /\n * starting / unknown.\n * - `contextPerSlot` wins over `contextTotal`. `-c 65536 --parallel 4` gives\n * each slot 16k, and the number an agent's turn actually runs in is the\n * per-slot one. Quoting the total would overstate every agent's context\n * fourfold — the same class of error as quoting slots as concurrency.\n * - `managed` → `supervised`. R is explicit that an unmanaged server may be\n * adopted but is \"never killed on a guess\", which is exactly the condition\n * under which this panel must not offer a stop button.\n */\nconst LLAMA_STATE: Readonly<Record<string, ServingRuntimeState>> = {\n up: 'running',\n down: 'stopped',\n loading: 'starting',\n unknown: 'unknown',\n};\n\nfunction projectLlamaShape(r: Record<string, unknown>): ServingRuntimeSnapshot | null {\n const rawState = r['state'];\n if (typeof rawState !== 'string' || !(rawState in LLAMA_STATE)) return null;\n const slots = record(r['slots']);\n const host = str(r, 'host');\n const port = count(r, 'port');\n const configured = slots ? count(slots, 'configured') : null;\n const busy = slots ? count(slots, 'busy') : null;\n // `slots.source` is R's own trust marker: 'unknown' means \"do not trust a\n // number\", so a slot count carrying it is dropped rather than quoted.\n const slotSource = slots ? str(slots, 'source') : null;\n const trusted = slotSource !== null && slotSource !== 'unknown';\n const total = trusted ? configured : null;\n\n const refusal = str(r, 'parallelRefusal');\n const identity = str(r, 'runtimeKind');\n const contextPerSlot = count(r, 'contextPerSlot');\n const contextTotal = count(r, 'contextTotal');\n\n return {\n // IDENTIFIED where the producer says so. A base URL pointed at Ollama's\n // /v1 answers /v1/models — enough to keep the engine routable — while\n // 404ing /health and /props, and asserting 'llama-server' for it would\n // hide the one fact that matters: that runtime serialises this model.\n kind: identity === 'ollama' ? 'ollama' : identity === 'unknown' ? 'unknown' : 'llama-server',\n state: LLAMA_STATE[rawState] ?? 'unknown',\n endpoint: host !== null && port !== null ? `${host}:${port}` : (str(r, 'origin') ?? null),\n // A digest is not a name. `modelName` falls back to `model_path`'s basename\n // at the producer, which for a blob out of Ollama's content-addressed store\n // is `sha256-…`: it identifies the machine's storage layout and names\n // nothing. Null renders as \"unknown\", which is the honest answer.\n model: humaneModelName(str(r, 'modelName')) ?? humaneModelName(str(r, 'model')),\n slotsTotal: total,\n slotsBusy: trusted ? busy : null,\n // PER-SLOT, or nothing. `-c 65536 --parallel 4` gives each agent 16k, so\n // the `-c` total is usable only divided by a slot count we trust.\n contextTokens:\n contextPerSlot ??\n (total !== null && total > 0 && contextTotal !== null && contextTotal > 0\n ? Math.floor(contextTotal / total)\n : null),\n startedAt: str(r, 'startedAt'),\n parallel: {\n // llama-server's slots ARE its continuous-batching slots: a count read\n // back off the live server (`/props.total_slots`, `/slots`) is a count\n // of requests it will run at the same time, which is what the\n // measurement in docs/LOCAL-FLEET.md showed. An untrusted or absent\n // reading claims nothing — `null`, not `true`.\n capable: refusal !== null ? false : trusted && total !== null ? true : null,\n // The runtime's own words when it refuses, carried verbatim as evidence.\n refusal,\n slots: total,\n },\n reason: str(r, 'lastError'),\n supervised: bool(r, 'managed') ?? false,\n sampledAt: str(r, 'checkedAt'),\n };\n}\n\n/** A displayable model name, or null for an absent or digest-shaped one. */\nfunction humaneModelName(raw: string | null): string | null {\n if (raw === null || raw.length === 0) return null;\n const base = raw.split('/').pop() ?? raw;\n if (base.length === 0) return null;\n if (/^sha256[-:][0-9a-f]{16,}$/i.test(base)) return null;\n return base;\n}\n\nexport function projectServingRuntime(raw: unknown): ServingRuntimeSnapshot | null {\n const r = record(raw);\n if (!r) return null;\n // A body already in this surface's own shape wins; otherwise try owner R's.\n if (!('kind' in r) && 'slots' in r && 'state' in r) {\n const llama = projectLlamaShape(r);\n if (llama !== null) return llama;\n }\n return {\n kind: oneOf(r, 'kind', RUNTIME_KINDS, 'unknown'),\n state: oneOf(r, 'state', RUNTIME_STATES, 'unknown'),\n endpoint: str(r, 'endpoint'),\n model: str(r, 'model'),\n slotsTotal: count(r, 'slotsTotal'),\n slotsBusy: count(r, 'slotsBusy'),\n contextTokens: count(r, 'contextTokens'),\n startedAt: str(r, 'startedAt'),\n parallel: projectParallelism(r['parallel']),\n reason: str(r, 'reason'),\n // A runtime this server cannot supervise must not be offered buttons, so\n // an unreported flag reads as NOT supervised. The conservative default is\n // \"no controls\" rather than controls that 404 on the first click.\n supervised: bool(r, 'supervised') ?? false,\n sampledAt: str(r, 'sampledAt'),\n };\n}\n\nfunction projectAgent(raw: unknown): FleetAgent | null {\n const r = record(raw);\n if (!r) return null;\n const id = str(r, 'id');\n if (id === null) return null;\n return {\n id,\n task: str(r, 'task'),\n repo: str(r, 'repo'),\n engine: str(r, 'engine'),\n model: str(r, 'model'),\n state: oneOf(r, 'state', AGENT_STATES, 'running'),\n startedAt: str(r, 'startedAt'),\n slot: count(r, 'slot'),\n };\n}\n\nexport function projectFleet(raw: unknown): FleetSnapshot | null {\n const r = record(raw);\n if (!r) return null;\n const rawAgents = Array.isArray(r['agents']) ? (r['agents'] as unknown[]) : [];\n return {\n agents: rawAgents.map(projectAgent).filter((a): a is FleetAgent => a !== null),\n queueDepth: count(r, 'queueDepth'),\n slotsTotal: count(r, 'slotsTotal'),\n slotsBusy: count(r, 'slotsBusy'),\n notes: strings(r, 'notes'),\n sampledAt: str(r, 'sampledAt'),\n };\n}\n\nfunction projectRefusal(raw: unknown): LocalOnlyRefusal | null {\n const r = record(raw);\n if (!r) return null;\n const engine = str(r, 'engine');\n if (engine === null) return null;\n return { engine, reason: str(r, 'reason') ?? 'The dispatcher refuses this engine while local-only is on.' };\n}\n\n/**\n * `source` is carried through as the resolver's own value — it is no longer\n * flattened onto a smaller union here.\n *\n * The flattening this replaced mapped `'latch'` onto `'config'`, which tells\n * an operator the mode is stored in the config and editable when in fact the\n * process has pinned it for its own lifetime. Only the separate `mutable`\n * flag kept that from reaching the screen as a working switch that silently\n * does nothing. An unrecognised value lands on `'unknown'`, which makes no\n * claim at all.\n */\n/** Sources that pin the mode to this server process, whatever the config says. */\nconst PINNED_SOURCES: ReadonlySet<LocalOnlySource> = new Set<LocalOnlySource>([\n 'env',\n 'config+env',\n 'latch',\n 'unknown',\n]);\n\nexport function projectLocalOnly(raw: unknown): LocalOnlyPolicy | null {\n const r = record(raw);\n if (!r) return null;\n const rawRefusals = Array.isArray(r['refuses']) ? (r['refuses'] as unknown[]) : [];\n const source = oneOf(r, 'source', LOCAL_ONLY_SOURCES, 'unknown');\n return {\n // An unreported `enabled` is NOT \"on\": claiming cloud is unreachable when\n // the server never said so is the one error this policy cannot afford.\n enabled: bool(r, 'enabled') ?? false,\n source,\n refuses: rawRefusals.map(projectRefusal).filter((x): x is LocalOnlyRefusal => x !== null),\n // A policy pinned by the server's own process — an env var, or a latch\n // this process already took — is never editable from here, whatever the\n // body claims: a write would be silently overridden on the next read.\n // `'unknown'` is treated as pinned too, because offering a switch for a\n // policy this client cannot identify is the more expensive mistake.\n mutable: PINNED_SOURCES.has(source) ? false : (bool(r, 'mutable') ?? false),\n detail: str(r, 'detail'),\n sampledAt: str(r, 'sampledAt') ?? str(r, 'checkedAt'),\n };\n}\n\n// ---------------------------------------------------------------------------\n// The capacity verdict — the point of this whole file\n// ---------------------------------------------------------------------------\n\nexport type CapacityVerdict = 'batched' | 'serialized' | 'unknown' | 'offline';\n\nexport interface RuntimeCapacity {\n verdict: CapacityVerdict;\n /**\n * How many agents can genuinely be in flight at once. `null` means it\n * cannot be known from this reading — which is NOT the same as 0 and must\n * never render as a number.\n */\n effectiveConcurrency: number | null;\n /** What the runtime was configured with, honoured or not. */\n configuredSlots: number | null;\n /**\n * True when the configured slot count is larger than what the runtime will\n * actually run in parallel. While this is true, the configured number must\n * not be presented as capacity anywhere on the screen.\n */\n overstated: boolean;\n headline: string;\n detail: string;\n tone: 'success' | 'warning' | 'danger' | 'neutral' | 'unknown';\n}\n\n/** Human name for a runtime, used in sentences. */\nexport function runtimeLabel(kind: ServingRuntimeKind): string {\n switch (kind) {\n case 'llama-server':\n return 'llama-server';\n case 'ollama':\n return 'Ollama';\n case 'vllm':\n return 'vLLM';\n default:\n return 'the serving runtime';\n }\n}\n\nexport function runtimeCapacity(runtime: ServingRuntimeSnapshot | null): RuntimeCapacity {\n if (runtime === null) {\n return {\n verdict: 'unknown',\n effectiveConcurrency: null,\n configuredSlots: null,\n overstated: false,\n headline: 'No serving runtime reported',\n detail:\n 'Serving runtime unavailable — concurrency unknown.',\n tone: 'unknown',\n };\n }\n\n const configured = runtime.slotsTotal ?? runtime.parallel.slots;\n const name = runtimeLabel(runtime.kind);\n\n if (runtime.state !== 'running') {\n return {\n verdict: 'offline',\n effectiveConcurrency: null,\n configuredSlots: configured,\n overstated: false,\n headline: runtime.state === 'stopped' ? 'Not serving' : `Serving runtime is ${runtime.state}`,\n detail:\n runtime.state === 'stopped'\n ? `${name} is not running, so no local agent can take a turn. Start it before dispatching a fleet.`\n : `${name} is ${runtime.state}; capacity is not settled until it reports running.`,\n tone: runtime.state === 'stopped' ? 'neutral' : 'warning',\n };\n }\n\n if (runtime.parallel.capable === false) {\n // The measured case. The configured number is real — it is simply not a\n // concurrency, and printing it as one is the failure mode this exists for.\n const overstated = configured !== null && configured > 1;\n return {\n verdict: 'serialized',\n effectiveConcurrency: 1,\n configuredSlots: configured,\n overstated,\n headline: `${name} serializes requests`,\n detail: overstated\n ? `${name} has ${configured} slots configured but runs one request at a time for this model, so the real capacity is one agent. The other ${configured - 1} would queue, and a queue looks like slowness rather than a wait.`\n : `${name} runs one request at a time for this model. A second agent waits for the first to finish.`,\n tone: 'danger',\n };\n }\n\n if (runtime.parallel.capable === null) {\n return {\n verdict: 'unknown',\n effectiveConcurrency: null,\n configuredSlots: configured,\n overstated: false,\n headline: 'Batching not confirmed',\n detail: `${name} is running${\n configured === null ? '' : ` with ${configured} slots configured`\n }, but it has not reported whether it batches concurrent requests for this model. Until it does, this panel will not quote a concurrency it cannot stand behind.`,\n tone: 'unknown',\n };\n }\n\n if (configured === null) {\n return {\n verdict: 'batched',\n effectiveConcurrency: null,\n configuredSlots: null,\n overstated: false,\n headline: `${name} batches concurrent requests`,\n detail: `${name} batches, but reported no slot count, so the ceiling on concurrent agents is unknown.`,\n tone: 'unknown',\n };\n }\n\n return {\n verdict: 'batched',\n effectiveConcurrency: configured,\n configuredSlots: configured,\n overstated: false,\n headline: `${configured} agent${configured === 1 ? '' : 's'} in parallel`,\n detail: `${name} batches across ${configured} slot${\n configured === 1 ? '' : 's'\n } sharing one copy of the weights, so ${configured} agent${\n configured === 1 ? '' : 's'\n } can be mid-turn at once.`,\n tone: 'success',\n };\n}\n\n/**\n * The measured comparison, kept as data so the panel renders it as evidence\n * rather than as an assertion in prose. Source: docs/LOCAL-FLEET.md, four\n * concurrent requests against the same Qwen3.8 GGUF on this machine.\n */\nexport interface ConcurrencyEvidenceRow {\n runtime: string;\n /** Per-request completion times, in seconds, in the order they returned. */\n perRequestSeconds: readonly number[];\n wallSeconds: number;\n verdict: string;\n}\n\nexport const CONCURRENCY_EVIDENCE: readonly ConcurrencyEvidenceRow[] = [\n {\n runtime: 'Ollama',\n perRequestSeconds: [3.7, 7.6, 11.4, 15.2],\n wallSeconds: 15.2,\n verdict: 'staggered — the requests queued',\n },\n {\n runtime: 'llama-server',\n perRequestSeconds: [8.6, 8.9, 9.0, 9.0],\n wallSeconds: 9.1,\n verdict: 'together — the requests batched',\n },\n] as const;\n\n// ---------------------------------------------------------------------------\n// Utilisation\n// ---------------------------------------------------------------------------\n\nexport interface SlotUtilisation {\n busy: number | null;\n total: number | null;\n /** 0–100, or null when either half is unknown. */\n percent: number | null;\n /** The share in words, by the one percent rule (\"<1%\", \"99%\"); null when unknown. */\n percentText: string | null;\n /** Every slot is taken: the next turn waits. */\n saturated: boolean;\n /** One line, safe to render on its own. */\n label: string;\n}\n\n/**\n * Slot occupancy.\n *\n * `total` is the EFFECTIVE concurrency, not the configured slot count — on a\n * runtime that serializes, one busy slot out of four configured is full, and\n * drawing it as 25% would tell the operator there is room when there is none.\n */\nexport function slotUtilisation(busy: number | null, effectiveTotal: number | null): SlotUtilisation {\n if (busy === null || effectiveTotal === null || effectiveTotal <= 0) {\n return {\n busy,\n total: effectiveTotal,\n percent: null,\n percentText: null,\n saturated: false,\n label:\n busy === null\n ? 'Slot occupancy is not reported.'\n : `${busy} in flight; the number of slots is not known, so occupancy cannot be drawn.`,\n };\n }\n const clamped = Math.min(busy, effectiveTotal);\n const share = (clamped / effectiveTotal) * 100;\n const percent = Math.round(share);\n const saturated = busy >= effectiveTotal;\n return {\n busy,\n total: effectiveTotal,\n percent,\n percentText: percentText(share),\n saturated,\n label: saturated\n ? `All ${effectiveTotal} slot${effectiveTotal === 1 ? '' : 's'} busy — new turns wait for one to free up.`\n : `${busy} of ${effectiveTotal} slot${effectiveTotal === 1 ? '' : 's'} busy.`,\n };\n}\n\n/**\n * What the operator is actually waiting on.\n *\n * Saturation alone is not bad news — a fleet at full occupancy is a fleet\n * working. Saturation WITH a queue is the thing to say plainly, because from\n * the outside it is indistinguishable from the machine being slow.\n */\nexport interface FleetPressure {\n state: 'idle' | 'working' | 'saturated' | 'queued' | 'unknown';\n headline: string;\n detail: string;\n tone: 'success' | 'running' | 'warning' | 'neutral' | 'unknown';\n}\n\nexport function fleetPressure(\n fleet: FleetSnapshot | null,\n utilisation: SlotUtilisation,\n capacity: RuntimeCapacity,\n): FleetPressure {\n if (fleet === null) {\n return {\n state: 'unknown',\n headline: 'Fleet not reported',\n // A missing reading — never shown as an empty fleet.\n detail: 'Fleet status unavailable.',\n tone: 'unknown',\n };\n }\n\n const running = fleet.agents.filter((a) => a.state !== 'queued').length;\n const queued = fleet.queueDepth ?? fleet.agents.filter((a) => a.state === 'queued').length;\n\n if (queued > 0) {\n return {\n state: 'queued',\n headline: `${queued} waiting for a slot`,\n detail:\n capacity.verdict === 'serialized'\n ? `${running} turn${running === 1 ? '' : 's'} in flight and ${queued} queued. This runtime serializes, so the queue drains one at a time — the wait is a wait, not slow inference.`\n : `${running} turn${running === 1 ? '' : 's'} in flight and ${queued} queued behind ${\n utilisation.total === null ? 'the available slots' : `${utilisation.total} slots`\n }. Queued turns have not started; their wall time is queueing, not work.`,\n tone: 'warning',\n };\n }\n\n if (running === 0) {\n return {\n state: 'idle',\n headline: 'Nothing in flight',\n detail: 'No agent is mid-turn.',\n tone: 'neutral',\n };\n }\n\n if (utilisation.saturated) {\n return {\n state: 'saturated',\n headline: `All slots busy · ${running} in flight`,\n detail: `Every slot is working and nothing is queued behind them. The next turn will wait, but none is waiting yet.`,\n tone: 'running',\n };\n }\n\n return {\n state: 'working',\n headline: `${running} in flight`,\n detail: utilisation.label,\n tone: 'running',\n };\n}\n\n// ---------------------------------------------------------------------------\n// Local-only impact\n// ---------------------------------------------------------------------------\n\nexport interface SeatLike {\n id: string;\n engine: string;\n label: string;\n}\n\nexport interface LocalOnlyImpact {\n /** Seats that become unreachable while the policy is on. */\n blocked: SeatLike[];\n /** Seats unaffected — the local ones. */\n allowed: SeatLike[];\n /** The named refusal per blocked engine, from the server when it sent one. */\n refusalFor: (engine: string) => string;\n /** One line summarising the blast radius against the CURRENT seat roster. */\n summary: string;\n}\n\nconst DEFAULT_REFUSAL = 'Refused: local-only is on and this engine is not local.';\n\n/**\n * What local-only would block, measured against the seats that actually exist\n * on this machine right now.\n *\n * Deliberately computed from the live roster rather than from a fixed list of\n * cloud engines: the sentence \"this would block 3 of your 5 seats\" is only\n * trustworthy if the 5 is the operator's own number.\n */\nexport function localOnlyImpact(\n policy: LocalOnlyPolicy | null,\n seats: readonly SeatLike[],\n): LocalOnlyImpact {\n const blocked = seats.filter((s) => s.engine !== 'local');\n const allowed = seats.filter((s) => s.engine === 'local');\n const byEngine = new Map<string, string>();\n for (const refusal of policy?.refuses ?? []) byEngine.set(refusal.engine, refusal.reason);\n\n const summary =\n seats.length === 0\n ? 'No seats are configured, so there is nothing for this policy to block yet.'\n : blocked.length === 0\n ? `Every one of your ${seats.length} seat${seats.length === 1 ? '' : 's'} is local, so this policy would block nothing today.`\n : `${blocked.length} of ${seats.length} seat${seats.length === 1 ? '' : 's'} would become unreachable; ${allowed.length} local seat${allowed.length === 1 ? '' : 's'} would keep working.`;\n\n return {\n blocked,\n allowed,\n refusalFor: (engine: string) => byEngine.get(engine) ?? DEFAULT_REFUSAL,\n summary,\n };\n}\n\n/** Where the policy came from, as a sentence an operator can act on. */\nexport function localOnlySourceNote(policy: LocalOnlyPolicy | null): string | null {\n if (policy === null) return null;\n // The policy's own sentence, when it wrote one. It knows things this client\n // does not — e.g. that an env var tried and failed to clear a persisted\n // local-only — and a template would erase exactly that.\n if (policy.detail !== null) return policy.detail;\n switch (policy.source) {\n case 'env':\n case 'config+env':\n return 'This policy is pinned by an environment variable on the server process, so it cannot be changed from here — restart the server without it to take the switch back.';\n case 'latch':\n return 'This process already observed a persisted local-only and has pinned it for its own lifetime, so turning it off takes effect for new dispatches but not for the seams that latched. Restart the server to clear it.';\n case 'config':\n return 'Stored in the hub config and applied on the next dispatch.';\n case 'off':\n return 'Not configured; cloud engines are reachable.';\n default:\n return 'The server did not say where this setting came from.';\n }\n}\n\n// ---------------------------------------------------------------------------\n// Small formatters\n// ---------------------------------------------------------------------------\n\n/** \"4m 12s\" since an ISO instant, against a live clock. Absent stays absent. */\nexport function elapsedSince(iso: string | null, now: number): string {\n if (!iso) return UNKNOWN;\n const at = Date.parse(iso);\n if (Number.isNaN(at)) return UNKNOWN;\n return formatDuration(Math.max(0, now - at));\n}\n\n/** Uptime for the runtime header. Same rules, different words. */\nexport function formatUptime(startedAt: string | null, now: number): string {\n if (!startedAt) return UNKNOWN;\n const at = Date.parse(startedAt);\n if (Number.isNaN(at)) return UNKNOWN;\n return `up ${formatDuration(Math.max(0, now - at))}`;\n}\n\n/** Agent rows, ordered so the reader's first question is answered first. */\nexport function orderAgents(agents: readonly FleetAgent[]): FleetAgent[] {\n const rank: Record<FleetAgentState, number> = { running: 0, finishing: 1, queued: 2 };\n return [...agents].sort((a, b) => {\n const byState = rank[a.state] - rank[b.state];\n if (byState !== 0) return byState;\n // Longest-running first: the one most likely to be stuck is the one an\n // operator scanning this table is looking for.\n const at = a.startedAt ? Date.parse(a.startedAt) : Number.NaN;\n const bt = b.startedAt ? Date.parse(b.startedAt) : Number.NaN;\n if (Number.isNaN(at) && Number.isNaN(bt)) return a.id.localeCompare(b.id);\n if (Number.isNaN(at)) return 1;\n if (Number.isNaN(bt)) return -1;\n return at - bt;\n });\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{a as e,c as t}from"./index-CyK31YRm.js";import{D as n,F as r,U as i,c as a,h as o,l as s,o as c}from"./global-y_exIyAP.js";import{n as l,t as u}from"./control-queries-CXtmZZH6.js";import{c as d,l as f,u as p}from"./fleet-model-DOK8ZtEt.js";var m=t(e(),1),h=`verse-runtime`,g=`verse-fleet`,_=`verse-local-only`;function v(e,t){return t instanceof c?t.status===404?`This server doesn't have ${e}.`:`${e} answered HTTP ${t.status}.`:`${e} could not be reached.`}function y(e,t){return async n=>{try{let r=t(await a(e,n));return r===null?{value:null,available:!0,reason:`Unrecognized response — update Ashlr.`}:{value:r,available:!0,reason:null}}catch(t){if(t instanceof c&&t.status===401||t instanceof DOMException&&t.name===`AbortError`)throw t;return{value:null,available:!1,reason:v(e,t)}}}}var b={key:h,fetch:y(`/api/verse/runtime`,p)},x={key:g,fetch:y(`/api/verse/fleet`,d)},S={key:_,fetch:y(`/api/verse/local-only`,f)};async function C(e,t){let i=n();if(!i)throw new l;let a=await s(e,t,i);return r(),a}async function w(e){let t=await C(`/api/verse/runtime`,{action:e});return i(h),i(g),i(u),t}async function T(e){let t=await C(`/api/verse/local-only`,{enabled:e});return i(_),i(u),t}var E=4e3;function D(e=E){let t=o(b),n=o(x);(0,m.useEffect)(()=>{let r=null,i=()=>{t(),n()},a=()=>{r===null&&(r=setInterval(i,e))},o=()=>{r!==null&&(clearInterval(r),r=null)},s=()=>{if(document.visibilityState===`hidden`){o();return}i(),a()};return document.visibilityState!==`hidden`&&a(),document.addEventListener(`visibilitychange`,s),()=>{o(),document.removeEventListener(`visibilitychange`,s)}},[e,t,n])}export{b as a,w as i,x as n,T as o,S as r,D as s,g as t};
2
- //# sourceMappingURL=fleet-queries-BeEAqD6y.js.map
1
+ import{a as e,c as t}from"./index-DZ-UQ7v8.js";import{I as n,O as r,W as i,g as a,l as o,o as s,u as c}from"./global-CMNW_dqn.js";import{n as l,t as u}from"./control-queries-B9K-wmcB.js";import{c as d,l as f,u as p}from"./fleet-model-BvDR3gjp.js";var m=t(e(),1),h=`verse-runtime`,g=`verse-fleet`,_=`verse-local-only`;function v(e,t){return t instanceof s?t.status===404?`This server doesn't have ${e}.`:`${e} answered HTTP ${t.status}.`:`${e} could not be reached.`}function y(e,t){return async n=>{try{let r=t(await o(e,n));return r===null?{value:null,available:!0,reason:`Unrecognized response — update Ashlr.`}:{value:r,available:!0,reason:null}}catch(t){if(t instanceof s&&t.status===401||t instanceof DOMException&&t.name===`AbortError`)throw t;return{value:null,available:!1,reason:v(e,t)}}}}var b={key:h,fetch:y(`/api/verse/runtime`,p)},x={key:g,fetch:y(`/api/verse/fleet`,d)},S={key:_,fetch:y(`/api/verse/local-only`,f)};async function C(e,t){let i=r();if(!i)throw new l;let a=await c(e,t,i);return n(),a}async function w(e){let t=await C(`/api/verse/runtime`,{action:e});return i(h),i(g),i(u),t}async function T(e){let t=await C(`/api/verse/local-only`,{enabled:e});return i(_),i(u),t}var E=4e3;function D(e=E){let t=a(b),n=a(x);(0,m.useEffect)(()=>{let r=null,i=()=>{t(),n()},a=()=>{r===null&&(r=setInterval(i,e))},o=()=>{r!==null&&(clearInterval(r),r=null)},s=()=>{if(document.visibilityState===`hidden`){o();return}i(),a()};return document.visibilityState!==`hidden`&&a(),document.addEventListener(`visibilitychange`,s),()=>{o(),document.removeEventListener(`visibilitychange`,s)}},[e,t,n])}export{b as a,w as i,x as n,T as o,S as r,D as s,g as t};
2
+ //# sourceMappingURL=fleet-queries-W2UFVMZJ.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"fleet-queries-BeEAqD6y.js","names":[],"sources":["../../../../../../src/web-ui/routes/verse/autonomy/fleet-queries.ts"],"sourcesContent":["/**\n * routes/verse/autonomy/fleet-queries.ts — the reads and writes behind the\n * local-fleet surfaces: `GET/POST /api/verse/runtime`,\n * `GET/POST /api/verse/local-only`, and `GET /api/verse/fleet`.\n *\n * Same contract as control-queries.ts — reads are QueryDefs consumed through\n * useQuery, writes go through the mutation-token gate and invalidate exactly\n * the keys they affect — with two differences that both come from these three\n * routes landing in parallel with this surface (owners R, L, F):\n *\n * 1. **The reads do not throw on absence.** A server without these routes\n * answers 404, and a 404 means \"this panel has no source yet\", not\n * \"Autonomy is broken\". The panels render a designed degraded state from\n * `OptionalFleetRead.reason`. 401 still propagates: an expired read\n * session is an unauthorized state for the whole surface.\n * 2. **The bodies are narrowed structurally, not cast.** `fleet-model.ts`\n * projects every field, so a name drift between the route and this client\n * degrades to \"unknown\" rather than throwing at render time.\n *\n * Polling: the fleet and the runtime are the two things on this screen that\n * change while nobody clicks anything, and neither has an SSE event yet, so\n * `useFleetPolling` re-reads them on an interval — pausing while the tab is\n * hidden, because a background tab polling a runtime probe is pure waste.\n */\nimport { useEffect } from 'react';\nimport { ApiError, apiGet, apiPost } from '../../../data/client.js';\nimport { getMutationToken, touchMutationHold } from '../../../data/auth-store.js';\nimport { invalidate } from '../../../data/cache.js';\nimport type { QueryDef } from '../../../data/queries.js';\nimport { useRefetch } from '../../../data/hooks.js';\nimport { VERSE_CONTROL_KEY, VerseControlLockedError } from './control-queries.js';\nimport type {\n FleetSnapshot,\n LocalOnlyPolicy,\n LocalOnlyUpdateResult,\n OptionalFleetRead,\n RuntimeAction,\n RuntimeActionResult,\n ServingRuntimeSnapshot,\n} from './fleet-contract.js';\nimport { projectFleet, projectLocalOnly, projectServingRuntime } from './fleet-model.js';\n\nexport const VERSE_RUNTIME_KEY = 'verse-runtime';\nexport const VERSE_FLEET_KEY = 'verse-fleet';\nexport const VERSE_LOCAL_ONLY_KEY = 'verse-local-only';\n\nfunction describeAbsence(path: string, err: unknown): string {\n if (err instanceof ApiError) {\n if (err.status === 404) {\n // A missing route — never read as an idle machine.\n return `This server doesn't have ${path}.`;\n }\n return `${path} answered HTTP ${err.status}.`;\n }\n return `${path} could not be reached.`;\n}\n\n/**\n * One optional read, projected. `value` is null both when the route was absent\n * and when it answered something this client could not narrow — `available`\n * and `reason` are what tell the two apart.\n */\nfunction optionalRead<T>(\n path: string,\n project: (raw: unknown) => T | null,\n): (signal?: AbortSignal) => Promise<OptionalFleetRead<T>> {\n return async (signal) => {\n try {\n const raw = await apiGet<unknown>(path, signal);\n const value = project(raw);\n if (value === null) {\n return {\n value: null,\n available: true,\n // Nothing is shown rather than guessed from an unknown shape.\n reason: 'Unrecognized response — update Ashlr.',\n };\n }\n return { value, available: true, reason: null };\n } catch (err) {\n if (err instanceof ApiError && err.status === 401) throw err;\n if (err instanceof DOMException && err.name === 'AbortError') throw err;\n return { value: null, available: false, reason: describeAbsence(path, err) };\n }\n };\n}\n\n/** GET /api/verse/runtime — is the serving runtime up, and what can it do. */\nexport const servingRuntimeQuery: QueryDef<OptionalFleetRead<ServingRuntimeSnapshot>> = {\n key: VERSE_RUNTIME_KEY,\n fetch: optionalRead('/api/verse/runtime', projectServingRuntime),\n};\n\n/** GET /api/verse/fleet — the agents in flight right now. */\nexport const fleetQuery: QueryDef<OptionalFleetRead<FleetSnapshot>> = {\n key: VERSE_FLEET_KEY,\n fetch: optionalRead('/api/verse/fleet', projectFleet),\n};\n\n/** GET /api/verse/local-only — the refusal policy and what it refuses. */\nexport const localOnlyQuery: QueryDef<OptionalFleetRead<LocalOnlyPolicy>> = {\n key: VERSE_LOCAL_ONLY_KEY,\n fetch: optionalRead('/api/verse/local-only', projectLocalOnly),\n};\n\n// ---------------------------------------------------------------------------\n// Writes\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/**\n * Start / stop / restart the serving runtime.\n *\n * Every one of these changes what the fleet can do, so all three fleet keys\n * and the control snapshot are invalidated together — a restarted runtime\n * with a stale slot count beside it is exactly the disagreement this surface\n * exists to prevent.\n */\nexport async function runRuntimeAction(action: RuntimeAction): Promise<RuntimeActionResult> {\n const result = await post<RuntimeActionResult>('/api/verse/runtime', { action });\n invalidate(VERSE_RUNTIME_KEY);\n invalidate(VERSE_FLEET_KEY);\n invalidate(VERSE_CONTROL_KEY);\n return result;\n}\n\n/** Turn the local-only refusal on or off. */\nexport async function setLocalOnly(enabled: boolean): Promise<LocalOnlyUpdateResult> {\n const result = await post<LocalOnlyUpdateResult>('/api/verse/local-only', { enabled });\n invalidate(VERSE_LOCAL_ONLY_KEY);\n invalidate(VERSE_CONTROL_KEY);\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Polling\n// ---------------------------------------------------------------------------\n\n/** Fast enough that \"is it working\" is answered live; slow enough to be free. */\nexport const FLEET_POLL_MS = 4000;\n\n/**\n * Keep the runtime and fleet reads live while this section is mounted and the\n * tab is visible.\n *\n * Both routes probe a local process, so this is cheap — but only while someone\n * is looking. `visibilitychange` stops the timer when the tab is hidden and\n * takes one immediate reading when it comes back, so a fleet view returned to\n * after lunch is current on the first frame rather than up to an interval old.\n */\nexport function useFleetPolling(intervalMs: number = FLEET_POLL_MS): void {\n const refetchRuntime = useRefetch(servingRuntimeQuery);\n const refetchFleet = useRefetch(fleetQuery);\n\n useEffect(() => {\n let timer: ReturnType<typeof setInterval> | null = null;\n\n const tick = (): void => {\n refetchRuntime();\n refetchFleet();\n };\n\n const start = (): void => {\n if (timer !== null) return;\n timer = setInterval(tick, intervalMs);\n };\n\n const stop = (): void => {\n if (timer === null) return;\n clearInterval(timer);\n timer = null;\n };\n\n const onVisibility = (): void => {\n if (document.visibilityState === 'hidden') {\n stop();\n return;\n }\n tick();\n start();\n };\n\n if (document.visibilityState !== 'hidden') start();\n document.addEventListener('visibilitychange', onVisibility);\n return () => {\n stop();\n document.removeEventListener('visibilitychange', onVisibility);\n };\n }, [intervalMs, refetchRuntime, refetchFleet]);\n}\n"],"mappings":"sQA0Ca,EAAoB,gBACpB,EAAkB,cAClB,EAAuB,mBAEpC,SAAS,EAAgB,EAAc,EAAsB,CAQ3D,OAPI,aAAe,EACb,EAAI,SAAW,IAEV,4BAA4B,EAAK,GAEnC,GAAG,EAAK,iBAAiB,EAAI,OAAO,GAEtC,GAAG,EAAK,uBACjB,CAOA,SAAS,EACP,EACA,EACyD,CACzD,OAAO,KAAO,IAAW,CACvB,GAAI,CAEF,IAAM,EAAQ,EAAQ,MADJ,EAAgB,EAAM,CAAM,CACrB,EASzB,OARI,IAAU,KACL,CACL,MAAO,KACP,UAAW,GAEX,OAAQ,uCACV,EAEK,CAAE,QAAO,UAAW,GAAM,OAAQ,IAAK,CAChD,OAAS,EAAK,CAEZ,GADI,aAAe,GAAY,EAAI,SAAW,KAC1C,aAAe,cAAgB,EAAI,OAAS,aAAc,MAAM,EACpE,MAAO,CAAE,MAAO,KAAM,UAAW,GAAO,OAAQ,EAAgB,EAAM,CAAG,CAAE,CAC7E,CACF,CACF,CAGA,IAAa,EAA2E,CACtF,IAAK,EACL,MAAO,EAAa,qBAAsB,CAAqB,CACjE,EAGa,EAAyD,CACpE,IAAK,EACL,MAAO,EAAa,mBAAoB,CAAY,CACtD,EAGa,EAA+D,CAC1E,IAAK,EACL,MAAO,EAAa,wBAAyB,CAAgB,CAC/D,EAMA,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,CAUA,eAAsB,EAAiB,EAAqD,CAC1F,IAAM,EAAS,MAAM,EAA0B,qBAAsB,CAAE,QAAO,CAAC,EAI/E,OAHA,EAAW,CAAiB,EAC5B,EAAW,CAAe,EAC1B,EAAW,CAAiB,EACrB,CACT,CAGA,eAAsB,EAAa,EAAkD,CACnF,IAAM,EAAS,MAAM,EAA4B,wBAAyB,CAAE,SAAQ,CAAC,EAGrF,OAFA,EAAW,CAAoB,EAC/B,EAAW,CAAiB,EACrB,CACT,CAOA,IAAa,EAAgB,IAW7B,SAAgB,EAAgB,EAAqB,EAAqB,CACxE,IAAM,EAAiB,EAAW,CAAmB,EAC/C,EAAe,EAAW,CAAU,GAE1C,EAAA,EAAA,UAAA,KAAgB,CACd,IAAI,EAA+C,KAE7C,MAAmB,CACvB,EAAe,EACf,EAAa,CACf,EAEM,MAAoB,CACpB,IAAU,OACd,EAAQ,YAAY,EAAM,CAAU,EACtC,EAEM,MAAmB,CACnB,IAAU,OACd,cAAc,CAAK,EACnB,EAAQ,KACV,EAEM,MAA2B,CAC/B,GAAI,SAAS,kBAAoB,SAAU,CACzC,EAAK,EACL,MACF,CACA,EAAK,EACL,EAAM,CACR,EAIA,OAFI,SAAS,kBAAoB,UAAU,EAAM,EACjD,SAAS,iBAAiB,mBAAoB,CAAY,MAC7C,CACX,EAAK,EACL,SAAS,oBAAoB,mBAAoB,CAAY,CAC/D,CACF,EAAG,CAAC,EAAY,EAAgB,CAAY,CAAC,CAC/C"}
1
+ {"version":3,"file":"fleet-queries-W2UFVMZJ.js","names":[],"sources":["../../../../../../src/web-ui/routes/verse/autonomy/fleet-queries.ts"],"sourcesContent":["/**\n * routes/verse/autonomy/fleet-queries.ts — the reads and writes behind the\n * local-fleet surfaces: `GET/POST /api/verse/runtime`,\n * `GET/POST /api/verse/local-only`, and `GET /api/verse/fleet`.\n *\n * Same contract as control-queries.ts — reads are QueryDefs consumed through\n * useQuery, writes go through the mutation-token gate and invalidate exactly\n * the keys they affect — with two differences that both come from these three\n * routes landing in parallel with this surface (owners R, L, F):\n *\n * 1. **The reads do not throw on absence.** A server without these routes\n * answers 404, and a 404 means \"this panel has no source yet\", not\n * \"Autonomy is broken\". The panels render a designed degraded state from\n * `OptionalFleetRead.reason`. 401 still propagates: an expired read\n * session is an unauthorized state for the whole surface.\n * 2. **The bodies are narrowed structurally, not cast.** `fleet-model.ts`\n * projects every field, so a name drift between the route and this client\n * degrades to \"unknown\" rather than throwing at render time.\n *\n * Polling: the fleet and the runtime are the two things on this screen that\n * change while nobody clicks anything, and neither has an SSE event yet, so\n * `useFleetPolling` re-reads them on an interval — pausing while the tab is\n * hidden, because a background tab polling a runtime probe is pure waste.\n */\nimport { useEffect } from 'react';\nimport { ApiError, apiGet, apiPost } from '../../../data/client.js';\nimport { getMutationToken, touchMutationHold } from '../../../data/auth-store.js';\nimport { invalidate } from '../../../data/cache.js';\nimport type { QueryDef } from '../../../data/queries.js';\nimport { useRefetch } from '../../../data/hooks.js';\nimport { VERSE_CONTROL_KEY, VerseControlLockedError } from './control-queries.js';\nimport type {\n FleetSnapshot,\n LocalOnlyPolicy,\n LocalOnlyUpdateResult,\n OptionalFleetRead,\n RuntimeAction,\n RuntimeActionResult,\n ServingRuntimeSnapshot,\n} from './fleet-contract.js';\nimport { projectFleet, projectLocalOnly, projectServingRuntime } from './fleet-model.js';\n\nexport const VERSE_RUNTIME_KEY = 'verse-runtime';\nexport const VERSE_FLEET_KEY = 'verse-fleet';\nexport const VERSE_LOCAL_ONLY_KEY = 'verse-local-only';\n\nfunction describeAbsence(path: string, err: unknown): string {\n if (err instanceof ApiError) {\n if (err.status === 404) {\n // A missing route — never read as an idle machine.\n return `This server doesn't have ${path}.`;\n }\n return `${path} answered HTTP ${err.status}.`;\n }\n return `${path} could not be reached.`;\n}\n\n/**\n * One optional read, projected. `value` is null both when the route was absent\n * and when it answered something this client could not narrow — `available`\n * and `reason` are what tell the two apart.\n */\nfunction optionalRead<T>(\n path: string,\n project: (raw: unknown) => T | null,\n): (signal?: AbortSignal) => Promise<OptionalFleetRead<T>> {\n return async (signal) => {\n try {\n const raw = await apiGet<unknown>(path, signal);\n const value = project(raw);\n if (value === null) {\n return {\n value: null,\n available: true,\n // Nothing is shown rather than guessed from an unknown shape.\n reason: 'Unrecognized response — update Ashlr.',\n };\n }\n return { value, available: true, reason: null };\n } catch (err) {\n if (err instanceof ApiError && err.status === 401) throw err;\n if (err instanceof DOMException && err.name === 'AbortError') throw err;\n return { value: null, available: false, reason: describeAbsence(path, err) };\n }\n };\n}\n\n/** GET /api/verse/runtime — is the serving runtime up, and what can it do. */\nexport const servingRuntimeQuery: QueryDef<OptionalFleetRead<ServingRuntimeSnapshot>> = {\n key: VERSE_RUNTIME_KEY,\n fetch: optionalRead('/api/verse/runtime', projectServingRuntime),\n};\n\n/** GET /api/verse/fleet — the agents in flight right now. */\nexport const fleetQuery: QueryDef<OptionalFleetRead<FleetSnapshot>> = {\n key: VERSE_FLEET_KEY,\n fetch: optionalRead('/api/verse/fleet', projectFleet),\n};\n\n/** GET /api/verse/local-only — the refusal policy and what it refuses. */\nexport const localOnlyQuery: QueryDef<OptionalFleetRead<LocalOnlyPolicy>> = {\n key: VERSE_LOCAL_ONLY_KEY,\n fetch: optionalRead('/api/verse/local-only', projectLocalOnly),\n};\n\n// ---------------------------------------------------------------------------\n// Writes\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/**\n * Start / stop / restart the serving runtime.\n *\n * Every one of these changes what the fleet can do, so all three fleet keys\n * and the control snapshot are invalidated together — a restarted runtime\n * with a stale slot count beside it is exactly the disagreement this surface\n * exists to prevent.\n */\nexport async function runRuntimeAction(action: RuntimeAction): Promise<RuntimeActionResult> {\n const result = await post<RuntimeActionResult>('/api/verse/runtime', { action });\n invalidate(VERSE_RUNTIME_KEY);\n invalidate(VERSE_FLEET_KEY);\n invalidate(VERSE_CONTROL_KEY);\n return result;\n}\n\n/** Turn the local-only refusal on or off. */\nexport async function setLocalOnly(enabled: boolean): Promise<LocalOnlyUpdateResult> {\n const result = await post<LocalOnlyUpdateResult>('/api/verse/local-only', { enabled });\n invalidate(VERSE_LOCAL_ONLY_KEY);\n invalidate(VERSE_CONTROL_KEY);\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Polling\n// ---------------------------------------------------------------------------\n\n/** Fast enough that \"is it working\" is answered live; slow enough to be free. */\nexport const FLEET_POLL_MS = 4000;\n\n/**\n * Keep the runtime and fleet reads live while this section is mounted and the\n * tab is visible.\n *\n * Both routes probe a local process, so this is cheap — but only while someone\n * is looking. `visibilitychange` stops the timer when the tab is hidden and\n * takes one immediate reading when it comes back, so a fleet view returned to\n * after lunch is current on the first frame rather than up to an interval old.\n */\nexport function useFleetPolling(intervalMs: number = FLEET_POLL_MS): void {\n const refetchRuntime = useRefetch(servingRuntimeQuery);\n const refetchFleet = useRefetch(fleetQuery);\n\n useEffect(() => {\n let timer: ReturnType<typeof setInterval> | null = null;\n\n const tick = (): void => {\n refetchRuntime();\n refetchFleet();\n };\n\n const start = (): void => {\n if (timer !== null) return;\n timer = setInterval(tick, intervalMs);\n };\n\n const stop = (): void => {\n if (timer === null) return;\n clearInterval(timer);\n timer = null;\n };\n\n const onVisibility = (): void => {\n if (document.visibilityState === 'hidden') {\n stop();\n return;\n }\n tick();\n start();\n };\n\n if (document.visibilityState !== 'hidden') start();\n document.addEventListener('visibilitychange', onVisibility);\n return () => {\n stop();\n document.removeEventListener('visibilitychange', onVisibility);\n };\n }, [intervalMs, refetchRuntime, refetchFleet]);\n}\n"],"mappings":"sQA0Ca,EAAoB,gBACpB,EAAkB,cAClB,EAAuB,mBAEpC,SAAS,EAAgB,EAAc,EAAsB,CAQ3D,OAPI,aAAe,EACb,EAAI,SAAW,IAEV,4BAA4B,EAAK,GAEnC,GAAG,EAAK,iBAAiB,EAAI,OAAO,GAEtC,GAAG,EAAK,uBACjB,CAOA,SAAS,EACP,EACA,EACyD,CACzD,OAAO,KAAO,IAAW,CACvB,GAAI,CAEF,IAAM,EAAQ,EAAQ,MADJ,EAAgB,EAAM,CAAM,CACrB,EASzB,OARI,IAAU,KACL,CACL,MAAO,KACP,UAAW,GAEX,OAAQ,uCACV,EAEK,CAAE,QAAO,UAAW,GAAM,OAAQ,IAAK,CAChD,OAAS,EAAK,CAEZ,GADI,aAAe,GAAY,EAAI,SAAW,KAC1C,aAAe,cAAgB,EAAI,OAAS,aAAc,MAAM,EACpE,MAAO,CAAE,MAAO,KAAM,UAAW,GAAO,OAAQ,EAAgB,EAAM,CAAG,CAAE,CAC7E,CACF,CACF,CAGA,IAAa,EAA2E,CACtF,IAAK,EACL,MAAO,EAAa,qBAAsB,CAAqB,CACjE,EAGa,EAAyD,CACpE,IAAK,EACL,MAAO,EAAa,mBAAoB,CAAY,CACtD,EAGa,EAA+D,CAC1E,IAAK,EACL,MAAO,EAAa,wBAAyB,CAAgB,CAC/D,EAMA,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,CAUA,eAAsB,EAAiB,EAAqD,CAC1F,IAAM,EAAS,MAAM,EAA0B,qBAAsB,CAAE,QAAO,CAAC,EAI/E,OAHA,EAAW,CAAiB,EAC5B,EAAW,CAAe,EAC1B,EAAW,CAAiB,EACrB,CACT,CAGA,eAAsB,EAAa,EAAkD,CACnF,IAAM,EAAS,MAAM,EAA4B,wBAAyB,CAAE,SAAQ,CAAC,EAGrF,OAFA,EAAW,CAAoB,EAC/B,EAAW,CAAiB,EACrB,CACT,CAOA,IAAa,EAAgB,IAW7B,SAAgB,EAAgB,EAAqB,EAAqB,CACxE,IAAM,EAAiB,EAAW,CAAmB,EAC/C,EAAe,EAAW,CAAU,GAE1C,EAAA,EAAA,UAAA,KAAgB,CACd,IAAI,EAA+C,KAE7C,MAAmB,CACvB,EAAe,EACf,EAAa,CACf,EAEM,MAAoB,CACpB,IAAU,OACd,EAAQ,YAAY,EAAM,CAAU,EACtC,EAEM,MAAmB,CACnB,IAAU,OACd,cAAc,CAAK,EACnB,EAAQ,KACV,EAEM,MAA2B,CAC/B,GAAI,SAAS,kBAAoB,SAAU,CACzC,EAAK,EACL,MACF,CACA,EAAK,EACL,EAAM,CACR,EAIA,OAFI,SAAS,kBAAoB,UAAU,EAAM,EACjD,SAAS,iBAAiB,mBAAoB,CAAY,MAC7C,CACX,EAAK,EACL,SAAS,oBAAoB,mBAAoB,CAAY,CAC/D,CACF,EAAG,CAAC,EAAY,EAAgB,CAAY,CAAC,CAC/C"}
@@ -1,2 +1,2 @@
1
- import{a as e,c as t}from"./index-CyK31YRm.js";var n=t(e(),1),r=[`a[href]`,`button:not([disabled])`,`input:not([disabled])`,`select:not([disabled])`,`textarea:not([disabled])`,`[tabindex]:not([tabindex="-1"])`].join(`,`);function i(e){return Array.from(e.querySelectorAll(r)).filter(e=>e.offsetParent!==null)}function a({open:e,containerRef:t,onClose:r,initialFocusRef:a}){(0,n.useEffect)(()=>{if(!e)return;let n=document.activeElement;return(a?.current??t.current)?.focus({preventScroll:!0}),()=>{n?.focus?.({preventScroll:!0})}},[e]),(0,n.useEffect)(()=>{if(!e)return;function n(e){if(e.key===`Escape`){e.stopPropagation(),r();return}if(e.key!==`Tab`)return;let n=t.current;if(!n)return;let a=i(n);if(a.length===0)return;let o=a[0],s=a[a.length-1];e.shiftKey&&document.activeElement===o?(e.preventDefault(),s.focus()):!e.shiftKey&&document.activeElement===s&&(e.preventDefault(),o.focus())}return document.addEventListener(`keydown`,n,!0),()=>document.removeEventListener(`keydown`,n,!0)},[e,r])}export{a as t};
2
- //# sourceMappingURL=focus-trap-ChZ5EFfM.js.map
1
+ import{a as e,c as t}from"./index-DZ-UQ7v8.js";var n=t(e(),1),r=[`a[href]`,`button:not([disabled])`,`input:not([disabled])`,`select:not([disabled])`,`textarea:not([disabled])`,`[tabindex]:not([tabindex="-1"])`].join(`,`);function i(e){return Array.from(e.querySelectorAll(r)).filter(e=>e.offsetParent!==null)}function a({open:e,containerRef:t,onClose:r,initialFocusRef:a}){(0,n.useEffect)(()=>{if(!e)return;let n=document.activeElement;return(a?.current??t.current)?.focus({preventScroll:!0}),()=>{n?.focus?.({preventScroll:!0})}},[e]),(0,n.useEffect)(()=>{if(!e)return;function n(e){if(e.key===`Escape`){e.stopPropagation(),r();return}if(e.key!==`Tab`)return;let n=t.current;if(!n)return;let a=i(n);if(a.length===0)return;let o=a[0],s=a[a.length-1];e.shiftKey&&document.activeElement===o?(e.preventDefault(),s.focus()):!e.shiftKey&&document.activeElement===s&&(e.preventDefault(),o.focus())}return document.addEventListener(`keydown`,n,!0),()=>document.removeEventListener(`keydown`,n,!0)},[e,r])}export{a as t};
2
+ //# sourceMappingURL=focus-trap-CtI1SlMX.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"focus-trap-ChZ5EFfM.js","names":[],"sources":["../../../../../../src/web-ui/components/primitives/focus-trap.ts"],"sourcesContent":["/**\n * components/primitives/focus-trap.ts — the modal focus contract, extracted\n * from Dialog.tsx so Sheet.tsx (the slide-over overlay) gets exactly the same\n * behavior instead of a second, subtly different implementation:\n *\n * - focus moves into the overlay (or `initialFocusRef`) on open\n * - focus is trapped inside while open (Tab/Shift+Tab wrap)\n * - focus returns to the element that opened it on close\n * - Escape closes\n *\n * Dialog.tsx is unchanged in behavior — it now calls this instead of\n * inlining it. Any future overlay MUST use this rather than hand-rolling a\n * trap (DESIGN.md §3, \"primitives own their accessibility\").\n */\nimport { useEffect, type RefObject } from 'react';\n\nexport const FOCUSABLE_SELECTOR = [\n 'a[href]',\n 'button:not([disabled])',\n 'input:not([disabled])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n '[tabindex]:not([tabindex=\"-1\"])',\n].join(',');\n\n/** Focusable descendants that are actually rendered (offsetParent is null for hidden nodes). */\nexport function focusableWithin(node: HTMLElement): HTMLElement[] {\n return Array.from(node.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)).filter((el) => el.offsetParent !== null);\n}\n\nexport interface FocusTrapOptions {\n open: boolean;\n containerRef: RefObject<HTMLElement | null>;\n onClose: () => void;\n initialFocusRef?: RefObject<HTMLElement | null>;\n}\n\nexport function useFocusTrap({ open, containerRef, onClose, initialFocusRef }: FocusTrapOptions): void {\n useEffect(() => {\n if (!open) return;\n const previouslyFocused = document.activeElement as HTMLElement | null;\n const target = initialFocusRef?.current ?? containerRef.current;\n target?.focus({ preventScroll: true });\n\n return () => {\n previouslyFocused?.focus?.({ preventScroll: true });\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- the refs are refs (stable identity is not the point); only `open` should re-run this.\n }, [open]);\n\n useEffect(() => {\n if (!open) return;\n function onKeyDown(e: KeyboardEvent) {\n if (e.key === 'Escape') {\n e.stopPropagation();\n onClose();\n return;\n }\n if (e.key !== 'Tab') return;\n const node = containerRef.current;\n if (!node) return;\n const focusable = focusableWithin(node);\n if (focusable.length === 0) return;\n const first = focusable[0]!;\n const last = focusable[focusable.length - 1]!;\n if (e.shiftKey && document.activeElement === first) {\n e.preventDefault();\n last.focus();\n } else if (!e.shiftKey && document.activeElement === last) {\n e.preventDefault();\n first.focus();\n }\n }\n document.addEventListener('keydown', onKeyDown, true);\n return () => document.removeEventListener('keydown', onKeyDown, true);\n // eslint-disable-next-line react-hooks/exhaustive-deps -- containerRef is a ref; re-binding on every render would drop in-flight key handling.\n }, [open, onClose]);\n}\n"],"mappings":"8DAgBa,EAAqB,CAChC,UACA,yBACA,wBACA,yBACA,2BACA,iCACF,CAAC,CAAC,KAAK,GAAG,EAGV,SAAgB,EAAgB,EAAkC,CAChE,OAAO,MAAM,KAAK,EAAK,iBAA8B,CAAkB,CAAC,CAAC,CAAC,OAAQ,GAAO,EAAG,eAAiB,IAAI,CACnH,CASA,SAAgB,EAAa,CAAE,OAAM,eAAc,UAAS,mBAA2C,EACrG,EAAA,EAAA,UAAA,KAAgB,CACd,GAAI,CAAC,EAAM,OACX,IAAM,EAAoB,SAAS,cAInC,OAHe,GAAiB,SAAW,EAAa,QAAA,EAChD,MAAM,CAAE,cAAe,EAAK,CAAC,MAExB,CACX,GAAmB,QAAQ,CAAE,cAAe,EAAK,CAAC,CACpD,CAEF,EAAG,CAAC,CAAI,CAAC,GAET,EAAA,EAAA,UAAA,KAAgB,CACd,GAAI,CAAC,EAAM,OACX,SAAS,EAAU,EAAkB,CACnC,GAAI,EAAE,MAAQ,SAAU,CACtB,EAAE,gBAAgB,EAClB,EAAQ,EACR,MACF,CACA,GAAI,EAAE,MAAQ,MAAO,OACrB,IAAM,EAAO,EAAa,QAC1B,GAAI,CAAC,EAAM,OACX,IAAM,EAAY,EAAgB,CAAI,EACtC,GAAI,EAAU,SAAW,EAAG,OAC5B,IAAM,EAAQ,EAAU,GAClB,EAAO,EAAU,EAAU,OAAS,GACtC,EAAE,UAAY,SAAS,gBAAkB,GAC3C,EAAE,eAAe,EACjB,EAAK,MAAM,GACF,CAAC,EAAE,UAAY,SAAS,gBAAkB,IACnD,EAAE,eAAe,EACjB,EAAM,MAAM,EAEhB,CAEA,OADA,SAAS,iBAAiB,UAAW,EAAW,EAAI,MACvC,SAAS,oBAAoB,UAAW,EAAW,EAAI,CAEtE,EAAG,CAAC,EAAM,CAAO,CAAC,CACpB"}
1
+ {"version":3,"file":"focus-trap-CtI1SlMX.js","names":[],"sources":["../../../../../../src/web-ui/components/primitives/focus-trap.ts"],"sourcesContent":["/**\n * components/primitives/focus-trap.ts — the modal focus contract, extracted\n * from Dialog.tsx so Sheet.tsx (the slide-over overlay) gets exactly the same\n * behavior instead of a second, subtly different implementation:\n *\n * - focus moves into the overlay (or `initialFocusRef`) on open\n * - focus is trapped inside while open (Tab/Shift+Tab wrap)\n * - focus returns to the element that opened it on close\n * - Escape closes\n *\n * Dialog.tsx is unchanged in behavior — it now calls this instead of\n * inlining it. Any future overlay MUST use this rather than hand-rolling a\n * trap (DESIGN.md §3, \"primitives own their accessibility\").\n */\nimport { useEffect, type RefObject } from 'react';\n\nexport const FOCUSABLE_SELECTOR = [\n 'a[href]',\n 'button:not([disabled])',\n 'input:not([disabled])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n '[tabindex]:not([tabindex=\"-1\"])',\n].join(',');\n\n/** Focusable descendants that are actually rendered (offsetParent is null for hidden nodes). */\nexport function focusableWithin(node: HTMLElement): HTMLElement[] {\n return Array.from(node.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)).filter((el) => el.offsetParent !== null);\n}\n\nexport interface FocusTrapOptions {\n open: boolean;\n containerRef: RefObject<HTMLElement | null>;\n onClose: () => void;\n initialFocusRef?: RefObject<HTMLElement | null>;\n}\n\nexport function useFocusTrap({ open, containerRef, onClose, initialFocusRef }: FocusTrapOptions): void {\n useEffect(() => {\n if (!open) return;\n const previouslyFocused = document.activeElement as HTMLElement | null;\n const target = initialFocusRef?.current ?? containerRef.current;\n target?.focus({ preventScroll: true });\n\n return () => {\n previouslyFocused?.focus?.({ preventScroll: true });\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- the refs are refs (stable identity is not the point); only `open` should re-run this.\n }, [open]);\n\n useEffect(() => {\n if (!open) return;\n function onKeyDown(e: KeyboardEvent) {\n if (e.key === 'Escape') {\n e.stopPropagation();\n onClose();\n return;\n }\n if (e.key !== 'Tab') return;\n const node = containerRef.current;\n if (!node) return;\n const focusable = focusableWithin(node);\n if (focusable.length === 0) return;\n const first = focusable[0]!;\n const last = focusable[focusable.length - 1]!;\n if (e.shiftKey && document.activeElement === first) {\n e.preventDefault();\n last.focus();\n } else if (!e.shiftKey && document.activeElement === last) {\n e.preventDefault();\n first.focus();\n }\n }\n document.addEventListener('keydown', onKeyDown, true);\n return () => document.removeEventListener('keydown', onKeyDown, true);\n // eslint-disable-next-line react-hooks/exhaustive-deps -- containerRef is a ref; re-binding on every render would drop in-flight key handling.\n }, [open, onClose]);\n}\n"],"mappings":"8DAgBa,EAAqB,CAChC,UACA,yBACA,wBACA,yBACA,2BACA,iCACF,CAAC,CAAC,KAAK,GAAG,EAGV,SAAgB,EAAgB,EAAkC,CAChE,OAAO,MAAM,KAAK,EAAK,iBAA8B,CAAkB,CAAC,CAAC,CAAC,OAAQ,GAAO,EAAG,eAAiB,IAAI,CACnH,CASA,SAAgB,EAAa,CAAE,OAAM,eAAc,UAAS,mBAA2C,EACrG,EAAA,EAAA,UAAA,KAAgB,CACd,GAAI,CAAC,EAAM,OACX,IAAM,EAAoB,SAAS,cAInC,OAHe,GAAiB,SAAW,EAAa,QAAA,EAChD,MAAM,CAAE,cAAe,EAAK,CAAC,MAExB,CACX,GAAmB,QAAQ,CAAE,cAAe,EAAK,CAAC,CACpD,CAEF,EAAG,CAAC,CAAI,CAAC,GAET,EAAA,EAAA,UAAA,KAAgB,CACd,GAAI,CAAC,EAAM,OACX,SAAS,EAAU,EAAkB,CACnC,GAAI,EAAE,MAAQ,SAAU,CACtB,EAAE,gBAAgB,EAClB,EAAQ,EACR,MACF,CACA,GAAI,EAAE,MAAQ,MAAO,OACrB,IAAM,EAAO,EAAa,QAC1B,GAAI,CAAC,EAAM,OACX,IAAM,EAAY,EAAgB,CAAI,EACtC,GAAI,EAAU,SAAW,EAAG,OAC5B,IAAM,EAAQ,EAAU,GAClB,EAAO,EAAU,EAAU,OAAS,GACtC,EAAE,UAAY,SAAS,gBAAkB,GAC3C,EAAE,eAAe,EACjB,EAAK,MAAM,GACF,CAAC,EAAE,UAAY,SAAS,gBAAkB,IACnD,EAAE,eAAe,EACjB,EAAM,MAAM,EAEhB,CAEA,OADA,SAAS,iBAAiB,UAAW,EAAW,EAAI,MACvC,SAAS,oBAAoB,UAAW,EAAW,EAAI,CAEtE,EAAG,CAAC,EAAM,CAAO,CAAC,CACpB"}
@@ -1,2 +1,2 @@
1
- import{n as e,r as t}from"./format-DKrNjdwD.js";import{D as n}from"./VerseConsoleApp-DXDv1aOi.js";import{_ as r,f as i}from"./verse-model-JTuvRBVK.js";function a(e,t){if(!e)return null;for(let n of e)if(n.seatId===t)return n;return null}function o(e,t=Date.now()){if(typeof e!=`string`||e.length===0)return null;let n=Date.parse(e);if(!Number.isFinite(n))return null;let r=new Date(n),i=r.toLocaleTimeString([],{hour:`numeric`,minute:`2-digit`});return new Date(t).toDateString()===r.toDateString()?`today ${i}`:n>t&&n-t<5184e5?`${r.toLocaleDateString([],{weekday:`short`})} ${i}`:`${r.toLocaleDateString([],{month:`short`,day:`numeric`})}, ${i}`}function s(e){return typeof e==`string`&&Number.isFinite(Date.parse(e))}function c(e){return e.limitReached||e.usedPercent!==null&&e.usedPercent>=100}function l(e){if(!e)return{resetAt:null,resetDescription:null};let t=e.windows.filter(c);if(t.length===0){let t=e.binding;return{resetAt:t!==null&&s(t.resetsAt)?t.resetsAt:null,resetDescription:t?.resetDescription??null}}let n=t.filter(e=>!s(e.resetsAt));if(n.length>0)return{resetAt:null,resetDescription:n.find(e=>e.resetDescription!==null)?.resetDescription??null};let r=t.reduce((e,t)=>Date.parse(t.resetsAt)>Date.parse(e.resetsAt)?t:e);return{resetAt:r.resetsAt,resetDescription:r.resetDescription}}function u(e,t,n=Date.now()){let r=null,i=null;if(t!==null)(t.connection===`signed-out`||t.connection===`exhausted`)&&(r=t.connection,i=t.resetAt);else if(e.engine!==`local`){let t=e.capacity?.usability;(t===`signed-out`||t===`exhausted`)&&(r=t,i=t===`exhausted`?l(e.capacity).resetAt:null)}if(r===null)return null;if(r===`signed-out`)return{connection:r,resetAt:null,reason:`${e.label} is signed out — reconnect it to use this seat.`};let a=o(i,n),s=a===null?l(e.capacity).resetDescription:null,c=a===null?s===null?``:` — ${s}`:` — resets ${a}`;return{connection:r,resetAt:i,reason:`${e.label} is out of usage${c}.`}}function d(e){return e.models.some(e=>!e.unavailableReason)}function f(e){return e.engine===`local`?2:e.capacity?.usability===`ready`?0:1}function p(e){let t=e.capacity?.binding?.usedPercent;return typeof t==`number`&&Number.isFinite(t)?t:101}function m(e,t,n){return!(!d(e)||e.health.state===`unavailable`||u(e,t,n)!==null||e.engine===`local`&&t!==null&&t.connection===`unknown`&&t.reasons.length>0)}function h(e,t,n,r=Date.now()){let i=t.find(t=>t.id===e)??null,o=t.map((e,t)=>({seat:e,index:t})).filter(({seat:t})=>t.id!==e&&m(t,a(n,t.id),r));return o.sort((e,t)=>{let n=f(e.seat)-f(t.seat);if(n!==0)return n;if(i!==null){let n=e.seat.engine===i.engine?0:1,r=t.seat.engine===i.engine?0:1;if(n!==r)return n-r}let r=p(e.seat)-p(t.seat);return r===0?e.index-t.index:r}),o.slice(0,5).map(({seat:e})=>e.id)}function g(e){return typeof e!=`number`||!Number.isFinite(e)?`—`:e>0&&e<.01?`<1%`:t(e)}function _(e){return n(e)}var v=/\b\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d{1,9})?)?(?:Z|[+-]\d{2}:?\d{2})?(?![\w:])/g,y=String.raw`[\p{L}\p{N}%…)\]"'’”\x60]`,b=RegExp(`(${y})\\.{1,2}\\s*;`,`gu`),x=RegExp(`(${y})\\.\\.(?=\\s|$)`,`gu`);function S(e,t=Date.now()){return e.replace(v,e=>o(e,t)??e).replace(b,`$1;`).replace(x,`$1.`)}function C(e){let t=e.trim();return t.endsWith(`...`)||t.endsWith(`…`)?t:t.replace(/\.+$/,``)}function w(e){let t=e.trim().replace(/[\s;,:]+$/,``);return t?/[.!?…]$/.test(t)?t:`${t}.`:``}function T(e){return i(e)?r(e):e}function E(e){return typeof e!=`number`||!Number.isFinite(e)?`—`:`$${e.toFixed(2)}`}function D(e){return typeof e!=`number`||!Number.isFinite(e)?`—`:String(e)}function O(e){let t=Math.max(0,Math.round(e/1e3));if(t<60)return`${t}s`;let n=Math.floor(t/60),r=t%60;return n<60?`${n}m ${String(r).padStart(2,`0`)}s`:`${Math.floor(n/60)}h ${String(n%60).padStart(2,`0`)}m`}function k(e){if(!e)return`—`;let t=Date.parse(e);if(Number.isNaN(t))return`—`;let n=Math.max(0,Math.round((Date.now()-t)/1e3));if(n<60)return`${n}s`;let r=Math.round(n/60);if(r<60)return`${r}m`;let i=Math.round(r/60);return i<24?`${i}h`:`${Math.round(i/24)}d`}function A(e){let t=k(e);return t===`—`?`—`:`${t} ago`}function j(e=new Date){return`${e.getFullYear()}-${String(e.getMonth()+1).padStart(2,`0`)}-${String(e.getDate()).padStart(2,`0`)}`}function M(e,t=new Date){return e===j(t)||e===t.toISOString().slice(0,10)}function N(e,t=new Date){if(!e)return`—`;let n=Date.parse(e);if(Number.isNaN(n))return`—`;let r=new Date(n),i=r.toLocaleTimeString(void 0,{hour:`2-digit`,minute:`2-digit`,second:`2-digit`});return j(r)===j(t)?i:`${r.toLocaleDateString(void 0,{month:`short`,day:`numeric`})} ${i}`}function P(e){return typeof e!=`number`||!Number.isFinite(e)||e<=0?`—`:`every ${O(e).replace(/ 00[ms]$/,``)}`}var F=70;function I(t,n,r,i=new Date){let a=typeof t==`number`&&Number.isFinite(t)?Math.max(0,t):null,o=typeof n==`number`&&Number.isFinite(n)?n:null,s=typeof r==`string`&&!M(r,i);if(o===0)return{state:`stopped`,percent:100,label:`${E(a)} spent today · budget $0.00`,note:`Daily budget is $0 — the loop is stopped.`};if(s)return{state:`unknown`,percent:null,label:`— spent today · budget ${E(o)}`,note:`Nothing has been recorded today — the last ledger day is ${e(r)}.`};if(o===null||a===null)return{state:`unknown`,percent:null,label:o===null?`${E(a)} spent today · budget —`:`— spent today · budget ${E(o)}`,note:o===null?`Daily budget is not configured.`:`Today's spend is not observable right now.`};let c=a/o*100,l=Math.min(100,Math.round(c)),u=c>=100?`over`:c>=F?`warn`:`ok`;return{state:u,percent:l,label:`${E(a)} of ${E(o)} today · ${_(a/o*100)}`,note:u===`over`?`Daily budget reached — the loop idles until the date rolls over.`:u===`warn`?`${E(o-a)} left before the loop idles today.`:`${E(o-a)} left today.`}}function L(e,t){if(!e)return null;let n=Date.parse(e);return Number.isNaN(n)||typeof t!=`number`||!Number.isFinite(t)||t<=0?null:n+t}function R(e,t){if(e===null)return`—`;let n=e-t;return n>1e3?`in ${O(n)}`:n>-1e3?`due now`:`overdue by ${O(-n)}`}function z(e){if(!e)return{label:`no tick recorded`,tone:`unknown`};switch(e){case`ok`:return{label:`ok`,tone:`success`};case`kill-switch`:return{label:`refused — emergency stop engaged`,tone:`danger`};case`budget-exhausted`:return{label:`stopped — daily budget spent`,tone:`warning`};case`no-enrolled-repos`:return{label:`nothing to do — no repos enrolled`,tone:`warning`};case`no-backlog`:return{label:`nothing to do — backlog empty`,tone:`neutral`};case`dry-run`:return{label:`dry run`,tone:`neutral`};default:return{label:e,tone:`neutral`}}}export{u as S,_,z as a,o as b,O as c,N as d,E as f,L as g,j as h,R as i,P as l,M as m,w as n,k as o,g as p,I as r,D as s,C as t,A as u,T as v,h as x,S as y};
2
- //# sourceMappingURL=format-BmH2yWL6.js.map
1
+ import{i as e,n as t}from"./format-D7nBg2bn.js";import{D as n}from"./VerseConsoleApp-CWU6R3By.js";import{p as r,v as i}from"./verse-model-DAVevkNe.js";function a(e,t){if(!e)return null;for(let n of e)if(n.seatId===t)return n;return null}function o(e,t=Date.now()){if(typeof e!=`string`||e.length===0)return null;let n=Date.parse(e);if(!Number.isFinite(n))return null;let r=new Date(n),i=r.toLocaleTimeString([],{hour:`numeric`,minute:`2-digit`});return new Date(t).toDateString()===r.toDateString()?`today ${i}`:n>t&&n-t<5184e5?`${r.toLocaleDateString([],{weekday:`short`})} ${i}`:`${r.toLocaleDateString([],{month:`short`,day:`numeric`})}, ${i}`}function s(e){return typeof e==`string`&&Number.isFinite(Date.parse(e))}function c(e){return e.limitReached||e.usedPercent!==null&&e.usedPercent>=100}function l(e){if(!e)return{resetAt:null,resetDescription:null};let t=e.windows.filter(c);if(t.length===0){let t=e.binding;return{resetAt:t!==null&&s(t.resetsAt)?t.resetsAt:null,resetDescription:t?.resetDescription??null}}let n=t.filter(e=>!s(e.resetsAt));if(n.length>0)return{resetAt:null,resetDescription:n.find(e=>e.resetDescription!==null)?.resetDescription??null};let r=t.reduce((e,t)=>Date.parse(t.resetsAt)>Date.parse(e.resetsAt)?t:e);return{resetAt:r.resetsAt,resetDescription:r.resetDescription}}function u(e,t,n=Date.now()){let r=null,i=null;if(t!==null)(t.connection===`signed-out`||t.connection===`exhausted`)&&(r=t.connection,i=t.resetAt);else if(e.engine!==`local`){let t=e.capacity?.usability;(t===`signed-out`||t===`exhausted`)&&(r=t,i=t===`exhausted`?l(e.capacity).resetAt:null)}if(r===null)return null;if(r===`signed-out`)return{connection:r,resetAt:null,reason:`${e.label} is signed out — reconnect it to use this seat.`};let a=o(i,n),s=a===null?l(e.capacity).resetDescription:null,c=a===null?s===null?``:` — ${s}`:` — resets ${a}`;return{connection:r,resetAt:i,reason:`${e.label} is out of usage${c}.`}}function d(e){return e.models.some(e=>!e.unavailableReason)}function f(e){return e.engine===`local`?2:e.capacity?.usability===`ready`?0:1}function p(e){let t=e.capacity?.binding?.usedPercent;return typeof t==`number`&&Number.isFinite(t)?t:101}function m(e,t,n){return!(!d(e)||e.health.state===`unavailable`||u(e,t,n)!==null||e.engine===`local`&&t!==null&&t.connection===`unknown`&&t.reasons.length>0)}function h(e,t,n,r=Date.now()){let i=t.find(t=>t.id===e)??null,o=t.map((e,t)=>({seat:e,index:t})).filter(({seat:t})=>t.id!==e&&m(t,a(n,t.id),r));return o.sort((e,t)=>{let n=f(e.seat)-f(t.seat);if(n!==0)return n;if(i!==null){let n=e.seat.engine===i.engine?0:1,r=t.seat.engine===i.engine?0:1;if(n!==r)return n-r}let r=p(e.seat)-p(t.seat);return r===0?e.index-t.index:r}),o.slice(0,5).map(({seat:e})=>e.id)}function g(t){return typeof t!=`number`||!Number.isFinite(t)?`—`:t>0&&t<.01?`<1%`:e(t)}function _(e){return n(e)}var v=/\b\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d{1,9})?)?(?:Z|[+-]\d{2}:?\d{2})?(?![\w:])/g,y=String.raw`[\p{L}\p{N}%…)\]"'’”\x60]`,b=RegExp(`(${y})\\.{1,2}\\s*;`,`gu`),x=RegExp(`(${y})\\.\\.(?=\\s|$)`,`gu`);function S(e,t=Date.now()){return e.replace(v,e=>o(e,t)??e).replace(b,`$1;`).replace(x,`$1.`)}function C(e){let t=e.trim();return t.endsWith(`...`)||t.endsWith(`…`)?t:t.replace(/\.+$/,``)}function w(e){let t=e.trim().replace(/[\s;,:]+$/,``);return t?/[.!?…]$/.test(t)?t:`${t}.`:``}function T(e){return r(e)?i(e):e}function E(e){return typeof e!=`number`||!Number.isFinite(e)?`—`:`$${e.toFixed(2)}`}function D(e){return typeof e!=`number`||!Number.isFinite(e)?`—`:String(e)}function O(e){let t=Math.max(0,Math.round(e/1e3));if(t<60)return`${t}s`;let n=Math.floor(t/60),r=t%60;return n<60?`${n}m ${String(r).padStart(2,`0`)}s`:`${Math.floor(n/60)}h ${String(n%60).padStart(2,`0`)}m`}function k(e){if(!e)return`—`;let t=Date.parse(e);if(Number.isNaN(t))return`—`;let n=Math.max(0,Math.round((Date.now()-t)/1e3));if(n<60)return`${n}s`;let r=Math.round(n/60);if(r<60)return`${r}m`;let i=Math.round(r/60);return i<24?`${i}h`:`${Math.round(i/24)}d`}function A(e){let t=k(e);return t===`—`?`—`:`${t} ago`}function j(e=new Date){return`${e.getFullYear()}-${String(e.getMonth()+1).padStart(2,`0`)}-${String(e.getDate()).padStart(2,`0`)}`}function M(e,t=new Date){return e===j(t)||e===t.toISOString().slice(0,10)}function N(e,t=new Date){if(!e)return`—`;let n=Date.parse(e);if(Number.isNaN(n))return`—`;let r=new Date(n),i=r.toLocaleTimeString(void 0,{hour:`2-digit`,minute:`2-digit`,second:`2-digit`});return j(r)===j(t)?i:`${r.toLocaleDateString(void 0,{month:`short`,day:`numeric`})} ${i}`}function P(e){return typeof e!=`number`||!Number.isFinite(e)||e<=0?`—`:`every ${O(e).replace(/ 00[ms]$/,``)}`}var F=70;function I(e,n,r,i=new Date){let a=typeof e==`number`&&Number.isFinite(e)?Math.max(0,e):null,o=typeof n==`number`&&Number.isFinite(n)?n:null,s=typeof r==`string`&&!M(r,i);if(o===0)return{state:`stopped`,percent:100,label:`${E(a)} spent today · budget $0.00`,note:`Daily budget is $0 — the loop is stopped.`};if(s)return{state:`unknown`,percent:null,label:`— spent today · budget ${E(o)}`,note:`Nothing has been recorded today — the last ledger day is ${t(r)}.`};if(o===null||a===null)return{state:`unknown`,percent:null,label:o===null?`${E(a)} spent today · budget —`:`— spent today · budget ${E(o)}`,note:o===null?`Daily budget is not configured.`:`Today's spend is not observable right now.`};let c=a/o*100,l=Math.min(100,Math.round(c)),u=c>=100?`over`:c>=F?`warn`:`ok`;return{state:u,percent:l,label:`${E(a)} of ${E(o)} today · ${_(a/o*100)}`,note:u===`over`?`Daily budget reached — the loop idles until the date rolls over.`:u===`warn`?`${E(o-a)} left before the loop idles today.`:`${E(o-a)} left today.`}}function L(e,t){if(!e)return null;let n=Date.parse(e);return Number.isNaN(n)||typeof t!=`number`||!Number.isFinite(t)||t<=0?null:n+t}function R(e,t){if(e===null)return`—`;let n=e-t;return n>1e3?`in ${O(n)}`:n>-1e3?`due now`:`overdue by ${O(-n)}`}function z(e){if(!e)return{label:`no tick recorded`,tone:`unknown`};switch(e){case`ok`:return{label:`ok`,tone:`success`};case`kill-switch`:return{label:`refused — emergency stop engaged`,tone:`danger`};case`budget-exhausted`:return{label:`stopped — daily budget spent`,tone:`warning`};case`no-enrolled-repos`:return{label:`nothing to do — no repos enrolled`,tone:`warning`};case`no-backlog`:return{label:`nothing to do — backlog empty`,tone:`neutral`};case`dry-run`:return{label:`dry run`,tone:`neutral`};default:return{label:e,tone:`neutral`}}}export{u as S,_,z as a,o as b,O as c,N as d,E as f,L as g,j as h,R as i,P as l,M as m,w as n,k as o,g as p,I as r,D as s,C as t,A as u,T as v,h as x,S as y};
2
+ //# sourceMappingURL=format-CH4g_sl1.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"format-BmH2yWL6.js","names":[],"sources":["../../../../../../src/core/verse/seat-readiness.ts","../../../../../../src/web-ui/routes/verse/autonomy/format.ts"],"sourcesContent":["/**\n * core/verse/seat-readiness.ts — \"can this seat run a turn right now, and if\n * not, where else can the operator go?\" (V3.10, unit A2).\n *\n * PURE and BROWSER-SAFE on purpose: the engine's admission gate\n * (`getSeatReadiness` in core/verse/seats.ts) and the Composer's block\n * (web-ui/routes/verse/health/ComposerSeatBlock.tsx) must give the SAME\n * answer, with the SAME ranked alternatives, or the UI would offer a seat the\n * server then refuses. One implementation, imported by both. Type-only\n * imports and `Intl` only — no node: modules.\n *\n * ── WHAT BLOCKS A SEAT ─────────────────────────────────────────────────────\n * Only POSITIVE evidence that a turn would fail at the provider:\n * - `signed-out` — the CLI said so (status command or the live collector).\n * - `exhausted` — every window that carried a reading is spent and no\n * Codex credit balance remains (`seatUsability` in seats.ts;\n * a spent per-model window beside a healthy account-wide\n * one is `tight`, not exhausted).\n * `unknown`, `expiring` and `binary-skew` are WARNINGS, never refusals: an\n * admission gate that refused on \"we could not read it\" would lock Mason out\n * of his own seats every time a probe hiccuped (docs/VERSE-TELEMETRY-V2.md —\n * no signal is not a verdict).\n *\n * ── HOW ALTERNATIVES ARE RANKED ────────────────────────────────────────────\n * (research r2/accounts.md, recommendation 4): other subscription seats with\n * measured headroom first, then subscription seats whose headroom is tight or\n * unread, then local models (free, always last because they are the weakest).\n * Inside a tier the SAME ENGINE leads (a Codex user blocked on one account most\n * likely wants the other Codex account), then the lower binding-window\n * percentage, then discovery order. A seat with no runnable model, a local\n * seat whose runtime is not answering, or a seat that is itself blocked is\n * never offered.\n */\nimport type { SeatConnection, SeatHealthReport, SeatReadiness } from './health-types.js';\nimport type { VerseSeat } from './types.js';\n\n/** The only connections that refuse a turn. Everything else is a warning. */\nexport const SEAT_BLOCKING_CONNECTIONS: ReadonlySet<SeatConnection> = new Set<SeatConnection>(['signed-out', 'exhausted']);\n\n/** At most this many alternatives are offered — a list, not a directory. */\nexport const SEAT_MAX_ALTERNATIVES = 5;\n\nexport interface SeatBlock {\n connection: 'signed-out' | 'exhausted';\n /** One plain sentence naming the seat and the fact (\"Personal Codex is out of usage — resets Fri 2:25 PM.\"). */\n reason: string;\n /** Machine-readable reset instant, when the provider gave one. */\n resetAt: string | null;\n}\n\nfunction reportFor(reports: readonly SeatHealthReport[] | null | undefined, seatId: string): SeatHealthReport | null {\n if (!reports) return null;\n for (const report of reports) if (report.seatId === seatId) return report;\n return null;\n}\n\n/**\n * A reset instant as a short local phrase (\"Fri 2:25 PM\", \"Sep 30, 2:25 PM\"),\n * or null when `iso` is not a valid instant. Only a MACHINE-READABLE instant\n * is ever formatted — Claude's prose reset never reaches this function.\n */\nexport function describeResetAt(iso: string | null | undefined, now: number = Date.now()): string | null {\n if (typeof iso !== 'string' || iso.length === 0) return null;\n const at = Date.parse(iso);\n if (!Number.isFinite(at)) return null;\n const date = new Date(at);\n const time = date.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' });\n if (new Date(now).toDateString() === date.toDateString()) return `today ${time}`;\n // Within the coming week the weekday is the clearest name for a day.\n if (at > now && at - now < 6 * 24 * 60 * 60 * 1000) {\n return `${date.toLocaleDateString([], { weekday: 'short' })} ${time}`;\n }\n return `${date.toLocaleDateString([], { month: 'short', day: 'numeric' })}, ${time}`;\n}\n\n/** When a spent seat reopens, as a machine-readable instant and/or the provider's own words. */\nexport interface SeatReopening {\n /** ISO instant the LAST spent window resets; null when any spent window gave only prose (or nothing). */\n resetAt: string | null;\n /** Provider reset wording to show when `resetAt` is null (Claude publishes prose only). */\n resetDescription: string | null;\n}\n\nfunction parseableInstant(iso: string | null | undefined): iso is string {\n return typeof iso === 'string' && Number.isFinite(Date.parse(iso));\n}\n\ntype CapacityWindow = NonNullable<VerseSeat['capacity']>['windows'][number];\n\nfunction isSpentWindow(w: CapacityWindow): boolean {\n return w.limitReached || (w.usedPercent !== null && w.usedPercent >= 100);\n}\n\n/**\n * When a spent seat is usable again: the LATEST reset among its spent\n * windows. A seat reopens only once EVERY spent window has reset — a Codex\n * seat whose 5-hour window resets in 2h but whose weekly window is spent\n * until Wednesday is still spent in 2h. `capacity.binding` cannot answer\n * this: `bindingWindow` (accounts.ts) keeps the FIRST window among equal\n * percentages, so two windows at 100% named whichever the provider listed\n * first — usually the earlier reset. This is the server twin of the web's\n * `seatReopensAt` (routes/verse/seat-subscription.ts), Fleet's\n * `eligibleAgain` and the router's headroom `lastReset`, so every surface\n * names the same instant.\n *\n * - a spent window with only provider prose → `resetAt` null (prose is\n * never parsed, and \"when all of them have reset\" is then unknown); that\n * window's prose is returned instead;\n * - nothing spent → the binding window's own reset, as before.\n */\nexport function seatReopening(capacity: VerseSeat['capacity'] | null | undefined): SeatReopening {\n if (!capacity) return { resetAt: null, resetDescription: null };\n const spent = capacity.windows.filter(isSpentWindow);\n if (spent.length === 0) {\n const binding = capacity.binding;\n return {\n resetAt: binding !== null && parseableInstant(binding.resetsAt) ? binding.resetsAt : null,\n resetDescription: binding?.resetDescription ?? null,\n };\n }\n const unknown = spent.filter((w) => !parseableInstant(w.resetsAt));\n if (unknown.length > 0) {\n return { resetAt: null, resetDescription: unknown.find((w) => w.resetDescription !== null)?.resetDescription ?? null };\n }\n const latest = spent.reduce((a, b) => (Date.parse(b.resetsAt!) > Date.parse(a.resetsAt!) ? b : a));\n return { resetAt: latest.resetsAt, resetDescription: latest.resetDescription };\n}\n\n/**\n * Why `seat` cannot take a turn, or null when nothing positively blocks it.\n *\n * The health report (sweep + live telemetry) wins when present; without one\n * the seat's own `capacity.usability` — which is on every native seat the\n * server serves — is the fallback, so the seat picker can mark a blocked seat\n * before the health endpoint has ever been read.\n */\nexport function seatBlock(\n seat: VerseSeat,\n report: SeatHealthReport | null,\n now: number = Date.now(),\n): SeatBlock | null {\n let connection: SeatBlock['connection'] | null = null;\n let resetAt: string | null = null;\n if (report !== null) {\n if (report.connection === 'signed-out' || report.connection === 'exhausted') {\n connection = report.connection;\n resetAt = report.resetAt;\n }\n } else if (seat.engine !== 'local') {\n const usability = seat.capacity?.usability;\n if (usability === 'signed-out' || usability === 'exhausted') {\n connection = usability;\n // The latest spent-window reset, the same instant the health report names.\n resetAt = usability === 'exhausted' ? seatReopening(seat.capacity).resetAt : null;\n }\n }\n if (connection === null) return null;\n if (connection === 'signed-out') {\n return { connection, resetAt: null, reason: `${seat.label} is signed out — reconnect it to use this seat.` };\n }\n const when = describeResetAt(resetAt, now);\n const prose = when === null ? seatReopening(seat.capacity).resetDescription : null;\n const tail = when !== null ? ` — resets ${when}` : prose !== null ? ` — ${prose}` : '';\n return { connection, resetAt, reason: `${seat.label} is out of usage${tail}.` };\n}\n\nfunction hasRunnableModel(seat: VerseSeat): boolean {\n return seat.models.some((model) => !model.unavailableReason);\n}\n\n/** 0 = subscription seat with measured headroom, 1 = tight/unread subscription seat, 2 = local. */\nfunction tier(seat: VerseSeat): number {\n if (seat.engine === 'local') return 2;\n return seat.capacity?.usability === 'ready' ? 0 : 1;\n}\n\nfunction bindingPercent(seat: VerseSeat): number {\n const used = seat.capacity?.binding?.usedPercent;\n // Unread sorts after every reading: no signal is not \"0% used\".\n return typeof used === 'number' && Number.isFinite(used) ? used : 101;\n}\n\n/** Can `seat` be offered as somewhere else to go? */\nfunction offerable(seat: VerseSeat, report: SeatHealthReport | null, now: number): boolean {\n if (!hasRunnableModel(seat)) return false;\n if (seat.health.state === 'unavailable') return false;\n if (seatBlock(seat, report, now) !== null) return false;\n // A local seat is only worth offering while its runtime answers. `unknown`\n // with reasons is the sweep saying Ollama did not respond.\n if (seat.engine === 'local' && report !== null && report.connection === 'unknown' && report.reasons.length > 0) {\n return false;\n }\n return true;\n}\n\n/** Ranked ids of seats that could take the turn instead of `seatId` (see the file header). */\nexport function rankSeatAlternatives(\n seatId: string,\n seats: readonly VerseSeat[],\n reports: readonly SeatHealthReport[] | null | undefined,\n now: number = Date.now(),\n): string[] {\n const blocked = seats.find((seat) => seat.id === seatId) ?? null;\n const candidates = seats\n .map((seat, index) => ({ seat, index }))\n .filter(({ seat }) => seat.id !== seatId && offerable(seat, reportFor(reports, seat.id), now));\n candidates.sort((a, b) => {\n const byTier = tier(a.seat) - tier(b.seat);\n if (byTier !== 0) return byTier;\n if (blocked !== null) {\n const sameA = a.seat.engine === blocked.engine ? 0 : 1;\n const sameB = b.seat.engine === blocked.engine ? 0 : 1;\n if (sameA !== sameB) return sameA - sameB;\n }\n const byUse = bindingPercent(a.seat) - bindingPercent(b.seat);\n if (byUse !== 0) return byUse;\n return a.index - b.index;\n });\n return candidates.slice(0, SEAT_MAX_ALTERNATIVES).map(({ seat }) => seat.id);\n}\n\n/**\n * The admission answer for one seat. A seat that is not in `seats` at all is\n * reported READY with no reason: the readiness layer has no evidence about\n * it, and \"seat not found\" is the engine's own error to raise.\n */\nexport function seatReadiness(\n seatId: string,\n seats: readonly VerseSeat[],\n reports: readonly SeatHealthReport[] | null | undefined,\n now: number = Date.now(),\n): SeatReadiness {\n const seat = seats.find((candidate) => candidate.id === seatId) ?? null;\n if (seat === null) return { seatId, ready: true, reason: null, alternatives: [] };\n const block = seatBlock(seat, reportFor(reports, seatId), now);\n if (block === null) return { seatId, ready: true, reason: null, alternatives: [] };\n return { seatId, ready: false, reason: block.reason, alternatives: rankSeatAlternatives(seatId, seats, reports, now) };\n}\n","/**\n * routes/verse/autonomy/format.ts — the small pure functions the cockpit's\n * numerals go through. Kept framework-free so they unit-test without a DOM,\n * and kept in ONE place so \"unknown\" never renders as 0 in one panel and as\n * an em-dash in the next.\n *\n * The rule every function here obeys: absent is not zero. A null spend is\n * \"—\", a null budget is \"not configured\", and a budget of exactly 0 is\n * \"stopped\" — which is the single most misread number in the whole product.\n */\nimport { describeResetAt } from '../../../../core/verse/seat-readiness.js';\nimport { formatDayLabel, formatPercent } from '../../../components/charts/format.js';\nimport { isAbsolutePath, projectName } from '../verse-model.js';\nimport { usedPercentText } from '../percent-text.js';\n\n/** What we render when a value genuinely is not known. */\nexport const UNKNOWN = '—';\n\n/**\n * A share as a whole percentage — \"18%\" — except a non-zero share under one\n * percent, which reads \"<1%\". One precision per panel: \"0%\" for a sliver of\n * real spend or progress would state that nothing happened.\n */\nexport function formatWholePercent(fraction: number | null | undefined): string {\n if (typeof fraction !== 'number' || !Number.isFinite(fraction)) return UNKNOWN;\n if (fraction > 0 && fraction < 0.01) return '<1%';\n return formatPercent(fraction);\n}\n\n/**\n * THE ONE PERCENT RULE — a measured share of a window (or of anything capped\n * at 100), printed the same way on every panel: a whole percent, except that a\n * real reading under 1% is \"<1%\" (never \"0%\", which would read as \"untouched\")\n * and one just short of 100 is \"99%\" (never a rounded \"100%\", which would read\n * as \"spent\"). `used` is 0–100 and is clamped; not a number is \"—\".\n *\n * Every place that prints a used percent goes through this — a bar reading\n * \"99%\" beside a summary reading \"100% of 5-hour window used\" is one seat\n * described two ways. Bar WIDTHS stay numeric; only the words use this.\n */\nexport function percentText(used: number): string {\n return usedPercentText(used);\n}\n\n/** An ISO-8601 instant (a date WITH a time) anywhere inside a sentence. */\nconst ISO_INSTANT_IN_TEXT = /\\b\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}(?::\\d{2}(?:\\.\\d{1,9})?)?(?:Z|[+-]\\d{2}:?\\d{2})?(?![\\w:])/g;\n\n/**\n * What may end a sentence right before its full stop: a letter, a digit, a\n * percent sign, an ellipsis, or a closing quote / bracket / backtick. NOT a\n * dot, a slash or a space — so a path segment (`../x`, `cd ..;`) never looks\n * like doubled punctuation.\n */\nconst SENTENCE_END = String.raw`[\\p{L}\\p{N}%…)\\]\"'’”\\x60]`;\n/** \"failed.; next\" / \"failed..; next\" → \"failed; next\". */\nconst STOP_BEFORE_SEMICOLON = new RegExp(`(${SENTENCE_END})\\\\.{1,2}\\\\s*;`, 'gu');\n/**\n * \"failed.. Next\" / \"exhausted..\" → one stop. The pair must be FOLLOWED by\n * whitespace or the end, so a git range (`a1b2c3d..e4f5a6b`, `HEAD~1..HEAD`)\n * and a traversal (`x/../y`) pass through untouched; a third dot (`...`) is\n * an ellipsis, not a doubled stop.\n */\nconst DOUBLED_STOP = new RegExp(`(${SENTENCE_END})\\\\.\\\\.(?=\\\\s|$)`, 'gu');\n\n/**\n * Server prose made fit to print: every ISO instant becomes the viewer's\n * local time (\"today 11:46 PM\", \"Fri 11:46 PM\", \"Sep 26, 11:46 PM\" — the\n * wording `describeResetAt` gives resets everywhere else), and the \".;\" / \"..\"\n * that a sentence joined onto another sentence leaves behind is collapsed.\n *\n * For text the server wrote for a person (`ApiError.detail`, action notes,\n * audit summaries, fleet notes): it is shown verbatim otherwise. That matters\n * on the trust surfaces this runs on (approval summaries, the audit trail):\n * a `../../.ssh/authorized_keys` rewritten to `././.ssh/…` would hide the very\n * traversal the operator is deciding about, so only sentence punctuation is\n * ever touched.\n */\nexport function tidyProse(text: string, now: number = Date.now()): string {\n return text\n .replace(ISO_INSTANT_IN_TEXT, (iso) => describeResetAt(iso, now) ?? iso)\n .replace(STOP_BEFORE_SEMICOLON, '$1;')\n .replace(DOUBLED_STOP, '$1.');\n}\n\n/**\n * A sentence ready to be embedded in another one: trimmed, with its own\n * closing period dropped so \"… unavailable: quota exhausted.\" does not become\n * \"quota exhausted..\". An ellipsis is kept — it is not a full stop.\n */\nexport function asClause(text: string): string {\n const trimmed = text.trim();\n if (trimmed.endsWith('...') || trimmed.endsWith('…')) return trimmed;\n return trimmed.replace(/\\.+$/, '');\n}\n\n/**\n * One sentence: trimmed and closed by exactly one full stop — added only when\n * the text does not already end in `.`, `!`, `?` or `…` (a trailing \";\", \",\"\n * or \":\" is dropped first). The fix for \"local Qwen offline..\" when a server\n * reason already carries its own stop. Never re-cased — a sentence may open on\n * a seat id.\n */\nexport function asSentence(text: string): string {\n const t = text.trim().replace(/[\\s;,:]+$/, '');\n if (!t) return '';\n return /[.!?…]$/.test(t) ? t : `${t}.`;\n}\n\n/**\n * What a repo cell shows: the folder's own name for an absolute checkout path\n * (the full path belongs in the cell's `title`), and a `owner/name` slug or a\n * bare name as it came — cutting a slug to its last segment would drop the\n * owner, which is half of what identifies it.\n */\nexport function repoDisplayName(repo: string): string {\n return isAbsolutePath(repo) ? projectName(repo) : repo;\n}\n\nexport function formatUsd(value: number | null | undefined): string {\n if (typeof value !== 'number' || !Number.isFinite(value)) return UNKNOWN;\n return `$${value.toFixed(2)}`;\n}\n\nexport function formatCount(value: number | null | undefined): string {\n if (typeof value !== 'number' || !Number.isFinite(value)) return UNKNOWN;\n return String(value);\n}\n\n/** \"4m 12s\" / \"1h 06m\" / \"12s\" — for durations and countdowns alike. */\nexport function formatDuration(ms: number): string {\n const total = Math.max(0, Math.round(ms / 1000));\n if (total < 60) return `${total}s`;\n const minutes = Math.floor(total / 60);\n const seconds = total % 60;\n if (minutes < 60) return `${minutes}m ${String(seconds).padStart(2, '0')}s`;\n const hours = Math.floor(minutes / 60);\n return `${hours}h ${String(minutes % 60).padStart(2, '0')}m`;\n}\n\n/** Compact age for dense table cells: \"42s\", \"7m\", \"3h\", \"5d\". */\nexport function formatAge(iso: string | null | undefined): string {\n if (!iso) return UNKNOWN;\n const ms = Date.parse(iso);\n if (Number.isNaN(ms)) return UNKNOWN;\n const seconds = Math.max(0, Math.round((Date.now() - ms) / 1000));\n if (seconds < 60) return `${seconds}s`;\n const minutes = Math.round(seconds / 60);\n if (minutes < 60) return `${minutes}m`;\n const hours = Math.round(minutes / 60);\n if (hours < 24) return `${hours}h`;\n return `${Math.round(hours / 24)}d`;\n}\n\nexport function formatRelative(iso: string | null | undefined): string {\n const age = formatAge(iso);\n return age === UNKNOWN ? UNKNOWN : `${age} ago`;\n}\n\n/** Local wall-clock time, for the audit table's second column. */\nexport function formatClock(iso: string | null | undefined): string {\n if (!iso) return UNKNOWN;\n const ms = Date.parse(iso);\n if (Number.isNaN(ms)) return UNKNOWN;\n return new Date(ms).toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', second: '2-digit' });\n}\n\n/** The viewer's local calendar day as `YYYY-MM-DD` — NOT the UTC slice. */\nexport function localDateKey(at: Date = new Date()): string {\n const y = at.getFullYear();\n const m = String(at.getMonth() + 1).padStart(2, '0');\n const d = String(at.getDate()).padStart(2, '0');\n return `${y}-${m}-${d}`;\n}\n\n/**\n * Is `todayDate` still the current ledger day?\n *\n * The daemon stamps it in UTC (`resetDayIfNeeded` in core/daemon/state.ts is\n * `new Date().toISOString().slice(0, 10)`) but the operator reads the screen\n * in local time, and the two disagree for part of every day at any offset but\n * zero. Comparing against the local day alone would announce \"nothing has been\n * recorded today\" every evening west of UTC — a false alarm on the meter whose\n * whole purpose is to be trustworthy.\n *\n * So either spelling counts as current. That is deliberately conservative: it\n * can call a day current for a few hours after the UTC rollover, and it will\n * never call a genuinely current day stale. The case this exists for is a\n * ledger day weeks old, which neither spelling matches.\n */\nexport function isCurrentLedgerDay(todayDate: string, now: Date = new Date()): boolean {\n return todayDate === localDateKey(now) || todayDate === now.toISOString().slice(0, 10);\n}\n\n/**\n * Time-of-day for a row from today; date + time for anything older.\n *\n * `formatClock` alone is only honest inside a single-day window, and the\n * tables that use it are not one: the audit trail defaults to 200 rows and\n * offers 500, which on a real machine spans days. Two rows reading \"03:12:44\"\n * could be last night and last week, and the only date was in a hover title\n * that a touch viewport never fires.\n */\nexport function formatStamp(iso: string | null | undefined, now: Date = new Date()): string {\n if (!iso) return UNKNOWN;\n const ms = Date.parse(iso);\n if (Number.isNaN(ms)) return UNKNOWN;\n const at = new Date(ms);\n const clock = at.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', second: '2-digit' });\n if (localDateKey(at) === localDateKey(now)) return clock;\n const day = at.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });\n return `${day} ${clock}`;\n}\n\n/** Human tick interval, e.g. \"every 15m\". */\nexport function formatInterval(intervalMs: number | null | undefined): string {\n if (typeof intervalMs !== 'number' || !Number.isFinite(intervalMs) || intervalMs <= 0) return UNKNOWN;\n // A configured interval is a round number far more often than not; \"every\n // 15m\" reads better than \"every 15m 00s\" and loses nothing when it isn't.\n return `every ${formatDuration(intervalMs).replace(/ 00[ms]$/, '')}`;\n}\n\nexport type BudgetState = 'stopped' | 'unknown' | 'ok' | 'warn' | 'over';\n\nexport interface BudgetMeter {\n state: BudgetState;\n /** 0-100, clamped. Null when there is nothing honest to draw. */\n percent: number | null;\n /** One line: \"$4.50 of $25.00 today · 18%\". */\n label: string;\n /** The plain-language verdict shown next to the meter. */\n note: string;\n}\n\nconst BUDGET_WARN_PERCENT = 70;\n\n/**\n * Today's spend against the configured daily cap, as ONE line.\n *\n * A cap of exactly 0 is the contract's guard rail: it means the loop is\n * stopped, and it must never render as an empty (i.e. \"plenty left\") meter.\n *\n * `todayDate` is the ledger day `spend.todayUsd` was counted for. It is NOT\n * optional decoration: the daemon writes the figure once per day and leaves it\n * there, so a machine that last ticked three weeks ago answers\n * `{todayUsd: 0, todayDate: \"2026-09-01\"}`. Rendering that as \"$0.00 of $50.00\n * today\" is a fabricated statement about today on the one number an operator\n * checks before walking away from an autonomous loop. Pass `null` only when\n * the source genuinely has no date to offer (the legacy daemon observation).\n */\nexport function budgetMeter(\n spentUsd: number | null | undefined,\n capUsd: number | null | undefined,\n todayDate?: string | null,\n now: Date = new Date(),\n): BudgetMeter {\n const spend = typeof spentUsd === 'number' && Number.isFinite(spentUsd) ? Math.max(0, spentUsd) : null;\n const cap = typeof capUsd === 'number' && Number.isFinite(capUsd) ? capUsd : null;\n // A cap of 0 is a statement about configuration, not about the ledger, so\n // it still reads true on a stale day and is checked first.\n const stale = typeof todayDate === 'string' && !isCurrentLedgerDay(todayDate, now);\n\n if (cap === 0) {\n return {\n state: 'stopped',\n percent: 100,\n label: `${formatUsd(spend)} spent today · budget $0.00`,\n note: 'Daily budget is $0 — the loop is stopped.',\n };\n }\n if (stale) {\n return {\n state: 'unknown',\n percent: null,\n label: `${UNKNOWN} spent today · budget ${formatUsd(cap)}`,\n // \"Sep 1\", not \"2026-09-01\": the ledger day is a calendar date, so it is\n // read as one (formatDayLabel parses it in UTC and cannot shift a day).\n note: `Nothing has been recorded today — the last ledger day is ${formatDayLabel(todayDate)}.`,\n };\n }\n if (cap === null || spend === null) {\n return {\n state: 'unknown',\n percent: null,\n label: cap === null ? `${formatUsd(spend)} spent today · budget ${UNKNOWN}` : `${UNKNOWN} spent today · budget ${formatUsd(cap)}`,\n note: cap === null ? 'Daily budget is not configured.' : \"Today's spend is not observable right now.\",\n };\n }\n\n const raw = (spend / cap) * 100;\n const percent = Math.min(100, Math.round(raw));\n const state: BudgetState = raw >= 100 ? 'over' : raw >= BUDGET_WARN_PERCENT ? 'warn' : 'ok';\n return {\n state,\n percent,\n // The text follows the one percent rule (\"<1%\", \"99%\" short of the cap); `percent` (the bar) stays numeric.\n label: `${formatUsd(spend)} of ${formatUsd(cap)} today · ${percentText((spend / cap) * 100)}`,\n note:\n state === 'over'\n ? 'Daily budget reached — the loop idles until the date rolls over.'\n : state === 'warn'\n ? `${formatUsd(cap - spend)} left before the loop idles today.`\n : `${formatUsd(cap - spend)} left today.`,\n };\n}\n\n/** Epoch ms of the next expected tick, or null when it cannot be derived. */\nexport function nextTickAt(lastTickAt: string | null | undefined, intervalMs: number | null | undefined): number | null {\n if (!lastTickAt) return null;\n const last = Date.parse(lastTickAt);\n if (Number.isNaN(last)) return null;\n if (typeof intervalMs !== 'number' || !Number.isFinite(intervalMs) || intervalMs <= 0) return null;\n return last + intervalMs;\n}\n\n/** \"in 4m 12s\" / \"due now\" / \"overdue by 2m 03s\". */\nexport function countdownLabel(nextAt: number | null, now: number): string {\n if (nextAt === null) return UNKNOWN;\n const delta = nextAt - now;\n if (delta > 1000) return `in ${formatDuration(delta)}`;\n if (delta > -1000) return 'due now';\n return `overdue by ${formatDuration(-delta)}`;\n}\n\n/** Last tick outcome, phrased the way a person would say it out loud. */\nexport function describeTickOutcome(reason: string | null | undefined): { label: string; tone: 'success' | 'warning' | 'danger' | 'neutral' | 'unknown' } {\n if (!reason) return { label: 'no tick recorded', tone: 'unknown' };\n switch (reason) {\n case 'ok':\n return { label: 'ok', tone: 'success' };\n case 'kill-switch':\n return { label: 'refused — emergency stop engaged', tone: 'danger' };\n case 'budget-exhausted':\n return { label: 'stopped — daily budget spent', tone: 'warning' };\n case 'no-enrolled-repos':\n return { label: 'nothing to do — no repos enrolled', tone: 'warning' };\n case 'no-backlog':\n return { label: 'nothing to do — backlog empty', tone: 'neutral' };\n case 'dry-run':\n return { label: 'dry run', tone: 'neutral' };\n default:\n return { label: reason, tone: 'neutral' };\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"format-CH4g_sl1.js","names":[],"sources":["../../../../../../src/core/verse/seat-readiness.ts","../../../../../../src/web-ui/routes/verse/autonomy/format.ts"],"sourcesContent":["/**\n * core/verse/seat-readiness.ts — \"can this seat run a turn right now, and if\n * not, where else can the operator go?\" (V3.10, unit A2).\n *\n * PURE and BROWSER-SAFE on purpose: the engine's admission gate\n * (`getSeatReadiness` in core/verse/seats.ts) and the Composer's block\n * (web-ui/routes/verse/health/ComposerSeatBlock.tsx) must give the SAME\n * answer, with the SAME ranked alternatives, or the UI would offer a seat the\n * server then refuses. One implementation, imported by both. Type-only\n * imports and `Intl` only — no node: modules.\n *\n * ── WHAT BLOCKS A SEAT ─────────────────────────────────────────────────────\n * Only POSITIVE evidence that a turn would fail at the provider:\n * - `signed-out` — the CLI said so (status command or the live collector).\n * - `exhausted` — every window that carried a reading is spent and no\n * Codex credit balance remains (`seatUsability` in seats.ts;\n * a spent per-model window beside a healthy account-wide\n * one is `tight`, not exhausted).\n * `unknown`, `expiring` and `binary-skew` are WARNINGS, never refusals: an\n * admission gate that refused on \"we could not read it\" would lock Mason out\n * of his own seats every time a probe hiccuped (docs/VERSE-TELEMETRY-V2.md —\n * no signal is not a verdict).\n *\n * ── HOW ALTERNATIVES ARE RANKED ────────────────────────────────────────────\n * (research r2/accounts.md, recommendation 4): other subscription seats with\n * measured headroom first, then subscription seats whose headroom is tight or\n * unread, then local models (free, always last because they are the weakest).\n * Inside a tier the SAME ENGINE leads (a Codex user blocked on one account most\n * likely wants the other Codex account), then the lower binding-window\n * percentage, then discovery order. A seat with no runnable model, a local\n * seat whose runtime is not answering, or a seat that is itself blocked is\n * never offered.\n */\nimport type { SeatConnection, SeatHealthReport, SeatReadiness } from './health-types.js';\nimport type { VerseSeat } from './types.js';\n\n/** The only connections that refuse a turn. Everything else is a warning. */\nexport const SEAT_BLOCKING_CONNECTIONS: ReadonlySet<SeatConnection> = new Set<SeatConnection>(['signed-out', 'exhausted']);\n\n/** At most this many alternatives are offered — a list, not a directory. */\nexport const SEAT_MAX_ALTERNATIVES = 5;\n\nexport interface SeatBlock {\n connection: 'signed-out' | 'exhausted';\n /** One plain sentence naming the seat and the fact (\"Personal Codex is out of usage — resets Fri 2:25 PM.\"). */\n reason: string;\n /** Machine-readable reset instant, when the provider gave one. */\n resetAt: string | null;\n}\n\nfunction reportFor(reports: readonly SeatHealthReport[] | null | undefined, seatId: string): SeatHealthReport | null {\n if (!reports) return null;\n for (const report of reports) if (report.seatId === seatId) return report;\n return null;\n}\n\n/**\n * A reset instant as a short local phrase (\"Fri 2:25 PM\", \"Sep 30, 2:25 PM\"),\n * or null when `iso` is not a valid instant. Only a MACHINE-READABLE instant\n * is ever formatted — Claude's prose reset never reaches this function.\n */\nexport function describeResetAt(iso: string | null | undefined, now: number = Date.now()): string | null {\n if (typeof iso !== 'string' || iso.length === 0) return null;\n const at = Date.parse(iso);\n if (!Number.isFinite(at)) return null;\n const date = new Date(at);\n const time = date.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' });\n if (new Date(now).toDateString() === date.toDateString()) return `today ${time}`;\n // Within the coming week the weekday is the clearest name for a day.\n if (at > now && at - now < 6 * 24 * 60 * 60 * 1000) {\n return `${date.toLocaleDateString([], { weekday: 'short' })} ${time}`;\n }\n return `${date.toLocaleDateString([], { month: 'short', day: 'numeric' })}, ${time}`;\n}\n\n/** When a spent seat reopens, as a machine-readable instant and/or the provider's own words. */\nexport interface SeatReopening {\n /** ISO instant the LAST spent window resets; null when any spent window gave only prose (or nothing). */\n resetAt: string | null;\n /** Provider reset wording to show when `resetAt` is null (Claude publishes prose only). */\n resetDescription: string | null;\n}\n\nfunction parseableInstant(iso: string | null | undefined): iso is string {\n return typeof iso === 'string' && Number.isFinite(Date.parse(iso));\n}\n\ntype CapacityWindow = NonNullable<VerseSeat['capacity']>['windows'][number];\n\nfunction isSpentWindow(w: CapacityWindow): boolean {\n return w.limitReached || (w.usedPercent !== null && w.usedPercent >= 100);\n}\n\n/**\n * When a spent seat is usable again: the LATEST reset among its spent\n * windows. A seat reopens only once EVERY spent window has reset — a Codex\n * seat whose 5-hour window resets in 2h but whose weekly window is spent\n * until Wednesday is still spent in 2h. `capacity.binding` cannot answer\n * this: `bindingWindow` (accounts.ts) keeps the FIRST window among equal\n * percentages, so two windows at 100% named whichever the provider listed\n * first — usually the earlier reset. This is the server twin of the web's\n * `seatReopensAt` (routes/verse/seat-subscription.ts), Fleet's\n * `eligibleAgain` and the router's headroom `lastReset`, so every surface\n * names the same instant.\n *\n * - a spent window with only provider prose → `resetAt` null (prose is\n * never parsed, and \"when all of them have reset\" is then unknown); that\n * window's prose is returned instead;\n * - nothing spent → the binding window's own reset, as before.\n */\nexport function seatReopening(capacity: VerseSeat['capacity'] | null | undefined): SeatReopening {\n if (!capacity) return { resetAt: null, resetDescription: null };\n const spent = capacity.windows.filter(isSpentWindow);\n if (spent.length === 0) {\n const binding = capacity.binding;\n return {\n resetAt: binding !== null && parseableInstant(binding.resetsAt) ? binding.resetsAt : null,\n resetDescription: binding?.resetDescription ?? null,\n };\n }\n const unknown = spent.filter((w) => !parseableInstant(w.resetsAt));\n if (unknown.length > 0) {\n return { resetAt: null, resetDescription: unknown.find((w) => w.resetDescription !== null)?.resetDescription ?? null };\n }\n const latest = spent.reduce((a, b) => (Date.parse(b.resetsAt!) > Date.parse(a.resetsAt!) ? b : a));\n return { resetAt: latest.resetsAt, resetDescription: latest.resetDescription };\n}\n\n/**\n * Why `seat` cannot take a turn, or null when nothing positively blocks it.\n *\n * The health report (sweep + live telemetry) wins when present; without one\n * the seat's own `capacity.usability` — which is on every native seat the\n * server serves — is the fallback, so the seat picker can mark a blocked seat\n * before the health endpoint has ever been read.\n */\nexport function seatBlock(\n seat: VerseSeat,\n report: SeatHealthReport | null,\n now: number = Date.now(),\n): SeatBlock | null {\n let connection: SeatBlock['connection'] | null = null;\n let resetAt: string | null = null;\n if (report !== null) {\n if (report.connection === 'signed-out' || report.connection === 'exhausted') {\n connection = report.connection;\n resetAt = report.resetAt;\n }\n } else if (seat.engine !== 'local') {\n const usability = seat.capacity?.usability;\n if (usability === 'signed-out' || usability === 'exhausted') {\n connection = usability;\n // The latest spent-window reset, the same instant the health report names.\n resetAt = usability === 'exhausted' ? seatReopening(seat.capacity).resetAt : null;\n }\n }\n if (connection === null) return null;\n if (connection === 'signed-out') {\n return { connection, resetAt: null, reason: `${seat.label} is signed out — reconnect it to use this seat.` };\n }\n const when = describeResetAt(resetAt, now);\n const prose = when === null ? seatReopening(seat.capacity).resetDescription : null;\n const tail = when !== null ? ` — resets ${when}` : prose !== null ? ` — ${prose}` : '';\n return { connection, resetAt, reason: `${seat.label} is out of usage${tail}.` };\n}\n\nfunction hasRunnableModel(seat: VerseSeat): boolean {\n return seat.models.some((model) => !model.unavailableReason);\n}\n\n/** 0 = subscription seat with measured headroom, 1 = tight/unread subscription seat, 2 = local. */\nfunction tier(seat: VerseSeat): number {\n if (seat.engine === 'local') return 2;\n return seat.capacity?.usability === 'ready' ? 0 : 1;\n}\n\nfunction bindingPercent(seat: VerseSeat): number {\n const used = seat.capacity?.binding?.usedPercent;\n // Unread sorts after every reading: no signal is not \"0% used\".\n return typeof used === 'number' && Number.isFinite(used) ? used : 101;\n}\n\n/** Can `seat` be offered as somewhere else to go? */\nfunction offerable(seat: VerseSeat, report: SeatHealthReport | null, now: number): boolean {\n if (!hasRunnableModel(seat)) return false;\n if (seat.health.state === 'unavailable') return false;\n if (seatBlock(seat, report, now) !== null) return false;\n // A local seat is only worth offering while its runtime answers. `unknown`\n // with reasons is the sweep saying Ollama did not respond.\n if (seat.engine === 'local' && report !== null && report.connection === 'unknown' && report.reasons.length > 0) {\n return false;\n }\n return true;\n}\n\n/** Ranked ids of seats that could take the turn instead of `seatId` (see the file header). */\nexport function rankSeatAlternatives(\n seatId: string,\n seats: readonly VerseSeat[],\n reports: readonly SeatHealthReport[] | null | undefined,\n now: number = Date.now(),\n): string[] {\n const blocked = seats.find((seat) => seat.id === seatId) ?? null;\n const candidates = seats\n .map((seat, index) => ({ seat, index }))\n .filter(({ seat }) => seat.id !== seatId && offerable(seat, reportFor(reports, seat.id), now));\n candidates.sort((a, b) => {\n const byTier = tier(a.seat) - tier(b.seat);\n if (byTier !== 0) return byTier;\n if (blocked !== null) {\n const sameA = a.seat.engine === blocked.engine ? 0 : 1;\n const sameB = b.seat.engine === blocked.engine ? 0 : 1;\n if (sameA !== sameB) return sameA - sameB;\n }\n const byUse = bindingPercent(a.seat) - bindingPercent(b.seat);\n if (byUse !== 0) return byUse;\n return a.index - b.index;\n });\n return candidates.slice(0, SEAT_MAX_ALTERNATIVES).map(({ seat }) => seat.id);\n}\n\n/**\n * The admission answer for one seat. A seat that is not in `seats` at all is\n * reported READY with no reason: the readiness layer has no evidence about\n * it, and \"seat not found\" is the engine's own error to raise.\n */\nexport function seatReadiness(\n seatId: string,\n seats: readonly VerseSeat[],\n reports: readonly SeatHealthReport[] | null | undefined,\n now: number = Date.now(),\n): SeatReadiness {\n const seat = seats.find((candidate) => candidate.id === seatId) ?? null;\n if (seat === null) return { seatId, ready: true, reason: null, alternatives: [] };\n const block = seatBlock(seat, reportFor(reports, seatId), now);\n if (block === null) return { seatId, ready: true, reason: null, alternatives: [] };\n return { seatId, ready: false, reason: block.reason, alternatives: rankSeatAlternatives(seatId, seats, reports, now) };\n}\n","/**\n * routes/verse/autonomy/format.ts — the small pure functions the cockpit's\n * numerals go through. Kept framework-free so they unit-test without a DOM,\n * and kept in ONE place so \"unknown\" never renders as 0 in one panel and as\n * an em-dash in the next.\n *\n * The rule every function here obeys: absent is not zero. A null spend is\n * \"—\", a null budget is \"not configured\", and a budget of exactly 0 is\n * \"stopped\" — which is the single most misread number in the whole product.\n */\nimport { describeResetAt } from '../../../../core/verse/seat-readiness.js';\nimport { formatDayLabel, formatPercent } from '../../../components/charts/format.js';\nimport { isAbsolutePath, projectName } from '../verse-model.js';\nimport { usedPercentText } from '../percent-text.js';\n\n/** What we render when a value genuinely is not known. */\nexport const UNKNOWN = '—';\n\n/**\n * A share as a whole percentage — \"18%\" — except a non-zero share under one\n * percent, which reads \"<1%\". One precision per panel: \"0%\" for a sliver of\n * real spend or progress would state that nothing happened.\n */\nexport function formatWholePercent(fraction: number | null | undefined): string {\n if (typeof fraction !== 'number' || !Number.isFinite(fraction)) return UNKNOWN;\n if (fraction > 0 && fraction < 0.01) return '<1%';\n return formatPercent(fraction);\n}\n\n/**\n * THE ONE PERCENT RULE — a measured share of a window (or of anything capped\n * at 100), printed the same way on every panel: a whole percent, except that a\n * real reading under 1% is \"<1%\" (never \"0%\", which would read as \"untouched\")\n * and one just short of 100 is \"99%\" (never a rounded \"100%\", which would read\n * as \"spent\"). `used` is 0–100 and is clamped; not a number is \"—\".\n *\n * Every place that prints a used percent goes through this — a bar reading\n * \"99%\" beside a summary reading \"100% of 5-hour window used\" is one seat\n * described two ways. Bar WIDTHS stay numeric; only the words use this.\n */\nexport function percentText(used: number): string {\n return usedPercentText(used);\n}\n\n/** An ISO-8601 instant (a date WITH a time) anywhere inside a sentence. */\nconst ISO_INSTANT_IN_TEXT = /\\b\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}(?::\\d{2}(?:\\.\\d{1,9})?)?(?:Z|[+-]\\d{2}:?\\d{2})?(?![\\w:])/g;\n\n/**\n * What may end a sentence right before its full stop: a letter, a digit, a\n * percent sign, an ellipsis, or a closing quote / bracket / backtick. NOT a\n * dot, a slash or a space — so a path segment (`../x`, `cd ..;`) never looks\n * like doubled punctuation.\n */\nconst SENTENCE_END = String.raw`[\\p{L}\\p{N}%…)\\]\"'’”\\x60]`;\n/** \"failed.; next\" / \"failed..; next\" → \"failed; next\". */\nconst STOP_BEFORE_SEMICOLON = new RegExp(`(${SENTENCE_END})\\\\.{1,2}\\\\s*;`, 'gu');\n/**\n * \"failed.. Next\" / \"exhausted..\" → one stop. The pair must be FOLLOWED by\n * whitespace or the end, so a git range (`a1b2c3d..e4f5a6b`, `HEAD~1..HEAD`)\n * and a traversal (`x/../y`) pass through untouched; a third dot (`...`) is\n * an ellipsis, not a doubled stop.\n */\nconst DOUBLED_STOP = new RegExp(`(${SENTENCE_END})\\\\.\\\\.(?=\\\\s|$)`, 'gu');\n\n/**\n * Server prose made fit to print: every ISO instant becomes the viewer's\n * local time (\"today 11:46 PM\", \"Fri 11:46 PM\", \"Sep 26, 11:46 PM\" — the\n * wording `describeResetAt` gives resets everywhere else), and the \".;\" / \"..\"\n * that a sentence joined onto another sentence leaves behind is collapsed.\n *\n * For text the server wrote for a person (`ApiError.detail`, action notes,\n * audit summaries, fleet notes): it is shown verbatim otherwise. That matters\n * on the trust surfaces this runs on (approval summaries, the audit trail):\n * a `../../.ssh/authorized_keys` rewritten to `././.ssh/…` would hide the very\n * traversal the operator is deciding about, so only sentence punctuation is\n * ever touched.\n */\nexport function tidyProse(text: string, now: number = Date.now()): string {\n return text\n .replace(ISO_INSTANT_IN_TEXT, (iso) => describeResetAt(iso, now) ?? iso)\n .replace(STOP_BEFORE_SEMICOLON, '$1;')\n .replace(DOUBLED_STOP, '$1.');\n}\n\n/**\n * A sentence ready to be embedded in another one: trimmed, with its own\n * closing period dropped so \"… unavailable: quota exhausted.\" does not become\n * \"quota exhausted..\". An ellipsis is kept — it is not a full stop.\n */\nexport function asClause(text: string): string {\n const trimmed = text.trim();\n if (trimmed.endsWith('...') || trimmed.endsWith('…')) return trimmed;\n return trimmed.replace(/\\.+$/, '');\n}\n\n/**\n * One sentence: trimmed and closed by exactly one full stop — added only when\n * the text does not already end in `.`, `!`, `?` or `…` (a trailing \";\", \",\"\n * or \":\" is dropped first). The fix for \"local Qwen offline..\" when a server\n * reason already carries its own stop. Never re-cased — a sentence may open on\n * a seat id.\n */\nexport function asSentence(text: string): string {\n const t = text.trim().replace(/[\\s;,:]+$/, '');\n if (!t) return '';\n return /[.!?…]$/.test(t) ? t : `${t}.`;\n}\n\n/**\n * What a repo cell shows: the folder's own name for an absolute checkout path\n * (the full path belongs in the cell's `title`), and a `owner/name` slug or a\n * bare name as it came — cutting a slug to its last segment would drop the\n * owner, which is half of what identifies it.\n */\nexport function repoDisplayName(repo: string): string {\n return isAbsolutePath(repo) ? projectName(repo) : repo;\n}\n\nexport function formatUsd(value: number | null | undefined): string {\n if (typeof value !== 'number' || !Number.isFinite(value)) return UNKNOWN;\n return `$${value.toFixed(2)}`;\n}\n\nexport function formatCount(value: number | null | undefined): string {\n if (typeof value !== 'number' || !Number.isFinite(value)) return UNKNOWN;\n return String(value);\n}\n\n/** \"4m 12s\" / \"1h 06m\" / \"12s\" — for durations and countdowns alike. */\nexport function formatDuration(ms: number): string {\n const total = Math.max(0, Math.round(ms / 1000));\n if (total < 60) return `${total}s`;\n const minutes = Math.floor(total / 60);\n const seconds = total % 60;\n if (minutes < 60) return `${minutes}m ${String(seconds).padStart(2, '0')}s`;\n const hours = Math.floor(minutes / 60);\n return `${hours}h ${String(minutes % 60).padStart(2, '0')}m`;\n}\n\n/** Compact age for dense table cells: \"42s\", \"7m\", \"3h\", \"5d\". */\nexport function formatAge(iso: string | null | undefined): string {\n if (!iso) return UNKNOWN;\n const ms = Date.parse(iso);\n if (Number.isNaN(ms)) return UNKNOWN;\n const seconds = Math.max(0, Math.round((Date.now() - ms) / 1000));\n if (seconds < 60) return `${seconds}s`;\n const minutes = Math.round(seconds / 60);\n if (minutes < 60) return `${minutes}m`;\n const hours = Math.round(minutes / 60);\n if (hours < 24) return `${hours}h`;\n return `${Math.round(hours / 24)}d`;\n}\n\nexport function formatRelative(iso: string | null | undefined): string {\n const age = formatAge(iso);\n return age === UNKNOWN ? UNKNOWN : `${age} ago`;\n}\n\n/** Local wall-clock time, for the audit table's second column. */\nexport function formatClock(iso: string | null | undefined): string {\n if (!iso) return UNKNOWN;\n const ms = Date.parse(iso);\n if (Number.isNaN(ms)) return UNKNOWN;\n return new Date(ms).toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', second: '2-digit' });\n}\n\n/** The viewer's local calendar day as `YYYY-MM-DD` — NOT the UTC slice. */\nexport function localDateKey(at: Date = new Date()): string {\n const y = at.getFullYear();\n const m = String(at.getMonth() + 1).padStart(2, '0');\n const d = String(at.getDate()).padStart(2, '0');\n return `${y}-${m}-${d}`;\n}\n\n/**\n * Is `todayDate` still the current ledger day?\n *\n * The daemon stamps it in UTC (`resetDayIfNeeded` in core/daemon/state.ts is\n * `new Date().toISOString().slice(0, 10)`) but the operator reads the screen\n * in local time, and the two disagree for part of every day at any offset but\n * zero. Comparing against the local day alone would announce \"nothing has been\n * recorded today\" every evening west of UTC — a false alarm on the meter whose\n * whole purpose is to be trustworthy.\n *\n * So either spelling counts as current. That is deliberately conservative: it\n * can call a day current for a few hours after the UTC rollover, and it will\n * never call a genuinely current day stale. The case this exists for is a\n * ledger day weeks old, which neither spelling matches.\n */\nexport function isCurrentLedgerDay(todayDate: string, now: Date = new Date()): boolean {\n return todayDate === localDateKey(now) || todayDate === now.toISOString().slice(0, 10);\n}\n\n/**\n * Time-of-day for a row from today; date + time for anything older.\n *\n * `formatClock` alone is only honest inside a single-day window, and the\n * tables that use it are not one: the audit trail defaults to 200 rows and\n * offers 500, which on a real machine spans days. Two rows reading \"03:12:44\"\n * could be last night and last week, and the only date was in a hover title\n * that a touch viewport never fires.\n */\nexport function formatStamp(iso: string | null | undefined, now: Date = new Date()): string {\n if (!iso) return UNKNOWN;\n const ms = Date.parse(iso);\n if (Number.isNaN(ms)) return UNKNOWN;\n const at = new Date(ms);\n const clock = at.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', second: '2-digit' });\n if (localDateKey(at) === localDateKey(now)) return clock;\n const day = at.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });\n return `${day} ${clock}`;\n}\n\n/** Human tick interval, e.g. \"every 15m\". */\nexport function formatInterval(intervalMs: number | null | undefined): string {\n if (typeof intervalMs !== 'number' || !Number.isFinite(intervalMs) || intervalMs <= 0) return UNKNOWN;\n // A configured interval is a round number far more often than not; \"every\n // 15m\" reads better than \"every 15m 00s\" and loses nothing when it isn't.\n return `every ${formatDuration(intervalMs).replace(/ 00[ms]$/, '')}`;\n}\n\nexport type BudgetState = 'stopped' | 'unknown' | 'ok' | 'warn' | 'over';\n\nexport interface BudgetMeter {\n state: BudgetState;\n /** 0-100, clamped. Null when there is nothing honest to draw. */\n percent: number | null;\n /** One line: \"$4.50 of $25.00 today · 18%\". */\n label: string;\n /** The plain-language verdict shown next to the meter. */\n note: string;\n}\n\nconst BUDGET_WARN_PERCENT = 70;\n\n/**\n * Today's spend against the configured daily cap, as ONE line.\n *\n * A cap of exactly 0 is the contract's guard rail: it means the loop is\n * stopped, and it must never render as an empty (i.e. \"plenty left\") meter.\n *\n * `todayDate` is the ledger day `spend.todayUsd` was counted for. It is NOT\n * optional decoration: the daemon writes the figure once per day and leaves it\n * there, so a machine that last ticked three weeks ago answers\n * `{todayUsd: 0, todayDate: \"2026-09-01\"}`. Rendering that as \"$0.00 of $50.00\n * today\" is a fabricated statement about today on the one number an operator\n * checks before walking away from an autonomous loop. Pass `null` only when\n * the source genuinely has no date to offer (the legacy daemon observation).\n */\nexport function budgetMeter(\n spentUsd: number | null | undefined,\n capUsd: number | null | undefined,\n todayDate?: string | null,\n now: Date = new Date(),\n): BudgetMeter {\n const spend = typeof spentUsd === 'number' && Number.isFinite(spentUsd) ? Math.max(0, spentUsd) : null;\n const cap = typeof capUsd === 'number' && Number.isFinite(capUsd) ? capUsd : null;\n // A cap of 0 is a statement about configuration, not about the ledger, so\n // it still reads true on a stale day and is checked first.\n const stale = typeof todayDate === 'string' && !isCurrentLedgerDay(todayDate, now);\n\n if (cap === 0) {\n return {\n state: 'stopped',\n percent: 100,\n label: `${formatUsd(spend)} spent today · budget $0.00`,\n note: 'Daily budget is $0 — the loop is stopped.',\n };\n }\n if (stale) {\n return {\n state: 'unknown',\n percent: null,\n label: `${UNKNOWN} spent today · budget ${formatUsd(cap)}`,\n // \"Sep 1\", not \"2026-09-01\": the ledger day is a calendar date, so it is\n // read as one (formatDayLabel parses it in UTC and cannot shift a day).\n note: `Nothing has been recorded today — the last ledger day is ${formatDayLabel(todayDate)}.`,\n };\n }\n if (cap === null || spend === null) {\n return {\n state: 'unknown',\n percent: null,\n label: cap === null ? `${formatUsd(spend)} spent today · budget ${UNKNOWN}` : `${UNKNOWN} spent today · budget ${formatUsd(cap)}`,\n note: cap === null ? 'Daily budget is not configured.' : \"Today's spend is not observable right now.\",\n };\n }\n\n const raw = (spend / cap) * 100;\n const percent = Math.min(100, Math.round(raw));\n const state: BudgetState = raw >= 100 ? 'over' : raw >= BUDGET_WARN_PERCENT ? 'warn' : 'ok';\n return {\n state,\n percent,\n // The text follows the one percent rule (\"<1%\", \"99%\" short of the cap); `percent` (the bar) stays numeric.\n label: `${formatUsd(spend)} of ${formatUsd(cap)} today · ${percentText((spend / cap) * 100)}`,\n note:\n state === 'over'\n ? 'Daily budget reached — the loop idles until the date rolls over.'\n : state === 'warn'\n ? `${formatUsd(cap - spend)} left before the loop idles today.`\n : `${formatUsd(cap - spend)} left today.`,\n };\n}\n\n/** Epoch ms of the next expected tick, or null when it cannot be derived. */\nexport function nextTickAt(lastTickAt: string | null | undefined, intervalMs: number | null | undefined): number | null {\n if (!lastTickAt) return null;\n const last = Date.parse(lastTickAt);\n if (Number.isNaN(last)) return null;\n if (typeof intervalMs !== 'number' || !Number.isFinite(intervalMs) || intervalMs <= 0) return null;\n return last + intervalMs;\n}\n\n/** \"in 4m 12s\" / \"due now\" / \"overdue by 2m 03s\". */\nexport function countdownLabel(nextAt: number | null, now: number): string {\n if (nextAt === null) return UNKNOWN;\n const delta = nextAt - now;\n if (delta > 1000) return `in ${formatDuration(delta)}`;\n if (delta > -1000) return 'due now';\n return `overdue by ${formatDuration(-delta)}`;\n}\n\n/** Last tick outcome, phrased the way a person would say it out loud. */\nexport function describeTickOutcome(reason: string | null | undefined): { label: string; tone: 'success' | 'warning' | 'danger' | 'neutral' | 'unknown' } {\n if (!reason) return { label: 'no tick recorded', tone: 'unknown' };\n switch (reason) {\n case 'ok':\n return { label: 'ok', tone: 'success' };\n case 'kill-switch':\n return { label: 'refused — emergency stop engaged', tone: 'danger' };\n case 'budget-exhausted':\n return { label: 'stopped — daily budget spent', tone: 'warning' };\n case 'no-enrolled-repos':\n return { label: 'nothing to do — no repos enrolled', tone: 'warning' };\n case 'no-backlog':\n return { label: 'nothing to do — backlog empty', tone: 'neutral' };\n case 'dry-run':\n return { label: 'dry run', tone: 'neutral' };\n default:\n return { label: reason, tone: 'neutral' };\n }\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- function e(e){if(!Number.isFinite(e))return`—`;let t=Math.abs(e);return t>=1e6?`${(e/1e6).toFixed(t>=1e7?0:1)}M`:t>=1e4?`${Math.round(e/1e3)}K`:t>=1e3?e.toLocaleString(`en-US`):Number.isInteger(e)?String(e):e.toFixed(1)}function t(e){if(!Number.isFinite(e))return`—`;let t=Math.abs(e),n=e<0?`-`:``;return t>=1e6?`${n}$${(t/1e6).toFixed(1)}M`:t>=1e3?`${n}$${(t/1e3).toFixed(1)}K`:`${n}$${t.toFixed(2)}`}function n(e,t=0){return Number.isFinite(e)?`${(e*100).toFixed(t)}%`:`—`}function r(t){if(!Number.isFinite(t))return`—`;let n=e(Math.abs(t));return t>0?`+${n}`:t<0?`-${n}`:n}function i(e){if(!/^\d{4}-\d{2}-\d{2}$/.test(e))return e;let t=new Date(`${e}T00:00:00Z`);return Number.isNaN(t.getTime())||t.toISOString().slice(0,10)!==e?e:t.toLocaleDateString(`en-US`,{month:`short`,day:`numeric`,timeZone:`UTC`})}function a(e){let t=new Date(e);return Number.isNaN(t.getTime())?``:t.toLocaleDateString(`en-US`,{month:`short`,day:`numeric`})}function o(e){let t=Math.abs(e);if(!Number.isFinite(t)||t===0)return 0;for(let e=0;e<=10;e++){let n=t*10**e;if(Math.abs(n-Math.round(n))<1e-9*Math.max(1,n))return e}return 10}function s(e,t){let n=Math.max(0,...e.filter(Number.isFinite).map(e=>Math.abs(e))),r=n>=1e6?[1e6,1e3]:n>=1e4?[1e3]:[];for(let e of r)if(o(t/e)<=3)return e;return 1}function c(e,t,n=s([e],t)){if(!Number.isFinite(e))return`—`;if(Math.abs(e)<Math.abs(t)*1e-9||e===0)return`0`;if(n!==1){let r=n===1e6?`M`:`K`;return`${(e/n).toFixed(Math.min(3,o(t/n)))}${r}`}let r=o(t);return r===0?Math.round(e).toLocaleString(`en-US`):e.toFixed(r)}function l(e){return t=>{let n=new Date(t);return Number.isNaN(n.getTime())?``:n.toLocaleString(`en-US`,e)}}var u=l({hour:`numeric`,minute:`2-digit`}),d=l({weekday:`short`,hour:`numeric`,minute:`2-digit`});function f(e,t){return!Number.isFinite(e)||!Number.isFinite(t)||t<=e?!1:t-e<432e5||new Date(e).toDateString()===new Date(t).toDateString()}function p(e,t,n,r=[e,t]){if(f(e,t))return[u];if(!n)return[a];let i=new Set(r.map(u)).size>1;return t-e<5616e5&&i?[n,d,a]:[n,a]}export{c as a,s as c,r as i,p as l,i as n,a as o,n as r,t as s,e as t};
2
- //# sourceMappingURL=format-DKrNjdwD.js.map
1
+ function e(e){if(!Number.isFinite(e))return`—`;let t=Math.abs(e);return t>=1e6?`${(e/1e6).toFixed(t>=1e7?0:1)}M`:t>=1e4?`${Math.round(e/1e3)}K`:t>=1e3?e.toLocaleString(`en-US`):Number.isInteger(e)?String(e):e.toFixed(1)}function t(e){if(!Number.isFinite(e))return`—`;let t=Math.abs(e),n=e<0?`-`:``;return t>=1e6?`${n}$${(t/1e6).toFixed(1)}M`:t>=1e3?`${n}$${(t/1e3).toFixed(1)}K`:`${n}$${t.toFixed(2)}`}function n(e,t=0){return Number.isFinite(e)?`${(e*100).toFixed(t)}%`:`—`}function r(t){if(!Number.isFinite(t))return`—`;let n=e(Math.abs(t));return t>0?`+${n}`:t<0?`-${n}`:n}function i(e){if(!/^\d{4}-\d{2}-\d{2}$/.test(e))return e;let t=new Date(`${e}T00:00:00Z`);return Number.isNaN(t.getTime())||t.toISOString().slice(0,10)!==e?e:t.toLocaleDateString(`en-US`,{month:`short`,day:`numeric`,timeZone:`UTC`})}function a(e){let t=new Date(e);return Number.isNaN(t.getTime())?``:t.toLocaleDateString(`en-US`,{month:`short`,day:`numeric`})}function o(e){let t=Math.abs(e);if(!Number.isFinite(t)||t===0)return 0;for(let e=0;e<=10;e++){let n=t*10**e;if(Math.abs(n-Math.round(n))<1e-9*Math.max(1,n))return e}return 10}function s(e,t){let n=Math.max(0,...e.filter(Number.isFinite).map(e=>Math.abs(e))),r=n>=1e6?[1e6,1e3]:n>=1e4?[1e3]:[];for(let e of r)if(o(t/e)<=3)return e;return 1}function c(e,t,n=s([e],t)){if(!Number.isFinite(e))return`—`;if(Math.abs(e)<Math.abs(t)*1e-9||e===0)return`0`;if(n!==1){let r=n===1e6?`M`:`K`;return`${(e/n).toFixed(Math.min(3,o(t/n)))}${r}`}let r=o(t);return r===0?Math.round(e).toLocaleString(`en-US`):e.toFixed(r)}function l(e){return t=>{let n=new Date(t);return Number.isNaN(n.getTime())?``:n.toLocaleString(`en-US`,e)}}var u=l({hour:`numeric`,minute:`2-digit`}),d=l({weekday:`short`,hour:`numeric`,minute:`2-digit`});function f(e,t){return!Number.isFinite(e)||!Number.isFinite(t)||t<=e?!1:t-e<432e5||new Date(e).toDateString()===new Date(t).toDateString()}function p(e,t,n,r=[e,t]){if(f(e,t))return[u];if(!n)return[a];let i=new Set(r.map(u)).size>1;return t-e<5616e5&&i?[n,d,a]:[n,a]}function m(e){return Number.isFinite(e)?e.toLocaleString(`en-US`,{maximumFractionDigits:Math.abs(e)<1&&e!==0?3:2}):`—`}var h=l({weekday:`short`,month:`short`,day:`numeric`}),g=l({weekday:`short`,month:`short`,day:`numeric`,hour:`numeric`,minute:`2-digit`});function _(e){let t=e.filter(e=>Number.isFinite(e));return new Set(t.map(u)).size<=1&&t.length>1?h:g}export{r as a,g as c,p as d,_ as f,n as i,t as l,i as n,c as o,m as r,a as s,e as t,s as u};
2
+ //# sourceMappingURL=format-D7nBg2bn.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"format-DKrNjdwD.js","names":[],"sources":["../../../../../../src/web-ui/components/charts/format.ts"],"sourcesContent":["/**\n * components/charts/format.ts — number formatting shared by every chart and\n * stat tile. Proportional figures everywhere EXCEPT columns that must align\n * vertically (table cells, axis ticks) — see marks-and-anatomy.md \"Proportional\n * figures for big numbers; tabular only in columns.\" Callers apply\n * `font-variant-numeric: tabular-nums` via CSS where that applies; these\n * functions only produce the string.\n */\n\n/** Auto-compact a count: 1,284 / 12.9K / 4.2M. */\nexport function formatCompact(n: number): string {\n if (!Number.isFinite(n)) return '—';\n const abs = Math.abs(n);\n if (abs >= 1_000_000) return `${(n / 1_000_000).toFixed(abs >= 10_000_000 ? 0 : 1)}M`;\n if (abs >= 10_000) return `${Math.round(n / 1000)}K`;\n if (abs >= 1_000) return n.toLocaleString('en-US');\n return Number.isInteger(n) ? String(n) : n.toFixed(1);\n}\n\n/** Auto-compact USD: $4.20 / $1.2K / $4.2M. */\nexport function formatUsd(n: number): string {\n if (!Number.isFinite(n)) return '—';\n const abs = Math.abs(n);\n const sign = n < 0 ? '-' : '';\n if (abs >= 1_000_000) return `${sign}$${(abs / 1_000_000).toFixed(1)}M`;\n if (abs >= 1_000) return `${sign}$${(abs / 1_000).toFixed(1)}K`;\n return `${sign}$${abs.toFixed(2)}`;\n}\n\n/** Fraction (0..1) as a percentage. */\nexport function formatPercent(fraction: number, digits = 0): string {\n if (!Number.isFinite(fraction)) return '—';\n return `${(fraction * 100).toFixed(digits)}%`;\n}\n\n/** Signed delta for a stat tile: \"+12\" / \"-3\" / \"0\". */\nexport function formatSignedCompact(n: number): string {\n if (!Number.isFinite(n)) return '—';\n const s = formatCompact(Math.abs(n));\n if (n > 0) return `+${s}`;\n if (n < 0) return `-${s}`;\n return s;\n}\n\n/** YYYY-MM-DD -> short label, e.g. \"Aug 14\". Local-independent (UTC parse). */\nexport function formatDayLabel(day: string): string {\n if (!/^\\d{4}-\\d{2}-\\d{2}$/.test(day)) return day;\n const d = new Date(`${day}T00:00:00Z`);\n // Date normalizes impossible dates; compare the padded UTC date to reject rollover.\n if (Number.isNaN(d.getTime()) || d.toISOString().slice(0, 10) !== day) return day;\n return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', timeZone: 'UTC' });\n}\n\n/** Epoch ms -> short label for axis ticks. */\nexport function formatTimeLabel(ms: number): string {\n const d = new Date(ms);\n if (Number.isNaN(d.getTime())) return '';\n return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });\n}\n\n// ---------------------------------------------------------------------------\n// Axis tick precision (V3.10.1)\n// ---------------------------------------------------------------------------\n\n/**\n * Decimals needed to print `step` exactly: 1 → 0, 0.2 → 1, 0.25 → 2, 0.05 → 2.\n * An axis label's precision comes from the STEP between ticks, never from a\n * fixed one-decimal format — a 0.25 step printed at one decimal read\n * \"0 / 0.3 / 0.5 / 0.8 / 1\" on the live Verse cards.\n */\nexport function stepDecimals(step: number): number {\n const s = Math.abs(step);\n if (!Number.isFinite(s) || s === 0) return 0;\n for (let d = 0; d <= 10; d++) {\n const scaled = s * 10 ** d;\n if (Math.abs(scaled - Math.round(scaled)) < 1e-9 * Math.max(1, scaled)) return d;\n }\n return 10;\n}\n\n/** The unit one axis's tick labels are written in: plain, thousands (K) or millions (M). */\nexport type TickUnit = 1 | 1_000 | 1_000_000;\n\n/** Most decimals a compacted (K / M) tick label may carry before the axis is written plain instead. */\nconst MAX_COMPACT_DECIMALS = 3;\n\n/**\n * The ONE unit for a whole axis, chosen from its largest tick — never tick\n * by tick. Per-tick choice printed \"0 / 2,500 / 5,000 / 7,500 / 10.0K\" on a\n * 0–10K axis (V3.10.1 review): two formats and two precisions on one scale.\n * Thresholds match formatCompact (K from 10,000, M from 1,000,000). A unit\n * whose labels would need more than three decimals to show the step (a 0.5\n * step at 10,000 is \"10.0005K\") falls back to the next smaller unit.\n */\nexport function tickUnit(ticks: ReadonlyArray<number>, step: number): TickUnit {\n const maxAbs = Math.max(0, ...ticks.filter(Number.isFinite).map((t) => Math.abs(t)));\n const units: TickUnit[] = maxAbs >= 1_000_000 ? [1_000_000, 1_000] : maxAbs >= 10_000 ? [1_000] : [];\n for (const unit of units) if (stepDecimals(step / unit) <= MAX_COMPACT_DECIMALS) return unit;\n return 1;\n}\n\n/**\n * A tick label at the precision its step needs: every tick on one axis\n * carries the same number of decimals (0.0 / 0.2 / … / 1.0 reads as a scale;\n * 0 / 0.3 / 0.5 misreads as rounded data). Zero is always \"0\". Large values\n * compact like formatCompact (12.5K, 1.25M), again at the step's precision.\n *\n * Pass the axis's `unit` (tickUnit) so every tick shares it; without one the\n * unit is taken from this tick alone, which is only right for a lone value.\n */\nexport function formatTick(value: number, step: number, unit: TickUnit = tickUnit([value], step)): string {\n if (!Number.isFinite(value)) return '—';\n if (Math.abs(value) < Math.abs(step) * 1e-9 || value === 0) return '0';\n if (unit !== 1) {\n const suffix = unit === 1_000_000 ? 'M' : 'K';\n return `${(value / unit).toFixed(Math.min(MAX_COMPACT_DECIMALS, stepDecimals(step / unit)))}${suffix}`;\n }\n const d = stepDecimals(step);\n return d === 0 ? Math.round(value).toLocaleString('en-US') : value.toFixed(d);\n}\n\n// ---------------------------------------------------------------------------\n// Time labels for axes (V3.10.1)\n// ---------------------------------------------------------------------------\n\nconst HOUR_MS = 3_600_000;\nconst DAY_MS = 86_400_000;\n\nfunction localeFormatter(opts: Intl.DateTimeFormatOptions): (ms: number) => string {\n return (ms) => {\n const d = new Date(ms);\n return Number.isNaN(d.getTime()) ? '' : d.toLocaleString('en-US', opts);\n };\n}\n\n/** \"11:46 PM\" — a clock time with no date. */\nexport const formatClockTime = localeFormatter({ hour: 'numeric', minute: '2-digit' });\n\n/** \"Fri 11:46 PM\" — weekday and clock time (unambiguous inside a week). */\nexport const formatWeekdayTime = localeFormatter({ weekday: 'short', hour: 'numeric', minute: '2-digit' });\n\n/**\n * True when a time span sits inside one day — both ends on the same local\n * calendar day, or under 12 h apart (a 5-hour window across midnight) — so a\n * date on every label would only repeat itself. A zero span is NOT \"within a\n * day\": a single daily bucket keeps its date, never becomes \"8:00 PM\".\n */\nexport function withinOneDay(from: number, to: number): boolean {\n if (!Number.isFinite(from) || !Number.isFinite(to) || to <= from) return false;\n return to - from < 12 * HOUR_MS || new Date(from).toDateString() === new Date(to).toDateString();\n}\n\n/**\n * The detail ladder for the labels on one time axis, most detailed first;\n * the label layout (chart-math layoutAxisLabels) walks down it only until\n * the labels fit. Level by level every label on the axis uses the same rung,\n * so an axis never mixes \"Fri, Sep 18 at 11:46 PM\" with \"Sep 25\".\n *\n * within one day → clock time only (\"11:46 PM\"), whatever the caller's format;\n * caller's format → it first, then \"Fri 11:46 PM\" (spans under ~a week\n * whose labels differ in time of day), then \"Sep 18\";\n * no caller format → \"Sep 18\" (the kit's day label).\n *\n * `from`/`to` are the DATA extent (not a widened domain); `stamps` are the\n * labelled instants — when they all share one time of day (daily buckets,\n * a 7-day window) the clock time says nothing and is skipped.\n */\nexport function timeLabelLadder(\n from: number,\n to: number,\n custom?: (ms: number) => string,\n stamps: readonly number[] = [from, to],\n): Array<(ms: number) => string> {\n if (withinOneDay(from, to)) return [formatClockTime];\n if (!custom) return [formatTimeLabel];\n const distinctTimeOfDay = new Set(stamps.map(formatClockTime)).size > 1;\n return to - from < 6.5 * DAY_MS && distinctTimeOfDay ? [custom, formatWeekdayTime, formatTimeLabel] : [custom, formatTimeLabel];\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"format-D7nBg2bn.js","names":[],"sources":["../../../../../../src/web-ui/components/charts/format.ts"],"sourcesContent":["/**\n * components/charts/format.ts — number formatting shared by every chart and\n * stat tile. Proportional figures everywhere EXCEPT columns that must align\n * vertically (table cells, axis ticks) — see marks-and-anatomy.md \"Proportional\n * figures for big numbers; tabular only in columns.\" Callers apply\n * `font-variant-numeric: tabular-nums` via CSS where that applies; these\n * functions only produce the string.\n */\n\n/** Auto-compact a count: 1,284 / 12.9K / 4.2M. */\nexport function formatCompact(n: number): string {\n if (!Number.isFinite(n)) return '—';\n const abs = Math.abs(n);\n if (abs >= 1_000_000) return `${(n / 1_000_000).toFixed(abs >= 10_000_000 ? 0 : 1)}M`;\n if (abs >= 10_000) return `${Math.round(n / 1000)}K`;\n if (abs >= 1_000) return n.toLocaleString('en-US');\n return Number.isInteger(n) ? String(n) : n.toFixed(1);\n}\n\n/** Auto-compact USD: $4.20 / $1.2K / $4.2M. */\nexport function formatUsd(n: number): string {\n if (!Number.isFinite(n)) return '—';\n const abs = Math.abs(n);\n const sign = n < 0 ? '-' : '';\n if (abs >= 1_000_000) return `${sign}$${(abs / 1_000_000).toFixed(1)}M`;\n if (abs >= 1_000) return `${sign}$${(abs / 1_000).toFixed(1)}K`;\n return `${sign}$${abs.toFixed(2)}`;\n}\n\n/** Fraction (0..1) as a percentage. */\nexport function formatPercent(fraction: number, digits = 0): string {\n if (!Number.isFinite(fraction)) return '—';\n return `${(fraction * 100).toFixed(digits)}%`;\n}\n\n/** Signed delta for a stat tile: \"+12\" / \"-3\" / \"0\". */\nexport function formatSignedCompact(n: number): string {\n if (!Number.isFinite(n)) return '—';\n const s = formatCompact(Math.abs(n));\n if (n > 0) return `+${s}`;\n if (n < 0) return `-${s}`;\n return s;\n}\n\n/** YYYY-MM-DD -> short label, e.g. \"Aug 14\". Local-independent (UTC parse). */\nexport function formatDayLabel(day: string): string {\n if (!/^\\d{4}-\\d{2}-\\d{2}$/.test(day)) return day;\n const d = new Date(`${day}T00:00:00Z`);\n // Date normalizes impossible dates; compare the padded UTC date to reject rollover.\n if (Number.isNaN(d.getTime()) || d.toISOString().slice(0, 10) !== day) return day;\n return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', timeZone: 'UTC' });\n}\n\n/** Epoch ms -> short label for axis ticks. */\nexport function formatTimeLabel(ms: number): string {\n const d = new Date(ms);\n if (Number.isNaN(d.getTime())) return '';\n return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });\n}\n\n// ---------------------------------------------------------------------------\n// Axis tick precision (V3.10.1)\n// ---------------------------------------------------------------------------\n\n/**\n * Decimals needed to print `step` exactly: 1 → 0, 0.2 → 1, 0.25 → 2, 0.05 → 2.\n * An axis label's precision comes from the STEP between ticks, never from a\n * fixed one-decimal format — a 0.25 step printed at one decimal read\n * \"0 / 0.3 / 0.5 / 0.8 / 1\" on the live Verse cards.\n */\nexport function stepDecimals(step: number): number {\n const s = Math.abs(step);\n if (!Number.isFinite(s) || s === 0) return 0;\n for (let d = 0; d <= 10; d++) {\n const scaled = s * 10 ** d;\n if (Math.abs(scaled - Math.round(scaled)) < 1e-9 * Math.max(1, scaled)) return d;\n }\n return 10;\n}\n\n/** The unit one axis's tick labels are written in: plain, thousands (K) or millions (M). */\nexport type TickUnit = 1 | 1_000 | 1_000_000;\n\n/** Most decimals a compacted (K / M) tick label may carry before the axis is written plain instead. */\nconst MAX_COMPACT_DECIMALS = 3;\n\n/**\n * The ONE unit for a whole axis, chosen from its largest tick — never tick\n * by tick. Per-tick choice printed \"0 / 2,500 / 5,000 / 7,500 / 10.0K\" on a\n * 0–10K axis (V3.10.1 review): two formats and two precisions on one scale.\n * Thresholds match formatCompact (K from 10,000, M from 1,000,000). A unit\n * whose labels would need more than three decimals to show the step (a 0.5\n * step at 10,000 is \"10.0005K\") falls back to the next smaller unit.\n */\nexport function tickUnit(ticks: ReadonlyArray<number>, step: number): TickUnit {\n const maxAbs = Math.max(0, ...ticks.filter(Number.isFinite).map((t) => Math.abs(t)));\n const units: TickUnit[] = maxAbs >= 1_000_000 ? [1_000_000, 1_000] : maxAbs >= 10_000 ? [1_000] : [];\n for (const unit of units) if (stepDecimals(step / unit) <= MAX_COMPACT_DECIMALS) return unit;\n return 1;\n}\n\n/**\n * A tick label at the precision its step needs: every tick on one axis\n * carries the same number of decimals (0.0 / 0.2 / … / 1.0 reads as a scale;\n * 0 / 0.3 / 0.5 misreads as rounded data). Zero is always \"0\". Large values\n * compact like formatCompact (12.5K, 1.25M), again at the step's precision.\n *\n * Pass the axis's `unit` (tickUnit) so every tick shares it; without one the\n * unit is taken from this tick alone, which is only right for a lone value.\n */\nexport function formatTick(value: number, step: number, unit: TickUnit = tickUnit([value], step)): string {\n if (!Number.isFinite(value)) return '—';\n if (Math.abs(value) < Math.abs(step) * 1e-9 || value === 0) return '0';\n if (unit !== 1) {\n const suffix = unit === 1_000_000 ? 'M' : 'K';\n return `${(value / unit).toFixed(Math.min(MAX_COMPACT_DECIMALS, stepDecimals(step / unit)))}${suffix}`;\n }\n const d = stepDecimals(step);\n return d === 0 ? Math.round(value).toLocaleString('en-US') : value.toFixed(d);\n}\n\n// ---------------------------------------------------------------------------\n// Time labels for axes (V3.10.1)\n// ---------------------------------------------------------------------------\n\nconst HOUR_MS = 3_600_000;\nconst DAY_MS = 86_400_000;\n\nfunction localeFormatter(opts: Intl.DateTimeFormatOptions): (ms: number) => string {\n return (ms) => {\n const d = new Date(ms);\n return Number.isNaN(d.getTime()) ? '' : d.toLocaleString('en-US', opts);\n };\n}\n\n/** \"11:46 PM\" — a clock time with no date. */\nexport const formatClockTime = localeFormatter({ hour: 'numeric', minute: '2-digit' });\n\n/** \"Fri 11:46 PM\" — weekday and clock time (unambiguous inside a week). */\nexport const formatWeekdayTime = localeFormatter({ weekday: 'short', hour: 'numeric', minute: '2-digit' });\n\n/**\n * True when a time span sits inside one day — both ends on the same local\n * calendar day, or under 12 h apart (a 5-hour window across midnight) — so a\n * date on every label would only repeat itself. A zero span is NOT \"within a\n * day\": a single daily bucket keeps its date, never becomes \"8:00 PM\".\n */\nexport function withinOneDay(from: number, to: number): boolean {\n if (!Number.isFinite(from) || !Number.isFinite(to) || to <= from) return false;\n return to - from < 12 * HOUR_MS || new Date(from).toDateString() === new Date(to).toDateString();\n}\n\n/**\n * The detail ladder for the labels on one time axis, most detailed first;\n * the label layout (chart-math layoutAxisLabels) walks down it only until\n * the labels fit. Level by level every label on the axis uses the same rung,\n * so an axis never mixes \"Fri, Sep 18 at 11:46 PM\" with \"Sep 25\".\n *\n * within one day → clock time only (\"11:46 PM\"), whatever the caller's format;\n * caller's format → it first, then \"Fri 11:46 PM\" (spans under ~a week\n * whose labels differ in time of day), then \"Sep 18\";\n * no caller format → \"Sep 18\" (the kit's day label).\n *\n * `from`/`to` are the DATA extent (not a widened domain); `stamps` are the\n * labelled instants — when they all share one time of day (daily buckets,\n * a 7-day window) the clock time says nothing and is skipped.\n */\nexport function timeLabelLadder(\n from: number,\n to: number,\n custom?: (ms: number) => string,\n stamps: readonly number[] = [from, to],\n): Array<(ms: number) => string> {\n if (withinOneDay(from, to)) return [formatClockTime];\n if (!custom) return [formatTimeLabel];\n const distinctTimeOfDay = new Set(stamps.map(formatClockTime)).size > 1;\n return to - from < 6.5 * DAY_MS && distinctTimeOfDay ? [custom, formatWeekdayTime, formatTimeLabel] : [custom, formatTimeLabel];\n}\n\n// ---------------------------------------------------------------------------\n// Tooltip precision (verse-visual-quality)\n// ---------------------------------------------------------------------------\n\n/**\n * The EXACT figure a tooltip prints: grouped digits, up to two decimals\n * (three below 1) — \"12,934\" where the axis says \"13K\". An axis label rounds\n * so ticks stay short; the tooltip is where a reader goes for the real number.\n */\nexport function formatExact(n: number): string {\n if (!Number.isFinite(n)) return '—';\n return n.toLocaleString('en-US', { maximumFractionDigits: Math.abs(n) < 1 && n !== 0 ? 3 : 2 });\n}\n\n/** \"Fri, Sep 26\" — a local calendar day with its weekday. */\nexport const formatTooltipDay = localeFormatter({ weekday: 'short', month: 'short', day: 'numeric' });\n\n/** \"Fri, Sep 26, 2:14 PM\" — a local instant. */\nexport const formatTooltipInstant = localeFormatter({ weekday: 'short', month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' });\n\n/**\n * The tooltip title for a time axis, in the VIEWER's zone: weekday and day\n * for daily buckets (every stamp shares one clock time — local midnight),\n * plus the clock time when readings fall at different times of day. An axis\n * label is budgeted for width (\"Sep 26\"); the tooltip has room to say\n * exactly which moment it describes.\n */\nexport function tooltipTimeFormatter(stamps: readonly number[]): (ms: number) => string {\n const finite = stamps.filter((s) => Number.isFinite(s));\n const sameClock = new Set(finite.map(formatClockTime)).size <= 1;\n return sameClock && finite.length > 1 ? formatTooltipDay : formatTooltipInstant;\n}\n"],"mappings":"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"}