@ashlr/hub 3.4.0 → 3.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +107 -1
- package/README.md +3 -0
- package/dist/build-identity.json +2 -2
- package/dist/cli/index.js +11 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/local-runtime.js +40 -9
- package/dist/cli/local-runtime.js.map +1 -1
- package/dist/core/fleet/external-skill-audit.js +1 -1
- package/dist/core/fleet/external-skill-audit.js.map +1 -1
- package/dist/core/local-eval/classify.d.ts +57 -0
- package/dist/core/local-eval/classify.js +121 -0
- package/dist/core/local-eval/classify.js.map +1 -0
- package/dist/core/local-eval/configuration.d.ts +37 -0
- package/dist/core/local-eval/configuration.js +171 -0
- package/dist/core/local-eval/configuration.js.map +1 -0
- package/dist/core/local-eval/index.d.ts +15 -0
- package/dist/core/local-eval/index.js +16 -0
- package/dist/core/local-eval/index.js.map +1 -0
- package/dist/core/local-eval/main.d.ts +31 -0
- package/dist/core/local-eval/main.js +174 -0
- package/dist/core/local-eval/main.js.map +1 -0
- package/dist/core/local-eval/report.d.ts +25 -0
- package/dist/core/local-eval/report.js +157 -0
- package/dist/core/local-eval/report.js.map +1 -0
- package/dist/core/local-eval/runner.d.ts +55 -0
- package/dist/core/local-eval/runner.js +290 -0
- package/dist/core/local-eval/runner.js.map +1 -0
- package/dist/core/local-eval/tasks.d.ts +22 -0
- package/dist/core/local-eval/tasks.js +303 -0
- package/dist/core/local-eval/tasks.js.map +1 -0
- package/dist/core/local-eval/trace.d.ts +67 -0
- package/dist/core/local-eval/trace.js +309 -0
- package/dist/core/local-eval/trace.js.map +1 -0
- package/dist/core/local-eval/types.d.ts +262 -0
- package/dist/core/local-eval/types.js +23 -0
- package/dist/core/local-eval/types.js.map +1 -0
- package/dist/core/local-runtime/llama/anthropic-proxy-host.d.ts +68 -0
- package/dist/core/local-runtime/llama/anthropic-proxy-host.js +120 -0
- package/dist/core/local-runtime/llama/anthropic-proxy-host.js.map +1 -0
- package/dist/core/local-runtime/llama/anthropic-proxy-process.d.ts +15 -0
- package/dist/core/local-runtime/llama/anthropic-proxy-process.js +21 -0
- package/dist/core/local-runtime/llama/anthropic-proxy-process.js.map +1 -0
- package/dist/core/local-runtime/llama/anthropic-proxy.d.ts +2 -1
- package/dist/core/local-runtime/llama/anthropic-proxy.js +14 -6
- package/dist/core/local-runtime/llama/anthropic-proxy.js.map +1 -1
- package/dist/core/local-runtime/llama/anthropic-shim.d.ts +33 -0
- package/dist/core/local-runtime/llama/anthropic-shim.js +59 -0
- package/dist/core/local-runtime/llama/anthropic-shim.js.map +1 -1
- package/dist/core/local-runtime/llama/config.d.ts +123 -6
- package/dist/core/local-runtime/llama/config.js +196 -6
- package/dist/core/local-runtime/llama/config.js.map +1 -1
- package/dist/core/local-runtime/llama/index.d.ts +15 -0
- package/dist/core/local-runtime/llama/index.js +13 -0
- package/dist/core/local-runtime/llama/index.js.map +1 -1
- package/dist/core/local-runtime/llama/paths.d.ts +17 -0
- package/dist/core/local-runtime/llama/paths.js +25 -0
- package/dist/core/local-runtime/llama/paths.js.map +1 -1
- package/dist/core/local-runtime/llama/proxy-invocation.d.ts +101 -0
- package/dist/core/local-runtime/llama/proxy-invocation.js +134 -0
- package/dist/core/local-runtime/llama/proxy-invocation.js.map +1 -0
- package/dist/core/local-runtime/llama/proxy-launchd.d.ts +83 -0
- package/dist/core/local-runtime/llama/proxy-launchd.js +216 -0
- package/dist/core/local-runtime/llama/proxy-launchd.js.map +1 -0
- package/dist/core/local-runtime/llama/shapes.d.ts +2 -1
- package/dist/core/local-runtime/llama/shapes.js +2 -1
- package/dist/core/local-runtime/llama/shapes.js.map +1 -1
- package/dist/core/verse/adapters/claude.js +16 -0
- package/dist/core/verse/adapters/claude.js.map +1 -1
- package/dist/core/verse/adapters/codex.js +60 -2
- package/dist/core/verse/adapters/codex.js.map +1 -1
- package/dist/core/verse/adapters/grok.js +13 -0
- package/dist/core/verse/adapters/grok.js.map +1 -1
- package/dist/core/verse/control-api.d.ts +8 -9
- package/dist/core/verse/control-api.js +14 -114
- package/dist/core/verse/control-api.js.map +1 -1
- package/dist/core/verse/github-api.d.ts +50 -0
- package/dist/core/verse/github-api.js +211 -0
- package/dist/core/verse/github-api.js.map +1 -0
- package/dist/core/verse/github-proposal.d.ts +64 -0
- package/dist/core/verse/github-proposal.js +186 -0
- package/dist/core/verse/github-proposal.js.map +1 -0
- package/dist/core/verse/github-repo.d.ts +104 -0
- package/dist/core/verse/github-repo.js +476 -0
- package/dist/core/verse/github-repo.js.map +1 -0
- package/dist/core/verse/github-types.d.ts +174 -0
- package/dist/core/verse/github-types.js +31 -0
- package/dist/core/verse/github-types.js.map +1 -0
- package/dist/core/verse/mcp-cli-health.d.ts +149 -0
- package/dist/core/verse/mcp-cli-health.js +227 -0
- package/dist/core/verse/mcp-cli-health.js.map +1 -0
- package/dist/core/verse/mcp-control-api.d.ts +68 -0
- package/dist/core/verse/mcp-control-api.js +322 -0
- package/dist/core/verse/mcp-control-api.js.map +1 -0
- package/dist/core/verse/mcp-mutation.d.ts +187 -0
- package/dist/core/verse/mcp-mutation.js +471 -0
- package/dist/core/verse/mcp-mutation.js.map +1 -0
- package/dist/core/verse/mcp-scope.d.ts +114 -0
- package/dist/core/verse/mcp-scope.js +149 -0
- package/dist/core/verse/mcp-scope.js.map +1 -0
- package/dist/core/verse/mcp-seat-view.d.ts +197 -0
- package/dist/core/verse/mcp-seat-view.js +322 -0
- package/dist/core/verse/mcp-seat-view.js.map +1 -0
- package/dist/core/verse/path-guard.d.ts +115 -0
- package/dist/core/verse/path-guard.js +171 -0
- package/dist/core/verse/path-guard.js.map +1 -0
- package/dist/core/verse/seats.js +30 -2
- package/dist/core/verse/seats.js.map +1 -1
- package/dist/core/verse/session-engine.js +60 -1
- package/dist/core/verse/session-engine.js.map +1 -1
- package/dist/core/verse/session-store.js +20 -0
- package/dist/core/verse/session-store.js.map +1 -1
- package/dist/core/verse/types.d.ts +225 -1
- package/dist/core/verse/types.js +26 -0
- package/dist/core/verse/types.js.map +1 -1
- package/dist/core/verse/verse-api.d.ts +16 -1
- package/dist/core/verse/verse-api.js +338 -16
- package/dist/core/verse/verse-api.js.map +1 -1
- package/dist/core/verse/workspaces.d.ts +130 -0
- package/dist/core/verse/workspaces.js +501 -0
- package/dist/core/verse/workspaces.js.map +1 -0
- package/dist/core/web/api.js +23 -0
- package/dist/core/web/api.js.map +1 -1
- package/dist/core/web/public/next/assets/App-BbVoUCIi.js +7 -0
- package/dist/core/web/public/next/assets/App-BbVoUCIi.js.map +1 -0
- package/dist/core/web/public/next/assets/App-Cgi6j1Gk.css +1 -0
- package/dist/core/web/public/next/assets/ApprovalsSection-BW9iVjUa.js +2 -0
- package/dist/core/web/public/next/assets/ApprovalsSection-BW9iVjUa.js.map +1 -0
- package/dist/core/web/public/next/assets/ApprovalsSection-Su-NUTis.css +1 -0
- package/dist/core/web/public/next/assets/AutonomySection-CRqHUhED.js +2 -0
- package/dist/core/web/public/next/assets/AutonomySection-CRqHUhED.js.map +1 -0
- package/dist/core/web/public/next/assets/AutonomySection-Cpk_oEhc.css +1 -0
- package/dist/core/web/public/next/assets/ChatSection-5irrh6Jw.js +80 -0
- package/dist/core/web/public/next/assets/ChatSection-5irrh6Jw.js.map +1 -0
- package/dist/core/web/public/next/assets/ChatSection-LQiAn6Eu.css +1 -0
- package/dist/core/web/public/next/assets/ConfirmDialog-CXevY3Au.js +2 -0
- package/dist/core/web/public/next/assets/ConfirmDialog-CXevY3Au.js.map +1 -0
- package/dist/core/web/public/next/assets/{ConfirmDialog-cYuivhCX.css → ConfirmDialog-Dx15x0Ho.css} +1 -1
- package/dist/core/web/public/next/assets/Dialog-Bn0CgEGz.js +2 -0
- package/dist/core/web/public/next/assets/Dialog-Bn0CgEGz.js.map +1 -0
- package/dist/core/web/public/next/assets/Dialog-tQ8kFulD.css +1 -0
- package/dist/core/web/public/next/assets/DiffViewer-C8HHTnVE.css +1 -0
- package/dist/core/web/public/next/assets/DiffViewer-QKyisN2L.js +7 -0
- package/dist/core/web/public/next/assets/DiffViewer-QKyisN2L.js.map +1 -0
- package/dist/core/web/public/next/assets/{MutationTokenDialog-jWLLCxWn.css → MutationTokenDialog-CrfJ0PdD.css} +1 -1
- package/dist/core/web/public/next/assets/MutationTokenDialog-DbSHPy3w.js +2 -0
- package/dist/core/web/public/next/assets/MutationTokenDialog-DbSHPy3w.js.map +1 -0
- package/dist/core/web/public/next/assets/ResourcePoolConsoleApp-C-PCgCzo.js +7 -0
- package/dist/core/web/public/next/assets/ResourcePoolConsoleApp-C-PCgCzo.js.map +1 -0
- package/dist/core/web/public/next/assets/ResourcePoolConsoleApp-CM_8pJs6.css +1 -0
- package/dist/core/web/public/next/assets/RouteErrorBoundary-Dw8ZjmkS.js +2 -0
- package/dist/core/web/public/next/assets/RouteErrorBoundary-Dw8ZjmkS.js.map +1 -0
- package/dist/core/web/public/next/assets/RouteErrorBoundary-ekyMXAK8.css +1 -0
- package/dist/core/web/public/next/assets/SettingsSection-BII7TTSD.css +1 -0
- package/dist/core/web/public/next/assets/SettingsSection-CWxtTNlD.js +2 -0
- package/dist/core/web/public/next/assets/SettingsSection-CWxtTNlD.js.map +1 -0
- package/dist/core/web/public/next/assets/UniverseConsoleApp-Bk5HipM0.js +2 -0
- package/dist/core/web/public/next/assets/UniverseConsoleApp-Bk5HipM0.js.map +1 -0
- package/dist/core/web/public/next/assets/UniverseConsoleApp-Dh7A-hLD.css +1 -0
- package/dist/core/web/public/next/assets/UniverseView-Bqa8DH57.css +1 -0
- package/dist/core/web/public/next/assets/UniverseView-D8k9q0ce.js +9 -0
- package/dist/core/web/public/next/assets/UniverseView-D8k9q0ce.js.map +1 -0
- package/dist/core/web/public/next/assets/UsageSection-B2E06ZH1.js +2 -0
- package/dist/core/web/public/next/assets/UsageSection-B2E06ZH1.js.map +1 -0
- package/dist/core/web/public/next/assets/UsageSection-DwgNOYvi.css +1 -0
- package/dist/core/web/public/next/assets/VerseConsoleApp-CRfZUV-T.js +3 -0
- package/dist/core/web/public/next/assets/VerseConsoleApp-CRfZUV-T.js.map +1 -0
- package/dist/core/web/public/next/assets/VerseConsoleApp-D2E3mU9a.css +1 -0
- package/dist/core/web/public/next/assets/charts-CsrtT4rk.css +1 -0
- package/dist/core/web/public/next/assets/charts-DSUpczxG.js +2 -0
- package/dist/core/web/public/next/assets/charts-DSUpczxG.js.map +1 -0
- package/dist/core/web/public/next/assets/fleet-model-ClBbQ0Uj.js +2 -0
- package/dist/core/web/public/next/assets/fleet-model-ClBbQ0Uj.js.map +1 -0
- package/dist/core/web/public/next/assets/format-BNYWGJat.js +2 -0
- package/dist/core/web/public/next/assets/format-BNYWGJat.js.map +1 -0
- package/dist/core/web/public/next/assets/global-C4jfYHrj.js +2 -0
- package/dist/core/web/public/next/assets/global-C4jfYHrj.js.map +1 -0
- package/dist/core/web/public/next/assets/global-D463gG1X.css +1 -0
- package/dist/core/web/public/next/assets/icons-BxOX8-XB.js +2 -0
- package/dist/core/web/public/next/assets/icons-BxOX8-XB.js.map +1 -0
- package/dist/core/web/public/next/assets/index-BMRIET7F.js +11 -0
- package/dist/core/web/public/next/assets/index-BMRIET7F.js.map +1 -0
- package/dist/core/web/public/next/assets/queries-AxnVa02h.js +2 -0
- package/dist/core/web/public/next/assets/queries-AxnVa02h.js.map +1 -0
- package/dist/core/web/public/next/assets/use-guarded-action-BO6c94Wp.js +2 -0
- package/dist/core/web/public/next/assets/use-guarded-action-BO6c94Wp.js.map +1 -0
- package/dist/core/web/public/next/assets/verse-model-CvtK6vP6.js +2 -0
- package/dist/core/web/public/next/assets/verse-model-CvtK6vP6.js.map +1 -0
- package/dist/core/web/public/next/assets/workspace-model-cE4LkKpE.js +2 -0
- package/dist/core/web/public/next/assets/workspace-model-cE4LkKpE.js.map +1 -0
- package/dist/core/web/public/next/index.html +1 -1
- package/dist/release-dependency-inventory.json +1 -1
- package/docs/README.md +4 -0
- package/node_modules/marked/README.md +8 -0
- package/node_modules/marked/bin/main.js +12 -11
- package/node_modules/marked/lib/marked.d.ts +88 -25
- package/node_modules/marked/lib/marked.esm.js +50 -44
- package/node_modules/marked/lib/marked.esm.js.map +3 -3
- package/node_modules/marked/lib/marked.umd.js +50 -44
- package/node_modules/marked/lib/marked.umd.js.map +3 -3
- package/node_modules/marked/man/marked.1 +1 -1
- package/node_modules/marked/package.json +21 -21
- package/package.json +13 -8
- package/dist/cli/activation.d.ts +0 -41
- package/dist/cli/activation.js +0 -485
- package/dist/cli/activation.js.map +0 -1
- package/dist/core/fleet/generated-repair-resolution.d.ts +0 -36
- package/dist/core/fleet/generated-repair-resolution.js +0 -245
- package/dist/core/fleet/generated-repair-resolution.js.map +0 -1
- package/dist/core/local-runtime/llama/port.d.ts +0 -17
- package/dist/core/local-runtime/llama/port.js +0 -47
- package/dist/core/local-runtime/llama/port.js.map +0 -1
- package/dist/core/local-runtime/llama/proxy-host.d.ts +0 -54
- package/dist/core/local-runtime/llama/proxy-host.js +0 -159
- package/dist/core/local-runtime/llama/proxy-host.js.map +0 -1
- package/dist/core/local-runtime/llama/proxy-process.d.ts +0 -148
- package/dist/core/local-runtime/llama/proxy-process.js +0 -250
- package/dist/core/local-runtime/llama/proxy-process.js.map +0 -1
- package/dist/core/local-runtime/llama/proxy-record.d.ts +0 -40
- package/dist/core/local-runtime/llama/proxy-record.js +0 -133
- package/dist/core/local-runtime/llama/proxy-record.js.map +0 -1
- package/dist/core/local-runtime/llama/proxy-supervisor.d.ts +0 -118
- package/dist/core/local-runtime/llama/proxy-supervisor.js +0 -511
- package/dist/core/local-runtime/llama/proxy-supervisor.js.map +0 -1
- package/dist/core/web/public/next/assets/App-BCKqSppK.css +0 -1
- package/dist/core/web/public/next/assets/App-LuILXjWG.js +0 -16
- package/dist/core/web/public/next/assets/App-LuILXjWG.js.map +0 -1
- package/dist/core/web/public/next/assets/ApprovalsSection-B_Xor8JH.js +0 -2
- package/dist/core/web/public/next/assets/ApprovalsSection-B_Xor8JH.js.map +0 -1
- package/dist/core/web/public/next/assets/ApprovalsSection-CRP59wSb.css +0 -1
- package/dist/core/web/public/next/assets/AutonomySection-BB8RD-tv.css +0 -1
- package/dist/core/web/public/next/assets/AutonomySection-BEwITtPJ.js +0 -2
- package/dist/core/web/public/next/assets/AutonomySection-BEwITtPJ.js.map +0 -1
- package/dist/core/web/public/next/assets/ChatSection-DrxqhBPK.css +0 -1
- package/dist/core/web/public/next/assets/ChatSection-bRAzlMth.js +0 -77
- package/dist/core/web/public/next/assets/ChatSection-bRAzlMth.js.map +0 -1
- package/dist/core/web/public/next/assets/ConfirmDialog-BqkZdPs_.js +0 -2
- package/dist/core/web/public/next/assets/ConfirmDialog-BqkZdPs_.js.map +0 -1
- package/dist/core/web/public/next/assets/Dialog-CPmedg4S.js +0 -2
- package/dist/core/web/public/next/assets/Dialog-CPmedg4S.js.map +0 -1
- package/dist/core/web/public/next/assets/Dialog-DxN7RgB5.css +0 -1
- package/dist/core/web/public/next/assets/DiffViewer-DmPJbeM_.js +0 -7
- package/dist/core/web/public/next/assets/DiffViewer-DmPJbeM_.js.map +0 -1
- package/dist/core/web/public/next/assets/DiffViewer-hN7X6wGT.css +0 -1
- package/dist/core/web/public/next/assets/Epistemic-DiyMyeaN.css +0 -1
- package/dist/core/web/public/next/assets/Epistemic-M3SxLHjq.js +0 -2
- package/dist/core/web/public/next/assets/Epistemic-M3SxLHjq.js.map +0 -1
- package/dist/core/web/public/next/assets/Meter-CGBsbXUX.css +0 -1
- package/dist/core/web/public/next/assets/Meter-D_X7c9U3.js +0 -2
- package/dist/core/web/public/next/assets/Meter-D_X7c9U3.js.map +0 -1
- package/dist/core/web/public/next/assets/MutationTokenDialog-Ba5ZHu-8.js +0 -2
- package/dist/core/web/public/next/assets/MutationTokenDialog-Ba5ZHu-8.js.map +0 -1
- package/dist/core/web/public/next/assets/RefreshIndicator-O7jUBOmX.css +0 -1
- package/dist/core/web/public/next/assets/RefreshIndicator-vHGpmyzb.js +0 -2
- package/dist/core/web/public/next/assets/RefreshIndicator-vHGpmyzb.js.map +0 -1
- package/dist/core/web/public/next/assets/ResourcePoolConsoleApp-B5g6JEVk.js +0 -7
- package/dist/core/web/public/next/assets/ResourcePoolConsoleApp-B5g6JEVk.js.map +0 -1
- package/dist/core/web/public/next/assets/ResourcePoolConsoleApp-DLl4DbGG.css +0 -1
- package/dist/core/web/public/next/assets/RouteErrorBoundary-BIpdnxp-.css +0 -1
- package/dist/core/web/public/next/assets/RouteErrorBoundary-CroxuKoi.js +0 -2
- package/dist/core/web/public/next/assets/RouteErrorBoundary-CroxuKoi.js.map +0 -1
- package/dist/core/web/public/next/assets/SettingsSection-DGJMdUh2.css +0 -1
- package/dist/core/web/public/next/assets/SettingsSection-p-7FCGfn.js +0 -2
- package/dist/core/web/public/next/assets/SettingsSection-p-7FCGfn.js.map +0 -1
- package/dist/core/web/public/next/assets/Skeleton-4qq4HuJO.js +0 -2
- package/dist/core/web/public/next/assets/Skeleton-4qq4HuJO.js.map +0 -1
- package/dist/core/web/public/next/assets/Skeleton-D2JaTXyv.css +0 -1
- package/dist/core/web/public/next/assets/StatusBadge-BbEdqEGn.js +0 -2
- package/dist/core/web/public/next/assets/StatusBadge-BbEdqEGn.js.map +0 -1
- package/dist/core/web/public/next/assets/StatusBadge-TpDqZf6K.css +0 -1
- package/dist/core/web/public/next/assets/TableView-R6Yeztp1.css +0 -1
- package/dist/core/web/public/next/assets/TableView-nyYs7Sv1.js +0 -2
- package/dist/core/web/public/next/assets/TableView-nyYs7Sv1.js.map +0 -1
- package/dist/core/web/public/next/assets/UniverseConsoleApp-CiZZxRPV.css +0 -1
- package/dist/core/web/public/next/assets/UniverseConsoleApp-DjTpQUrA.js +0 -2
- package/dist/core/web/public/next/assets/UniverseConsoleApp-DjTpQUrA.js.map +0 -1
- package/dist/core/web/public/next/assets/UniverseView-BtApwayT.js +0 -9
- package/dist/core/web/public/next/assets/UniverseView-BtApwayT.js.map +0 -1
- package/dist/core/web/public/next/assets/UniverseView-gBxl6yum.css +0 -1
- package/dist/core/web/public/next/assets/UsageSection-Db99N6ao.js +0 -2
- package/dist/core/web/public/next/assets/UsageSection-Db99N6ao.js.map +0 -1
- package/dist/core/web/public/next/assets/UsageSection-UUYDidOg.css +0 -1
- package/dist/core/web/public/next/assets/VerseConsoleApp-COa3J2dO.js +0 -3
- package/dist/core/web/public/next/assets/VerseConsoleApp-COa3J2dO.js.map +0 -1
- package/dist/core/web/public/next/assets/VerseConsoleApp-mOAXvWa6.css +0 -1
- package/dist/core/web/public/next/assets/fleet-model-DXCfVtNW.js +0 -2
- package/dist/core/web/public/next/assets/fleet-model-DXCfVtNW.js.map +0 -1
- package/dist/core/web/public/next/assets/format-fggwu_Rs.js +0 -2
- package/dist/core/web/public/next/assets/format-fggwu_Rs.js.map +0 -1
- package/dist/core/web/public/next/assets/global-BeRYd1Bu.css +0 -1
- package/dist/core/web/public/next/assets/global-CNVapoV1.js +0 -2
- package/dist/core/web/public/next/assets/global-CNVapoV1.js.map +0 -1
- package/dist/core/web/public/next/assets/index-BkUlX2Se.js +0 -51
- package/dist/core/web/public/next/assets/index-BkUlX2Se.js.map +0 -1
- package/dist/core/web/public/next/assets/index-YNTgDG2b.js +0 -2
- package/dist/core/web/public/next/assets/index-YNTgDG2b.js.map +0 -1
- package/dist/core/web/public/next/assets/queries-C9XS4SYq.js +0 -2
- package/dist/core/web/public/next/assets/queries-C9XS4SYq.js.map +0 -1
- package/dist/core/web/public/next/assets/use-guarded-action-Bqa2v0a-.css +0 -1
- package/dist/core/web/public/next/assets/use-guarded-action-Bql1nA-z.js +0 -2
- package/dist/core/web/public/next/assets/use-guarded-action-Bql1nA-z.js.map +0 -1
- package/dist/core/web/public/next/assets/verse-model-IrNQKtER.js +0 -2
- package/dist/core/web/public/next/assets/verse-model-IrNQKtER.js.map +0 -1
- package/node_modules/which/CHANGELOG.md +0 -166
- /package/node_modules/marked/{LICENSE.md → LICENSE} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
._button_12qr7_1{display:inline-flex;align-items:center;justify-content:center;gap:var(--space-2);height:var(--density-row);padding:0 var(--density-pad);border:1px solid transparent;border-radius:var(--radius-sm);background:transparent;color:var(--text-primary);font-family:var(--font-ui);font-size:var(--text-sm-size);line-height:1;font-weight:var(--font-weight-medium);white-space:nowrap;cursor:pointer;transition:background-color var(--duration-fast) var(--ease-standard),border-color var(--duration-fast) var(--ease-standard),color var(--duration-fast) var(--ease-standard),opacity var(--duration-fast) var(--ease-standard)}._button_12qr7_1:disabled{cursor:not-allowed;opacity:.55}._glyph_12qr7_30{display:inline-flex;flex:none;align-items:center;justify-content:center}._sm_12qr7_37{height:var(--density-row-sm);padding:0 var(--space-2);font-size:var(--text-xs-size)}._block_12qr7_43{width:100%}._iconOnly_12qr7_48{width:var(--density-row);padding:0}._sm_12qr7_37._iconOnly_12qr7_48{width:var(--density-row-sm)}._ghost_12qr7_60{color:var(--text-secondary)}._ghost_12qr7_60:hover:not(:disabled){background:var(--bg-hover);color:var(--text-primary)}._ghost_12qr7_60:active:not(:disabled){background:var(--bg-active)}._subtle_12qr7_71{border-color:var(--border-default);background:var(--bg-surface);color:var(--text-primary)}._subtle_12qr7_71:hover:not(:disabled){background:var(--bg-hover);border-color:var(--border-strong)}._subtle_12qr7_71:active:not(:disabled){background:var(--bg-active)}._primary_12qr7_84{background:var(--accent-600);border-color:var(--accent-600);color:var(--text-on-accent)}._primary_12qr7_84:hover:not(:disabled){background:var(--accent-700);border-color:var(--accent-700)}._primary_12qr7_84:active:not(:disabled){background:var(--accent-700)}._danger_12qr7_99{border-color:var(--border-default);color:var(--status-danger-fg)}._danger_12qr7_99:hover:not(:disabled){background:var(--status-danger-bg);border-color:var(--status-danger-solid)}._danger_12qr7_99:active:not(:disabled){background:var(--status-danger-bg)}._button_12qr7_1[aria-pressed=true],._button_12qr7_1[aria-expanded=true]{background:var(--bg-selected);color:var(--text-primary)}._tag_2eqdv_1{display:inline-flex;align-items:center;gap:var(--space-1);max-width:100%;height:var(--density-row-sm);padding:0 var(--space-2);border:1px solid var(--border-subtle);border-radius:var(--radius-xs);background:var(--bg-hover);color:var(--text-secondary);font-size:var(--text-xs-size);line-height:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}._sm_2eqdv_19{height:calc(var(--density-row-sm) - var(--space-2));padding:0 var(--space-1);font-size:var(--text-2xs-size)}._mono_2eqdv_25{font-family:var(--font-mono);font-size:var(--text-2xs-size)}._dot_2eqdv_30{flex:none;width:6px;height:6px;border-radius:var(--radius-full);background:var(--status-neutral-solid)}._marker_2eqdv_38{display:inline-block;flex:none;width:2px;height:100%;min-height:var(--engine-marker-h);border-radius:var(--radius-full)}._card_13g9r_14{position:fixed;z-index:var(--z-dialog);left:calc(var(--rail-width) + var(--space-5));right:auto;bottom:calc(var(--space-5) + env(safe-area-inset-bottom,0px));width:min(420px,calc(100vw - var(--rail-width) - var(--space-5) * 2));max-height:min(640px,calc(100vh - var(--space-8)));display:flex;flex-direction:column;overflow:hidden;background:var(--bg-surface-raised);border:1px solid var(--border-default);border-radius:var(--radius-lg);box-shadow:var(--shadow-dialog)}._card_13g9r_14{animation:_onboarding-in_13g9r_1 var(--duration-base) var(--ease-decelerate)}@keyframes _onboarding-in_13g9r_1{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}._head_13g9r_56{display:flex;align-items:flex-start;gap:var(--space-2);padding:var(--space-4) var(--space-4) var(--space-3);border-bottom:1px solid var(--border-subtle)}._headText_13g9r_64{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}._eyebrow_13g9r_72{font-family:var(--font-display);font-size:var(--text-2xs-size);letter-spacing:var(--tracking-label);text-transform:uppercase;color:var(--text-tertiary)}._title_13g9r_80{font-family:var(--font-display);font-size:var(--text-md-size);font-weight:var(--font-weight-semibold);letter-spacing:var(--tracking-display);color:var(--text-primary)}._subtitle_13g9r_88{font-size:var(--text-xs-size);line-height:var(--text-xs-line-relaxed);color:var(--text-secondary)}._body_13g9r_96{flex:1;min-height:0;overflow-y:auto;padding:var(--space-4);display:flex;flex-direction:column;gap:var(--space-3);align-items:flex-start}._lead_13g9r_107{font-size:var(--text-sm-size);line-height:var(--text-sm-line-relaxed);color:var(--text-primary)}._aside_13g9r_113,._caveat_13g9r_114{font-size:var(--text-xs-size);line-height:var(--text-xs-line-relaxed);color:var(--text-tertiary)}._caveat_13g9r_114{border-left:2px solid var(--border-strong);padding-left:var(--space-3)}._kbd_13g9r_125{font-family:var(--font-mono);font-size:var(--text-2xs-size);padding:1px 5px;border:1px solid var(--border-default);border-radius:var(--radius-xs);background:var(--bg-surface);color:var(--text-secondary);white-space:nowrap}._sections_13g9r_138,._findings_13g9r_139{list-style:none;margin:0;padding:0;width:100%;display:flex;flex-direction:column}._sectionRow_13g9r_148{display:flex;align-items:flex-start;gap:var(--space-3);padding:var(--space-2) 0;border-top:1px solid var(--border-subtle)}._sectionRow_13g9r_148:first-child{border-top:none}._sectionIcon_13g9r_160,._localIcon_13g9r_161{flex:none;display:inline-flex;align-items:center;justify-content:center;width:var(--control-h-xs);height:var(--control-h-xs);color:var(--text-tertiary)}._sectionText_13g9r_173{display:flex;flex-direction:column;gap:2px;min-width:0}._sectionName_13g9r_180{font-family:var(--font-display);font-size:var(--text-xs-size);font-weight:var(--font-weight-semibold);color:var(--text-primary);display:inline-flex;align-items:center;gap:var(--space-2)}._sectionBlurb_13g9r_190{font-size:var(--text-xs-size);line-height:var(--text-xs-line-relaxed);color:var(--text-secondary)}._finding_13g9r_139{display:flex;flex-direction:column;gap:var(--space-1);padding:var(--space-3) 0;border-top:1px solid var(--border-subtle)}._finding_13g9r_139:first-child{border-top:none}._findingHead_13g9r_210{display:flex;align-items:center;justify-content:space-between;gap:var(--space-2);flex-wrap:wrap}._stopName_13g9r_218{font-family:var(--font-display);font-size:var(--text-sm-size);font-weight:var(--font-weight-semibold);color:var(--text-primary)}._findingDetail_13g9r_225{font-size:var(--text-xs-size);line-height:var(--text-xs-line-relaxed);color:var(--text-secondary)}._findingNote_13g9r_231{font-size:var(--text-xs-size);line-height:var(--text-xs-line-relaxed);color:var(--text-tertiary)}._findingFix_13g9r_237{display:flex;align-items:baseline;gap:var(--space-2);flex-wrap:wrap;margin-top:2px}._fixLabel_13g9r_245{font-family:var(--font-display);font-size:var(--text-2xs-size);letter-spacing:var(--tracking-label);text-transform:uppercase;color:var(--text-tertiary);flex:none}._fixText_13g9r_254{font-size:var(--text-xs-size);line-height:var(--text-xs-line-relaxed);color:var(--text-secondary)}._code_13g9r_260{font-family:var(--font-mono);font-size:var(--text-xs-size);padding:2px 6px;border:1px solid var(--border-subtle);border-radius:var(--radius-xs);background:var(--bg-code, var(--bg-surface));color:var(--text-primary);overflow-wrap:anywhere}._tone_13g9r_276{display:inline-flex;align-items:center;gap:5px;flex:none}._toneLabel_13g9r_283{font-family:var(--font-display);font-size:var(--text-2xs-size);letter-spacing:var(--tracking-label);text-transform:uppercase;color:var(--text-secondary)}._tone_13g9r_276[data-tone=ok]{color:var(--status-success-fg, var(--text-secondary))}._tone_13g9r_276[data-tone=attention]{color:var(--status-warning-fg, var(--text-secondary))}._tone_13g9r_276[data-tone=unknown]{color:var(--text-tertiary)}._foot_13g9r_305{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);padding:var(--space-3) var(--space-4);border-top:1px solid var(--border-subtle);background:var(--bg-surface)}._pips_13g9r_315{list-style:none;margin:0;padding:0;display:flex;gap:5px}._pip_13g9r_315{width:14px;height:2px;border-radius:1px;background:var(--border-default)}._pip_13g9r_315[data-state=current]{background:var(--accent-500)}._pip_13g9r_315[data-state=done]{background:var(--border-strong)}._actions_13g9r_338{display:flex;align-items:center;gap:var(--space-2)}@media(max-width:900px){._card_13g9r_14{left:calc(var(--rail-width) + var(--space-3));right:auto;width:min(420px,calc(100vw - var(--rail-width) - var(--space-3) * 2))}}._shell_tbse1_5{display:grid;grid-template-columns:var(--rail-width) minmax(0,1fr);height:100%;min-height:0;background:var(--bg-canvas);color:var(--text-primary);font-family:var(--font-ui)}._shell_tbse1_5>*{min-width:0;min-height:0}._rail_tbse1_21{position:relative;display:flex;flex-direction:column;align-items:center;gap:var(--space-2);padding:var(--space-3) 0;border-right:1px solid var(--border-subtle);background:var(--bg-canvas)}._rail_tbse1_21{padding-top:calc(var(--space-3) + var(--app-titlebar-height, 0px))}._railDragStrip_tbse1_44{position:absolute;top:0;left:0;width:100%;height:var(--app-titlebar-height, 0px)}._mark_tbse1_52{display:flex;align-items:center;justify-content:center;width:32px;height:32px;margin-bottom:var(--space-2);color:var(--accent-500)}._railList_tbse1_62{display:flex;flex-direction:column;align-items:center;gap:2px;list-style:none;margin:0;padding:0}._railFoot_tbse1_71{margin-top:auto}._railButton_tbse1_73{position:relative;display:flex;align-items:center;justify-content:center;width:calc(var(--control-h) + var(--space-1));height:calc(var(--control-h) + var(--space-1));border-radius:var(--radius-sm);color:var(--text-tertiary);transition:color var(--duration-fast) var(--ease-standard),background var(--duration-fast) var(--ease-standard)}._railButton_tbse1_73:hover{color:var(--text-primary);background:var(--bg-hover)}._railButton_tbse1_73:active{background:var(--bg-active)}._railButton_tbse1_73[aria-current]{color:var(--text-primary)}._railButton_tbse1_73[aria-current]:before{content:"";position:absolute;left:-10px;top:50%;height:56%;transform:translateY(-50%);width:2px;border-radius:1px;background:var(--accent-500)}._railDot_tbse1_104{position:absolute;top:6px;right:6px;width:6px;height:6px;border-radius:var(--radius-full);background:var(--status-warning-solid);box-shadow:0 0 0 2px var(--bg-canvas)}._section_tbse1_116{display:flex;flex-direction:column;min-width:0;min-height:0;overflow:hidden;background:var(--bg-surface)}._section_tbse1_116>*{min-height:0}._loading_tbse1_126{flex:1;display:flex;align-items:center;justify-content:center;color:var(--text-secondary);font-size:var(--text-sm-size)}._missing_tbse1_135{max-width:56ch;margin:clamp(32px,12vh,120px) auto 0;padding:0 var(--space-6);display:grid;gap:var(--space-3)}._missingTitle_tbse1_142{font-family:var(--font-display);font-size:var(--text-xl-size);line-height:var(--text-xl-line);font-weight:var(--font-weight-medium);letter-spacing:var(--tracking-display, -.02em)}._missingBody_tbse1_149,._missingDetail_tbse1_150{color:var(--text-secondary);font-size:var(--text-sm-size);line-height:var(--text-sm-line)}._missingBody_tbse1_149 code{font-size:var(--text-xs-size);color:var(--text-primary)}._missingDetail_tbse1_150{padding-left:var(--space-3);border-left:2px solid var(--border-strong);color:var(--text-tertiary);font-family:var(--font-mono);font-size:var(--text-xs-size);overflow-wrap:anywhere}._shell_k4nf7_1{height:100dvh;min-height:100%;display:flex;flex-direction:column;overflow:hidden;background:var(--bg-canvas);color:var(--text-primary)}._checking_k4nf7_2{padding:var(--space-8);color:var(--text-secondary)}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{U as d,l as $}from"./format-fggwu_Rs.js";function u(n){return typeof n=="object"&&n!==null&&!Array.isArray(n)?n:null}function r(n,e){const t=n[e];return typeof t=="string"&&t.length>0?t:null}function v(n,e){const t=n[e];return typeof t=="number"&&Number.isFinite(t)?t:null}function a(n,e){const t=v(n,e);return t===null?null:Math.max(0,Math.round(t))}function f(n,e){const t=n[e];return typeof t=="boolean"?t:null}function A(n,e){const t=n[e];return Array.isArray(t)?t.filter(s=>typeof s=="string"):[]}const S=["llama-server","ollama","vllm","unknown"],q=["running","starting","stopping","stopped","unknown"],T=["queued","running","finishing"],E=["off","config","env","config+env","latch","unknown"];function h(n,e,t,s){const o=n[e];return typeof o=="string"&&t.includes(o)?o:s}function C(n){const e=u(n);return e?{capable:f(e,"capable"),refusal:r(e,"refusal"),slots:a(e,"slots")}:{capable:null,refusal:null,slots:null}}const y={up:"running",down:"stopped",loading:"starting",unknown:"unknown"};function R(n){const e=n.state;if(typeof e!="string"||!(e in y))return null;const t=u(n.slots),s=r(n,"host"),o=a(n,"port"),i=t?a(t,"configured"):null,l=t?a(t,"busy"):null,m=t?r(t,"source"):null,g=m!==null&&m!=="unknown",c=g?i:null,w=r(n,"parallelRefusal"),b=r(n,"runtimeKind"),k=a(n,"contextPerSlot"),p=a(n,"contextTotal");return{kind:b==="ollama"?"ollama":b==="unknown"?"unknown":"llama-server",state:y[e]??"unknown",endpoint:s!==null&&o!==null?`${s}:${o}`:r(n,"origin")??null,model:N(r(n,"modelName"))??N(r(n,"model")),slotsTotal:c,slotsBusy:g?l:null,contextTokens:k??(c!==null&&c>0&&p!==null&&p>0?Math.floor(p/c):null),startedAt:r(n,"startedAt"),parallel:{capable:w!==null?!1:g&&c!==null?!0:null,refusal:w,slots:c},reason:r(n,"lastError"),supervised:f(n,"managed")??!1,sampledAt:r(n,"checkedAt")}}function N(n){if(n===null||n.length===0)return null;const e=n.split("/").pop()??n;return e.length===0||/^sha256[-:][0-9a-f]{16,}$/i.test(e)?null:e}function _(n){const e=u(n);if(!e)return null;if(!("kind"in e)&&"slots"in e&&"state"in e){const t=R(e);if(t!==null)return t}return{kind:h(e,"kind",S,"unknown"),state:h(e,"state",q,"unknown"),endpoint:r(e,"endpoint"),model:r(e,"model"),slotsTotal:a(e,"slotsTotal"),slotsBusy:a(e,"slotsBusy"),contextTokens:a(e,"contextTokens"),startedAt:r(e,"startedAt"),parallel:C(e.parallel),reason:r(e,"reason"),supervised:f(e,"supervised")??!1,sampledAt:r(e,"sampledAt")}}function x(n){const e=u(n);if(!e)return null;const t=r(e,"id");return t===null?null:{id:t,task:r(e,"task"),repo:r(e,"repo"),engine:r(e,"engine"),model:r(e,"model"),state:h(e,"state",T,"running"),startedAt:r(e,"startedAt"),slot:a(e,"slot")}}function j(n){const e=u(n);return e?{agents:(Array.isArray(e.agents)?e.agents:[]).map(x).filter(s=>s!==null),queueDepth:a(e,"queueDepth"),slotsTotal:a(e,"slotsTotal"),slotsBusy:a(e,"slotsBusy"),notes:A(e,"notes"),sampledAt:r(e,"sampledAt")}:null}function M(n){const e=u(n);if(!e)return null;const t=r(e,"engine");return t===null?null:{engine:t,reason:r(e,"reason")??"The dispatcher refuses this engine while local-only is on."}}const D=new Set(["env","config+env","latch","unknown"]);function F(n){const e=u(n);if(!e)return null;const t=Array.isArray(e.refuses)?e.refuses:[],s=h(e,"source",E,"unknown");return{enabled:f(e,"enabled")??!1,source:s,refuses:t.map(M).filter(o=>o!==null),mutable:D.has(s)?!1:f(e,"mutable")??!1,detail:r(e,"detail"),sampledAt:r(e,"sampledAt")??r(e,"checkedAt")}}function L(n){switch(n){case"llama-server":return"llama-server";case"ollama":return"Ollama";case"vllm":return"vLLM";default:return"the serving runtime"}}function B(n){if(n===null)return{verdict:"unknown",effectiveConcurrency:null,configuredSlots:null,overstated:!1,headline:"No serving runtime reported",detail:"Nothing answered for the serving runtime, so how many agents can run at once is unknown. That is a missing reading, not an idle machine.",tone:"unknown"};const e=n.slotsTotal??n.parallel.slots,t=L(n.kind);if(n.state!=="running")return{verdict:"offline",effectiveConcurrency:null,configuredSlots:e,overstated:!1,headline:n.state==="stopped"?"Not serving":`Serving runtime is ${n.state}`,detail:n.state==="stopped"?`${t} is not running, so no local agent can take a turn. Start it before dispatching a fleet.`:`${t} is ${n.state}; capacity is not settled until it reports running.`,tone:n.state==="stopped"?"neutral":"warning"};if(n.parallel.capable===!1){const s=e!==null&&e>1;return{verdict:"serialized",effectiveConcurrency:1,configuredSlots:e,overstated:s,headline:`${t} serializes requests`,detail:s?`${t} has ${e} slots configured but runs one request at a time for this model, so the real capacity is one agent. The other ${e-1} would queue, and a queue looks like slowness rather than a wait.`:`${t} runs one request at a time for this model. A second agent waits for the first to finish.`,tone:"danger"}}return n.parallel.capable===null?{verdict:"unknown",effectiveConcurrency:null,configuredSlots:e,overstated:!1,headline:"Batching not confirmed",detail:`${t} is running${e===null?"":` with ${e} slots configured`}, 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.`,tone:"unknown"}:e===null?{verdict:"batched",effectiveConcurrency:null,configuredSlots:null,overstated:!1,headline:`${t} batches concurrent requests`,detail:`${t} batches, but reported no slot count, so the ceiling on concurrent agents is unknown.`,tone:"unknown"}:{verdict:"batched",effectiveConcurrency:e,configuredSlots:e,overstated:!1,headline:`${e} agent${e===1?"":"s"} in parallel`,detail:`${t} batches across ${e} slot${e===1?"":"s"} sharing one copy of the weights, so ${e} agent${e===1?"":"s"} can be mid-turn at once.`,tone:"success"}}const I=[{runtime:"Ollama",perRequestSeconds:[3.7,7.6,11.4,15.2],wallSeconds:15.2,verdict:"staggered — the requests queued"},{runtime:"llama-server",perRequestSeconds:[8.6,8.9,9,9],wallSeconds:9.1,verdict:"together — the requests batched"}];function P(n,e){if(n===null||e===null||e<=0)return{busy:n,total:e,percent:null,saturated:!1,label:n===null?"Slot occupancy is not reported.":`${n} in flight; the number of slots is not known, so occupancy cannot be drawn.`};const t=Math.min(n,e),s=Math.round(t/e*100),o=n>=e;return{busy:n,total:e,percent:s,saturated:o,label:o?`All ${e} slot${e===1?"":"s"} busy — new turns wait for one to free up.`:`${n} of ${e} slot${e===1?"":"s"} busy.`}}function z(n,e,t){if(n===null)return{state:"unknown",headline:"Fleet not reported",detail:"No fleet snapshot answered, so what is in flight cannot be listed. That is a missing reading, not an empty fleet.",tone:"unknown"};const s=n.agents.filter(i=>i.state!=="queued").length,o=n.queueDepth??n.agents.filter(i=>i.state==="queued").length;return o>0?{state:"queued",headline:`${o} waiting for a slot`,detail:t.verdict==="serialized"?`${s} turn${s===1?"":"s"} in flight and ${o} queued. This runtime serializes, so the queue drains one at a time — the wait is a wait, not slow inference.`:`${s} turn${s===1?"":"s"} in flight and ${o} queued behind ${e.total===null?"the available slots":`${e.total} slots`}. Queued turns have not started; their wall time is queueing, not work.`,tone:"warning"}:s===0?{state:"idle",headline:"Nothing in flight",detail:"No agent is mid-turn. The fleet is idle, not stuck.",tone:"neutral"}:e.saturated?{state:"saturated",headline:`All slots busy · ${s} in flight`,detail:"Every slot is working and nothing is queued behind them. The next turn will wait, but none is waiting yet.",tone:"running"}:{state:"working",headline:`${s} in flight`,detail:e.label,tone:"running"}}const O="Refused: local-only is on and this engine is not local.";function K(n,e){const t=e.filter(l=>l.engine!=="local"),s=e.filter(l=>l.engine==="local"),o=new Map;for(const l of n?.refuses??[])o.set(l.engine,l.reason);const i=e.length===0?"No seats are configured, so there is nothing for this policy to block yet.":t.length===0?`Every one of your ${e.length} seat${e.length===1?"":"s"} is local, so this policy would block nothing today.`:`${t.length} of ${e.length} seat${e.length===1?"":"s"} would become unreachable; ${s.length} local seat${s.length===1?"":"s"} would keep working.`;return{blocked:t,allowed:s,refusalFor:l=>o.get(l)??O,summary:i}}function Y(n){if(n===null)return null;if(n.detail!==null)return n.detail;switch(n.source){case"env":case"config+env":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.";case"latch":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.";case"config":return"Stored in the hub config and applied on the next dispatch.";case"off":return"Not configured; cloud engines are reachable.";default:return"The server did not say where this setting came from."}}function G(n,e){if(!n)return d;const t=Date.parse(n);return Number.isNaN(t)?d:$(Math.max(0,e-t))}function Q(n,e){if(!n)return d;const t=Date.parse(n);return Number.isNaN(t)?d:`up ${$(Math.max(0,e-t))}`}function V(n){return n===null||!Number.isFinite(n)?d:n>=1024?`${Math.round(n/1024)}k`:String(n)}function W(n){const e={running:0,finishing:1,queued:2};return[...n].sort((t,s)=>{const o=e[t.state]-e[s.state];if(o!==0)return o;const i=t.startedAt?Date.parse(t.startedAt):Number.NaN,l=s.startedAt?Date.parse(s.startedAt):Number.NaN;return Number.isNaN(i)&&Number.isNaN(l)?t.id.localeCompare(s.id):Number.isNaN(i)?1:Number.isNaN(l)?-1:i-l})}export{I as C,j as a,F as b,Y as c,L as d,G as e,z as f,V as g,Q as h,K as l,W as o,_ as p,B as r,P as s};
|
|
2
|
-
//# sourceMappingURL=fleet-model-DXCfVtNW.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"fleet-model-DXCfVtNW.js","sources":["../../../../../../src/web-ui/routes/verse/autonomy/fleet-model.ts"],"sourcesContent":["/**\n * routes/verse/autonomy/fleet-model.ts — every derivation the local-fleet\n * surfaces make, as pure functions. No React, no I/O, so the rules that decide\n * what the operator is told are unit-testable without a DOM.\n *\n * The one rule this file exists to enforce:\n *\n * **A concurrency number the runtime will not honour is a lie, and the UI\n * must print the truth instead of the configuration.**\n *\n * That is not a hypothetical. Measured on this machine (docs/LOCAL-FLEET.md),\n * four concurrent Qwen3.8 requests to Ollama returned at 3.7 / 7.6 / 11.4 /\n * 15.2s — a four-second stagger, which is a queue. The same weights on\n * llama-server returned at 8.6 / 8.9 / 9.0 / 9.0s: continuous batching across\n * four slots sharing one 27 GB copy. Ollama's log calls it a refusal\n * (`model architecture does not currently support parallel requests`) and\n * `OLLAMA_NUM_PARALLEL` does not move it.\n *\n * So `runtimeCapacity()` never reports `slotsTotal` as capacity unless the\n * runtime says it batches. When a runtime that serializes is configured with\n * four slots, the capacity is ONE and `overstated` is true, and the panel says\n * so in words. A queue that looks like slowness is the exact failure this\n * surface is here to prevent.\n *\n * The bodies these functions narrow come from routes that are landing in\n * parallel (see fleet-contract.ts), so everything is projected STRUCTURALLY\n * rather than cast: a field-name drift degrades to an honest \"unknown\" at\n * render time instead of throwing.\n */\nimport { UNKNOWN, formatDuration } from './format.js';\nimport type {\n FleetAgent,\n FleetAgentState,\n FleetSnapshot,\n LocalOnlyPolicy,\n LocalOnlyRefusal,\n LocalOnlySource,\n ServingRuntimeKind,\n ServingRuntimeParallelism,\n ServingRuntimeSnapshot,\n ServingRuntimeState,\n} from './fleet-contract.js';\n\n// ---------------------------------------------------------------------------\n// Structural narrowing\n// ---------------------------------------------------------------------------\n\nfunction record(value: unknown): Record<string, unknown> | null {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : null;\n}\n\nfunction str(source: Record<string, unknown>, key: string): string | null {\n const v = source[key];\n return typeof v === 'string' && v.length > 0 ? v : null;\n}\n\nfunction num(source: Record<string, unknown>, key: string): number | null {\n const v = source[key];\n return typeof v === 'number' && Number.isFinite(v) ? v : null;\n}\n\n/** A count that is meaningful at zero — so 0 stays 0 and absent stays null. */\nfunction count(source: Record<string, unknown>, key: string): number | null {\n const v = num(source, key);\n return v === null ? null : Math.max(0, Math.round(v));\n}\n\nfunction bool(source: Record<string, unknown>, key: string): boolean | null {\n const v = source[key];\n return typeof v === 'boolean' ? v : null;\n}\n\nfunction strings(source: Record<string, unknown>, key: string): string[] {\n const v = source[key];\n return Array.isArray(v) ? v.filter((entry): entry is string => typeof entry === 'string') : [];\n}\n\nconst RUNTIME_KINDS: readonly ServingRuntimeKind[] = ['llama-server', 'ollama', 'vllm', 'unknown'];\nconst RUNTIME_STATES: readonly ServingRuntimeState[] = [\n 'running',\n 'starting',\n 'stopping',\n 'stopped',\n 'unknown',\n];\nconst AGENT_STATES: readonly FleetAgentState[] = ['queued', 'running', 'finishing'];\nconst LOCAL_ONLY_SOURCES: readonly LocalOnlySource[] = [\n 'off',\n 'config',\n 'env',\n 'config+env',\n 'latch',\n 'unknown',\n];\n\nfunction oneOf<T extends string>(\n source: Record<string, unknown>,\n key: string,\n allowed: readonly T[],\n fallback: T,\n): T {\n const v = source[key];\n return typeof v === 'string' && (allowed as readonly string[]).includes(v) ? (v as T) : fallback;\n}\n\nfunction projectParallelism(raw: unknown): ServingRuntimeParallelism {\n const r = record(raw);\n if (!r) return { capable: null, refusal: null, slots: null };\n return { capable: bool(r, 'capable'), refusal: str(r, 'refusal'), slots: count(r, 'slots') };\n}\n\n/**\n * Owner R's `LlamaRuntimeSnapshot` (`src/core/local-runtime/llama/types.ts`)\n * spells the same facts differently, and the route that serves it is not\n * written yet — so rather than betting on one envelope, this reads both.\n *\n * The mapping, with the two judgement calls stated:\n *\n * - `state: 'up' | 'down' | 'loading' | 'unknown'` → running / stopped /\n * starting / unknown.\n * - `contextPerSlot` wins over `contextTotal`. `-c 65536 --parallel 4` gives\n * each slot 16k, and the number an agent's turn actually runs in is the\n * per-slot one. Quoting the total would overstate every agent's context\n * fourfold — the same class of error as quoting slots as concurrency.\n * - `managed` → `supervised`. R is explicit that an unmanaged server may be\n * adopted but is \"never killed on a guess\", which is exactly the condition\n * under which this panel must not offer a stop button.\n */\nconst LLAMA_STATE: Readonly<Record<string, ServingRuntimeState>> = {\n up: 'running',\n down: 'stopped',\n loading: 'starting',\n unknown: 'unknown',\n};\n\nfunction projectLlamaShape(r: Record<string, unknown>): ServingRuntimeSnapshot | null {\n const rawState = r['state'];\n if (typeof rawState !== 'string' || !(rawState in LLAMA_STATE)) return null;\n const slots = record(r['slots']);\n const host = str(r, 'host');\n const port = count(r, 'port');\n const configured = slots ? count(slots, 'configured') : null;\n const busy = slots ? count(slots, 'busy') : null;\n // `slots.source` is R's own trust marker: 'unknown' means \"do not trust a\n // number\", so a slot count carrying it is dropped rather than quoted.\n const slotSource = slots ? str(slots, 'source') : null;\n const trusted = slotSource !== null && slotSource !== 'unknown';\n const total = trusted ? configured : null;\n\n const refusal = str(r, 'parallelRefusal');\n const identity = str(r, 'runtimeKind');\n const contextPerSlot = count(r, 'contextPerSlot');\n const contextTotal = count(r, 'contextTotal');\n\n return {\n // IDENTIFIED where the producer says so. A base URL pointed at Ollama's\n // /v1 answers /v1/models — enough to keep the engine routable — while\n // 404ing /health and /props, and asserting 'llama-server' for it would\n // hide the one fact that matters: that runtime serialises this model.\n kind: identity === 'ollama' ? 'ollama' : identity === 'unknown' ? 'unknown' : 'llama-server',\n state: LLAMA_STATE[rawState] ?? 'unknown',\n endpoint: host !== null && port !== null ? `${host}:${port}` : (str(r, 'origin') ?? null),\n // A digest is not a name. `modelName` falls back to `model_path`'s basename\n // at the producer, which for a blob out of Ollama's content-addressed store\n // is `sha256-…`: it identifies the machine's storage layout and names\n // nothing. Null renders as \"unknown\", which is the honest answer.\n model: humaneModelName(str(r, 'modelName')) ?? humaneModelName(str(r, 'model')),\n slotsTotal: total,\n slotsBusy: trusted ? busy : null,\n // PER-SLOT, or nothing. `-c 65536 --parallel 4` gives each agent 16k, so\n // the `-c` total is usable only divided by a slot count we trust.\n contextTokens:\n contextPerSlot ??\n (total !== null && total > 0 && contextTotal !== null && contextTotal > 0\n ? Math.floor(contextTotal / total)\n : null),\n startedAt: str(r, 'startedAt'),\n parallel: {\n // llama-server's slots ARE its continuous-batching slots: a count read\n // back off the live server (`/props.total_slots`, `/slots`) is a count\n // of requests it will run at the same time, which is what the\n // measurement in docs/LOCAL-FLEET.md showed. An untrusted or absent\n // reading claims nothing — `null`, not `true`.\n capable: refusal !== null ? false : trusted && total !== null ? true : null,\n // The runtime's own words when it refuses, carried verbatim as evidence.\n refusal,\n slots: total,\n },\n reason: str(r, 'lastError'),\n supervised: bool(r, 'managed') ?? false,\n sampledAt: str(r, 'checkedAt'),\n };\n}\n\n/** A displayable model name, or null for an absent or digest-shaped one. */\nfunction humaneModelName(raw: string | null): string | null {\n if (raw === null || raw.length === 0) return null;\n const base = raw.split('/').pop() ?? raw;\n if (base.length === 0) return null;\n if (/^sha256[-:][0-9a-f]{16,}$/i.test(base)) return null;\n return base;\n}\n\nexport function projectServingRuntime(raw: unknown): ServingRuntimeSnapshot | null {\n const r = record(raw);\n if (!r) return null;\n // A body already in this surface's own shape wins; otherwise try owner R's.\n if (!('kind' in r) && 'slots' in r && 'state' in r) {\n const llama = projectLlamaShape(r);\n if (llama !== null) return llama;\n }\n return {\n kind: oneOf(r, 'kind', RUNTIME_KINDS, 'unknown'),\n state: oneOf(r, 'state', RUNTIME_STATES, 'unknown'),\n endpoint: str(r, 'endpoint'),\n model: str(r, 'model'),\n slotsTotal: count(r, 'slotsTotal'),\n slotsBusy: count(r, 'slotsBusy'),\n contextTokens: count(r, 'contextTokens'),\n startedAt: str(r, 'startedAt'),\n parallel: projectParallelism(r['parallel']),\n reason: str(r, 'reason'),\n // A runtime this server cannot supervise must not be offered buttons, so\n // an unreported flag reads as NOT supervised. The conservative default is\n // \"no controls\" rather than controls that 404 on the first click.\n supervised: bool(r, 'supervised') ?? false,\n sampledAt: str(r, 'sampledAt'),\n };\n}\n\nfunction projectAgent(raw: unknown): FleetAgent | null {\n const r = record(raw);\n if (!r) return null;\n const id = str(r, 'id');\n if (id === null) return null;\n return {\n id,\n task: str(r, 'task'),\n repo: str(r, 'repo'),\n engine: str(r, 'engine'),\n model: str(r, 'model'),\n state: oneOf(r, 'state', AGENT_STATES, 'running'),\n startedAt: str(r, 'startedAt'),\n slot: count(r, 'slot'),\n };\n}\n\nexport function projectFleet(raw: unknown): FleetSnapshot | null {\n const r = record(raw);\n if (!r) return null;\n const rawAgents = Array.isArray(r['agents']) ? (r['agents'] as unknown[]) : [];\n return {\n agents: rawAgents.map(projectAgent).filter((a): a is FleetAgent => a !== null),\n queueDepth: count(r, 'queueDepth'),\n slotsTotal: count(r, 'slotsTotal'),\n slotsBusy: count(r, 'slotsBusy'),\n notes: strings(r, 'notes'),\n sampledAt: str(r, 'sampledAt'),\n };\n}\n\nfunction projectRefusal(raw: unknown): LocalOnlyRefusal | null {\n const r = record(raw);\n if (!r) return null;\n const engine = str(r, 'engine');\n if (engine === null) return null;\n return { engine, reason: str(r, 'reason') ?? 'The dispatcher refuses this engine while local-only is on.' };\n}\n\n/**\n * `source` is carried through as the resolver's own value — it is no longer\n * flattened onto a smaller union here.\n *\n * The flattening this replaced mapped `'latch'` onto `'config'`, which tells\n * an operator the mode is stored in the config and editable when in fact the\n * process has pinned it for its own lifetime. Only the separate `mutable`\n * flag kept that from reaching the screen as a working switch that silently\n * does nothing. An unrecognised value lands on `'unknown'`, which makes no\n * claim at all.\n */\n/** Sources that pin the mode to this server process, whatever the config says. */\nconst PINNED_SOURCES: ReadonlySet<LocalOnlySource> = new Set<LocalOnlySource>([\n 'env',\n 'config+env',\n 'latch',\n 'unknown',\n]);\n\nexport function projectLocalOnly(raw: unknown): LocalOnlyPolicy | null {\n const r = record(raw);\n if (!r) return null;\n const rawRefusals = Array.isArray(r['refuses']) ? (r['refuses'] as unknown[]) : [];\n const source = oneOf(r, 'source', LOCAL_ONLY_SOURCES, 'unknown');\n return {\n // An unreported `enabled` is NOT \"on\": claiming cloud is unreachable when\n // the server never said so is the one error this policy cannot afford.\n enabled: bool(r, 'enabled') ?? false,\n source,\n refuses: rawRefusals.map(projectRefusal).filter((x): x is LocalOnlyRefusal => x !== null),\n // A policy pinned by the server's own process — an env var, or a latch\n // this process already took — is never editable from here, whatever the\n // body claims: a write would be silently overridden on the next read.\n // `'unknown'` is treated as pinned too, because offering a switch for a\n // policy this client cannot identify is the more expensive mistake.\n mutable: PINNED_SOURCES.has(source) ? false : (bool(r, 'mutable') ?? false),\n detail: str(r, 'detail'),\n sampledAt: str(r, 'sampledAt') ?? str(r, 'checkedAt'),\n };\n}\n\n// ---------------------------------------------------------------------------\n// The capacity verdict — the point of this whole file\n// ---------------------------------------------------------------------------\n\nexport type CapacityVerdict = 'batched' | 'serialized' | 'unknown' | 'offline';\n\nexport interface RuntimeCapacity {\n verdict: CapacityVerdict;\n /**\n * How many agents can genuinely be in flight at once. `null` means it\n * cannot be known from this reading — which is NOT the same as 0 and must\n * never render as a number.\n */\n effectiveConcurrency: number | null;\n /** What the runtime was configured with, honoured or not. */\n configuredSlots: number | null;\n /**\n * True when the configured slot count is larger than what the runtime will\n * actually run in parallel. While this is true, the configured number must\n * not be presented as capacity anywhere on the screen.\n */\n overstated: boolean;\n headline: string;\n detail: string;\n tone: 'success' | 'warning' | 'danger' | 'neutral' | 'unknown';\n}\n\n/** Human name for a runtime, used in sentences. */\nexport function runtimeLabel(kind: ServingRuntimeKind): string {\n switch (kind) {\n case 'llama-server':\n return 'llama-server';\n case 'ollama':\n return 'Ollama';\n case 'vllm':\n return 'vLLM';\n default:\n return 'the serving runtime';\n }\n}\n\nexport function runtimeCapacity(runtime: ServingRuntimeSnapshot | null): RuntimeCapacity {\n if (runtime === null) {\n return {\n verdict: 'unknown',\n effectiveConcurrency: null,\n configuredSlots: null,\n overstated: false,\n headline: 'No serving runtime reported',\n detail:\n 'Nothing answered for the serving runtime, so how many agents can run at once is unknown. That is a missing reading, not an idle machine.',\n tone: 'unknown',\n };\n }\n\n const configured = runtime.slotsTotal ?? runtime.parallel.slots;\n const name = runtimeLabel(runtime.kind);\n\n if (runtime.state !== 'running') {\n return {\n verdict: 'offline',\n effectiveConcurrency: null,\n configuredSlots: configured,\n overstated: false,\n headline: runtime.state === 'stopped' ? 'Not serving' : `Serving runtime is ${runtime.state}`,\n detail:\n runtime.state === 'stopped'\n ? `${name} is not running, so no local agent can take a turn. Start it before dispatching a fleet.`\n : `${name} is ${runtime.state}; capacity is not settled until it reports running.`,\n tone: runtime.state === 'stopped' ? 'neutral' : 'warning',\n };\n }\n\n if (runtime.parallel.capable === false) {\n // The measured case. The configured number is real — it is simply not a\n // concurrency, and printing it as one is the failure mode this exists for.\n const overstated = configured !== null && configured > 1;\n return {\n verdict: 'serialized',\n effectiveConcurrency: 1,\n configuredSlots: configured,\n overstated,\n headline: `${name} serializes requests`,\n detail: overstated\n ? `${name} has ${configured} slots configured but runs one request at a time for this model, so the real capacity is one agent. The other ${configured - 1} would queue, and a queue looks like slowness rather than a wait.`\n : `${name} runs one request at a time for this model. A second agent waits for the first to finish.`,\n tone: 'danger',\n };\n }\n\n if (runtime.parallel.capable === null) {\n return {\n verdict: 'unknown',\n effectiveConcurrency: null,\n configuredSlots: configured,\n overstated: false,\n headline: 'Batching not confirmed',\n detail: `${name} is running${\n configured === null ? '' : ` with ${configured} slots configured`\n }, but it has not reported whether it batches concurrent requests for this model. Until it does, this panel will not quote a concurrency it cannot stand behind.`,\n tone: 'unknown',\n };\n }\n\n if (configured === null) {\n return {\n verdict: 'batched',\n effectiveConcurrency: null,\n configuredSlots: null,\n overstated: false,\n headline: `${name} batches concurrent requests`,\n detail: `${name} batches, but reported no slot count, so the ceiling on concurrent agents is unknown.`,\n tone: 'unknown',\n };\n }\n\n return {\n verdict: 'batched',\n effectiveConcurrency: configured,\n configuredSlots: configured,\n overstated: false,\n headline: `${configured} agent${configured === 1 ? '' : 's'} in parallel`,\n detail: `${name} batches across ${configured} slot${\n configured === 1 ? '' : 's'\n } sharing one copy of the weights, so ${configured} agent${\n configured === 1 ? '' : 's'\n } can be mid-turn at once.`,\n tone: 'success',\n };\n}\n\n/**\n * The measured comparison, kept as data so the panel renders it as evidence\n * rather than as an assertion in prose. Source: docs/LOCAL-FLEET.md, four\n * concurrent requests against the same Qwen3.8 GGUF on this machine.\n */\nexport interface ConcurrencyEvidenceRow {\n runtime: string;\n /** Per-request completion times, in seconds, in the order they returned. */\n perRequestSeconds: readonly number[];\n wallSeconds: number;\n verdict: string;\n}\n\nexport const CONCURRENCY_EVIDENCE: readonly ConcurrencyEvidenceRow[] = [\n {\n runtime: 'Ollama',\n perRequestSeconds: [3.7, 7.6, 11.4, 15.2],\n wallSeconds: 15.2,\n verdict: 'staggered — the requests queued',\n },\n {\n runtime: 'llama-server',\n perRequestSeconds: [8.6, 8.9, 9.0, 9.0],\n wallSeconds: 9.1,\n verdict: 'together — the requests batched',\n },\n] as const;\n\n// ---------------------------------------------------------------------------\n// Utilisation\n// ---------------------------------------------------------------------------\n\nexport interface SlotUtilisation {\n busy: number | null;\n total: number | null;\n /** 0–100, or null when either half is unknown. */\n percent: number | null;\n /** Every slot is taken: the next turn waits. */\n saturated: boolean;\n /** One line, safe to render on its own. */\n label: string;\n}\n\n/**\n * Slot occupancy.\n *\n * `total` is the EFFECTIVE concurrency, not the configured slot count — on a\n * runtime that serializes, one busy slot out of four configured is full, and\n * drawing it as 25% would tell the operator there is room when there is none.\n */\nexport function slotUtilisation(busy: number | null, effectiveTotal: number | null): SlotUtilisation {\n if (busy === null || effectiveTotal === null || effectiveTotal <= 0) {\n return {\n busy,\n total: effectiveTotal,\n percent: null,\n saturated: false,\n label:\n busy === null\n ? 'Slot occupancy is not reported.'\n : `${busy} in flight; the number of slots is not known, so occupancy cannot be drawn.`,\n };\n }\n const clamped = Math.min(busy, effectiveTotal);\n const percent = Math.round((clamped / effectiveTotal) * 100);\n const saturated = busy >= effectiveTotal;\n return {\n busy,\n total: effectiveTotal,\n percent,\n saturated,\n label: saturated\n ? `All ${effectiveTotal} slot${effectiveTotal === 1 ? '' : 's'} busy — new turns wait for one to free up.`\n : `${busy} of ${effectiveTotal} slot${effectiveTotal === 1 ? '' : 's'} busy.`,\n };\n}\n\n/**\n * What the operator is actually waiting on.\n *\n * Saturation alone is not bad news — a fleet at full occupancy is a fleet\n * working. Saturation WITH a queue is the thing to say plainly, because from\n * the outside it is indistinguishable from the machine being slow.\n */\nexport interface FleetPressure {\n state: 'idle' | 'working' | 'saturated' | 'queued' | 'unknown';\n headline: string;\n detail: string;\n tone: 'success' | 'running' | 'warning' | 'neutral' | 'unknown';\n}\n\nexport function fleetPressure(\n fleet: FleetSnapshot | null,\n utilisation: SlotUtilisation,\n capacity: RuntimeCapacity,\n): FleetPressure {\n if (fleet === null) {\n return {\n state: 'unknown',\n headline: 'Fleet not reported',\n detail:\n 'No fleet snapshot answered, so what is in flight cannot be listed. That is a missing reading, not an empty fleet.',\n tone: 'unknown',\n };\n }\n\n const running = fleet.agents.filter((a) => a.state !== 'queued').length;\n const queued = fleet.queueDepth ?? fleet.agents.filter((a) => a.state === 'queued').length;\n\n if (queued > 0) {\n return {\n state: 'queued',\n headline: `${queued} waiting for a slot`,\n detail:\n capacity.verdict === 'serialized'\n ? `${running} turn${running === 1 ? '' : 's'} in flight and ${queued} queued. This runtime serializes, so the queue drains one at a time — the wait is a wait, not slow inference.`\n : `${running} turn${running === 1 ? '' : 's'} in flight and ${queued} queued behind ${\n utilisation.total === null ? 'the available slots' : `${utilisation.total} slots`\n }. Queued turns have not started; their wall time is queueing, not work.`,\n tone: 'warning',\n };\n }\n\n if (running === 0) {\n return {\n state: 'idle',\n headline: 'Nothing in flight',\n detail: 'No agent is mid-turn. The fleet is idle, not stuck.',\n tone: 'neutral',\n };\n }\n\n if (utilisation.saturated) {\n return {\n state: 'saturated',\n headline: `All slots busy · ${running} in flight`,\n detail: `Every slot is working and nothing is queued behind them. The next turn will wait, but none is waiting yet.`,\n tone: 'running',\n };\n }\n\n return {\n state: 'working',\n headline: `${running} in flight`,\n detail: utilisation.label,\n tone: 'running',\n };\n}\n\n// ---------------------------------------------------------------------------\n// Local-only impact\n// ---------------------------------------------------------------------------\n\nexport interface SeatLike {\n id: string;\n engine: string;\n label: string;\n}\n\nexport interface LocalOnlyImpact {\n /** Seats that become unreachable while the policy is on. */\n blocked: SeatLike[];\n /** Seats unaffected — the local ones. */\n allowed: SeatLike[];\n /** The named refusal per blocked engine, from the server when it sent one. */\n refusalFor: (engine: string) => string;\n /** One line summarising the blast radius against the CURRENT seat roster. */\n summary: string;\n}\n\nconst DEFAULT_REFUSAL = 'Refused: local-only is on and this engine is not local.';\n\n/**\n * What local-only would block, measured against the seats that actually exist\n * on this machine right now.\n *\n * Deliberately computed from the live roster rather than from a fixed list of\n * cloud engines: the sentence \"this would block 3 of your 5 seats\" is only\n * trustworthy if the 5 is the operator's own number.\n */\nexport function localOnlyImpact(\n policy: LocalOnlyPolicy | null,\n seats: readonly SeatLike[],\n): LocalOnlyImpact {\n const blocked = seats.filter((s) => s.engine !== 'local');\n const allowed = seats.filter((s) => s.engine === 'local');\n const byEngine = new Map<string, string>();\n for (const refusal of policy?.refuses ?? []) byEngine.set(refusal.engine, refusal.reason);\n\n const summary =\n seats.length === 0\n ? 'No seats are configured, so there is nothing for this policy to block yet.'\n : blocked.length === 0\n ? `Every one of your ${seats.length} seat${seats.length === 1 ? '' : 's'} is local, so this policy would block nothing today.`\n : `${blocked.length} of ${seats.length} seat${seats.length === 1 ? '' : 's'} would become unreachable; ${allowed.length} local seat${allowed.length === 1 ? '' : 's'} would keep working.`;\n\n return {\n blocked,\n allowed,\n refusalFor: (engine: string) => byEngine.get(engine) ?? DEFAULT_REFUSAL,\n summary,\n };\n}\n\n/** Where the policy came from, as a sentence an operator can act on. */\nexport function localOnlySourceNote(policy: LocalOnlyPolicy | null): string | null {\n if (policy === null) return null;\n // The policy's own sentence, when it wrote one. It knows things this client\n // does not — e.g. that an env var tried and failed to clear a persisted\n // local-only — and a template would erase exactly that.\n if (policy.detail !== null) return policy.detail;\n switch (policy.source) {\n case 'env':\n case 'config+env':\n return 'This policy is pinned by an environment variable on the server process, so it cannot be changed from here — restart the server without it to take the switch back.';\n case 'latch':\n return 'This process already observed a persisted local-only and has pinned it for its own lifetime, so turning it off takes effect for new dispatches but not for the seams that latched. Restart the server to clear it.';\n case 'config':\n return 'Stored in the hub config and applied on the next dispatch.';\n case 'off':\n return 'Not configured; cloud engines are reachable.';\n default:\n return 'The server did not say where this setting came from.';\n }\n}\n\n// ---------------------------------------------------------------------------\n// Small formatters\n// ---------------------------------------------------------------------------\n\n/** \"4m 12s\" since an ISO instant, against a live clock. Absent stays absent. */\nexport function elapsedSince(iso: string | null, now: number): string {\n if (!iso) return UNKNOWN;\n const at = Date.parse(iso);\n if (Number.isNaN(at)) return UNKNOWN;\n return formatDuration(Math.max(0, now - at));\n}\n\n/** Uptime for the runtime header. Same rules, different words. */\nexport function formatUptime(startedAt: string | null, now: number): string {\n if (!startedAt) return UNKNOWN;\n const at = Date.parse(startedAt);\n if (Number.isNaN(at)) return UNKNOWN;\n return `up ${formatDuration(Math.max(0, now - at))}`;\n}\n\n/**\n * Context windows are quoted in powers of two everywhere the operator has\n * seen them (65536 is \"64k\"), so the divisor is 1024. Matching\n * `usage/local-model.ts`'s `formatContext` on purpose: two spellings of the\n * same number across two panels is how a UI starts to feel untrustworthy.\n */\nexport function formatContextTokens(tokens: number | null): string {\n if (tokens === null || !Number.isFinite(tokens)) return UNKNOWN;\n if (tokens >= 1024) return `${Math.round(tokens / 1024)}k`;\n return String(tokens);\n}\n\n/** Agent rows, ordered so the reader's first question is answered first. */\nexport function orderAgents(agents: readonly FleetAgent[]): FleetAgent[] {\n const rank: Record<FleetAgentState, number> = { running: 0, finishing: 1, queued: 2 };\n return [...agents].sort((a, b) => {\n const byState = rank[a.state] - rank[b.state];\n if (byState !== 0) return byState;\n // Longest-running first: the one most likely to be stuck is the one an\n // operator scanning this table is looking for.\n const at = a.startedAt ? Date.parse(a.startedAt) : Number.NaN;\n const bt = b.startedAt ? Date.parse(b.startedAt) : Number.NaN;\n if (Number.isNaN(at) && Number.isNaN(bt)) return a.id.localeCompare(b.id);\n if (Number.isNaN(at)) return 1;\n if (Number.isNaN(bt)) return -1;\n return at - bt;\n });\n}\n"],"names":["record","value","str","source","key","v","num","count","bool","strings","entry","RUNTIME_KINDS","RUNTIME_STATES","AGENT_STATES","LOCAL_ONLY_SOURCES","oneOf","allowed","fallback","projectParallelism","raw","r","LLAMA_STATE","projectLlamaShape","rawState","slots","host","port","configured","busy","slotSource","trusted","total","refusal","identity","contextPerSlot","contextTotal","humaneModelName","base","projectServingRuntime","llama","projectAgent","id","projectFleet","a","projectRefusal","engine","PINNED_SOURCES","projectLocalOnly","rawRefusals","x","runtimeLabel","kind","runtimeCapacity","runtime","name","overstated","CONCURRENCY_EVIDENCE","slotUtilisation","effectiveTotal","clamped","percent","saturated","fleetPressure","fleet","utilisation","capacity","running","queued","DEFAULT_REFUSAL","localOnlyImpact","policy","seats","blocked","s","byEngine","summary","localOnlySourceNote","elapsedSince","iso","now","UNKNOWN","at","formatDuration","formatUptime","startedAt","formatContextTokens","tokens","orderAgents","agents","rank","b","byState","bt"],"mappings":"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"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
const g="—";function a(t){return typeof t!="number"||!Number.isFinite(t)?"—":`$${t.toFixed(2)}`}function p(t){return typeof t!="number"||!Number.isFinite(t)?"—":String(t)}function l(t){const e=Math.max(0,Math.round(t/1e3));if(e<60)return`${e}s`;const n=Math.floor(e/60),o=e%60;return n<60?`${n}m ${String(o).padStart(2,"0")}s`:`${Math.floor(n/60)}h ${String(n%60).padStart(2,"0")}m`}function f(t){if(!t)return"—";const e=Date.parse(t);if(Number.isNaN(e))return"—";const n=Math.max(0,Math.round((Date.now()-e)/1e3));if(n<60)return`${n}s`;const o=Math.round(n/60);if(o<60)return`${o}m`;const r=Math.round(o/60);return r<24?`${r}h`:`${Math.round(r/24)}d`}function h(t){const e=f(t);return e==="—"?"—":`${e} ago`}function $(t){if(!t)return"—";const e=Date.parse(t);return Number.isNaN(e)?"—":new Date(e).toLocaleTimeString(void 0,{hour:"2-digit",minute:"2-digit",second:"2-digit"})}function c(t=new Date){const e=t.getFullYear(),n=String(t.getMonth()+1).padStart(2,"0"),o=String(t.getDate()).padStart(2,"0");return`${e}-${n}-${o}`}function m(t,e=new Date){return t===c(e)||t===e.toISOString().slice(0,10)}function y(t,e=new Date){if(!t)return"—";const n=Date.parse(t);if(Number.isNaN(n))return"—";const o=new Date(n),r=o.toLocaleTimeString(void 0,{hour:"2-digit",minute:"2-digit",second:"2-digit"});return c(o)===c(e)?r:`${o.toLocaleDateString(void 0,{month:"short",day:"numeric"})} ${r}`}function w(t){return typeof t!="number"||!Number.isFinite(t)||t<=0?"—":`every ${l(t).replace(/ 00[ms]$/,"")}`}const b=70;function O(t,e,n,o=new Date){const r=typeof t=="number"&&Number.isFinite(t)?Math.max(0,t):null,u=typeof e=="number"&&Number.isFinite(e)?e:null,N=typeof n=="string"&&!m(n,o);if(u===0)return{state:"stopped",percent:100,label:`${a(r)} spent today · budget $0.00`,note:"Daily budget is 0 — the loop is stopped, not unlimited."};if(N)return{state:"unknown",percent:null,label:`— spent today · budget ${a(u)}`,note:`Nothing has been recorded today — the last ledger day is ${n}.`};if(u===null||r===null)return{state:"unknown",percent:null,label:u===null?`${a(r)} spent today · budget —`:`— spent today · budget ${a(u)}`,note:u===null?"Daily budget is not configured.":"Today's spend is not observable right now."};const i=r/u*100,d=Math.min(100,Math.round(i)),s=i>=100?"over":i>=b?"warn":"ok";return{state:s,percent:d,label:`${a(r)} of ${a(u)} today · ${d}%`,note:s==="over"?"Daily budget reached — the loop idles until the date rolls over.":s==="warn"?`${a(u-r)} left before the loop idles today.`:`${a(u-r)} left today.`}}function D(t,e){if(!t)return null;const n=Date.parse(t);return Number.isNaN(n)||typeof e!="number"||!Number.isFinite(e)||e<=0?null:n+e}function K(t,e){if(t===null)return"—";const n=t-e;return n>1e3?`in ${l(n)}`:n>-1e3?"due now":`overdue by ${l(-n)}`}function W(t){if(!t)return{label:"no tick recorded",tone:"unknown"};switch(t){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:t,tone:"neutral"}}}export{g as U,f as a,y as b,p as c,W as d,a as e,h as f,O as g,$ as h,K as i,w as j,m as k,l,D as n};
|
|
2
|
-
//# sourceMappingURL=format-fggwu_Rs.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"format-fggwu_Rs.js","sources":["../../../../../../src/web-ui/routes/verse/autonomy/format.ts"],"sourcesContent":["/**\n * routes/verse/autonomy/format.ts — the small pure functions the cockpit's\n * numerals go through. Kept framework-free so they unit-test without a DOM,\n * and kept in ONE place so \"unknown\" never renders as 0 in one panel and as\n * an em-dash in the next.\n *\n * The rule every function here obeys: absent is not zero. A null spend is\n * \"—\", a null budget is \"not configured\", and a budget of exactly 0 is\n * \"stopped\" — which is the single most misread number in the whole product.\n */\n\n/** What we render when a value genuinely is not known. */\nexport const UNKNOWN = '—';\n\nexport function formatUsd(value: number | null | undefined): string {\n if (typeof value !== 'number' || !Number.isFinite(value)) return UNKNOWN;\n return `$${value.toFixed(2)}`;\n}\n\nexport function formatCount(value: number | null | undefined): string {\n if (typeof value !== 'number' || !Number.isFinite(value)) return UNKNOWN;\n return String(value);\n}\n\n/** \"4m 12s\" / \"1h 06m\" / \"12s\" — for durations and countdowns alike. */\nexport function formatDuration(ms: number): string {\n const total = Math.max(0, Math.round(ms / 1000));\n if (total < 60) return `${total}s`;\n const minutes = Math.floor(total / 60);\n const seconds = total % 60;\n if (minutes < 60) return `${minutes}m ${String(seconds).padStart(2, '0')}s`;\n const hours = Math.floor(minutes / 60);\n return `${hours}h ${String(minutes % 60).padStart(2, '0')}m`;\n}\n\n/** Compact age for dense table cells: \"42s\", \"7m\", \"3h\", \"5d\". */\nexport function formatAge(iso: string | null | undefined): string {\n if (!iso) return UNKNOWN;\n const ms = Date.parse(iso);\n if (Number.isNaN(ms)) return UNKNOWN;\n const seconds = Math.max(0, Math.round((Date.now() - ms) / 1000));\n if (seconds < 60) return `${seconds}s`;\n const minutes = Math.round(seconds / 60);\n if (minutes < 60) return `${minutes}m`;\n const hours = Math.round(minutes / 60);\n if (hours < 24) return `${hours}h`;\n return `${Math.round(hours / 24)}d`;\n}\n\nexport function formatRelative(iso: string | null | undefined): string {\n const age = formatAge(iso);\n return age === UNKNOWN ? UNKNOWN : `${age} ago`;\n}\n\n/** Local wall-clock time, for the audit table's second column. */\nexport function formatClock(iso: string | null | undefined): string {\n if (!iso) return UNKNOWN;\n const ms = Date.parse(iso);\n if (Number.isNaN(ms)) return UNKNOWN;\n return new Date(ms).toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', second: '2-digit' });\n}\n\n/** The viewer's local calendar day as `YYYY-MM-DD` — NOT the UTC slice. */\nexport function localDateKey(at: Date = new Date()): string {\n const y = at.getFullYear();\n const m = String(at.getMonth() + 1).padStart(2, '0');\n const d = String(at.getDate()).padStart(2, '0');\n return `${y}-${m}-${d}`;\n}\n\n/**\n * Is `todayDate` still the current ledger day?\n *\n * The daemon stamps it in UTC (`resetDayIfNeeded` in core/daemon/state.ts is\n * `new Date().toISOString().slice(0, 10)`) but the operator reads the screen\n * in local time, and the two disagree for part of every day at any offset but\n * zero. Comparing against the local day alone would announce \"nothing has been\n * recorded today\" every evening west of UTC — a false alarm on the meter whose\n * whole purpose is to be trustworthy.\n *\n * So either spelling counts as current. That is deliberately conservative: it\n * can call a day current for a few hours after the UTC rollover, and it will\n * never call a genuinely current day stale. The case this exists for is a\n * ledger day weeks old, which neither spelling matches.\n */\nexport function isCurrentLedgerDay(todayDate: string, now: Date = new Date()): boolean {\n return todayDate === localDateKey(now) || todayDate === now.toISOString().slice(0, 10);\n}\n\n/**\n * Time-of-day for a row from today; date + time for anything older.\n *\n * `formatClock` alone is only honest inside a single-day window, and the\n * tables that use it are not one: the audit trail defaults to 200 rows and\n * offers 500, which on a real machine spans days. Two rows reading \"03:12:44\"\n * could be last night and last week, and the only date was in a hover title\n * that a touch viewport never fires.\n */\nexport function formatStamp(iso: string | null | undefined, now: Date = new Date()): string {\n if (!iso) return UNKNOWN;\n const ms = Date.parse(iso);\n if (Number.isNaN(ms)) return UNKNOWN;\n const at = new Date(ms);\n const clock = at.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', second: '2-digit' });\n if (localDateKey(at) === localDateKey(now)) return clock;\n const day = at.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });\n return `${day} ${clock}`;\n}\n\n/** Human tick interval, e.g. \"every 15m\". */\nexport function formatInterval(intervalMs: number | null | undefined): string {\n if (typeof intervalMs !== 'number' || !Number.isFinite(intervalMs) || intervalMs <= 0) return UNKNOWN;\n // A configured interval is a round number far more often than not; \"every\n // 15m\" reads better than \"every 15m 00s\" and loses nothing when it isn't.\n return `every ${formatDuration(intervalMs).replace(/ 00[ms]$/, '')}`;\n}\n\nexport type BudgetState = 'stopped' | 'unknown' | 'ok' | 'warn' | 'over';\n\nexport interface BudgetMeter {\n state: BudgetState;\n /** 0-100, clamped. Null when there is nothing honest to draw. */\n percent: number | null;\n /** One line: \"$4.50 of $25.00 today · 18%\". */\n label: string;\n /** The plain-language verdict shown next to the meter. */\n note: string;\n}\n\nconst BUDGET_WARN_PERCENT = 70;\n\n/**\n * Today's spend against the configured daily cap, as ONE line.\n *\n * A cap of exactly 0 is the contract's guard rail: it means the loop is\n * stopped, and it must never render as an empty (i.e. \"plenty left\") meter.\n *\n * `todayDate` is the ledger day `spend.todayUsd` was counted for. It is NOT\n * optional decoration: the daemon writes the figure once per day and leaves it\n * there, so a machine that last ticked three weeks ago answers\n * `{todayUsd: 0, todayDate: \"2026-09-01\"}`. Rendering that as \"$0.00 of $50.00\n * today\" is a fabricated statement about today on the one number an operator\n * checks before walking away from an autonomous loop. Pass `null` only when\n * the source genuinely has no date to offer (the legacy daemon observation).\n */\nexport function budgetMeter(\n spentUsd: number | null | undefined,\n capUsd: number | null | undefined,\n todayDate?: string | null,\n now: Date = new Date(),\n): BudgetMeter {\n const spend = typeof spentUsd === 'number' && Number.isFinite(spentUsd) ? Math.max(0, spentUsd) : null;\n const cap = typeof capUsd === 'number' && Number.isFinite(capUsd) ? capUsd : null;\n // A cap of 0 is a statement about configuration, not about the ledger, so\n // it still reads true on a stale day and is checked first.\n const stale = typeof todayDate === 'string' && !isCurrentLedgerDay(todayDate, now);\n\n if (cap === 0) {\n return {\n state: 'stopped',\n percent: 100,\n label: `${formatUsd(spend)} spent today · budget $0.00`,\n note: 'Daily budget is 0 — the loop is stopped, not unlimited.',\n };\n }\n if (stale) {\n return {\n state: 'unknown',\n percent: null,\n label: `${UNKNOWN} spent today · budget ${formatUsd(cap)}`,\n note: `Nothing has been recorded today — the last ledger day is ${todayDate}.`,\n };\n }\n if (cap === null || spend === null) {\n return {\n state: 'unknown',\n percent: null,\n label: cap === null ? `${formatUsd(spend)} spent today · budget ${UNKNOWN}` : `${UNKNOWN} spent today · budget ${formatUsd(cap)}`,\n note: cap === null ? 'Daily budget is not configured.' : \"Today's spend is not observable right now.\",\n };\n }\n\n const raw = (spend / cap) * 100;\n const percent = Math.min(100, Math.round(raw));\n const state: BudgetState = raw >= 100 ? 'over' : raw >= BUDGET_WARN_PERCENT ? 'warn' : 'ok';\n return {\n state,\n percent,\n label: `${formatUsd(spend)} of ${formatUsd(cap)} today · ${percent}%`,\n note:\n state === 'over'\n ? 'Daily budget reached — the loop idles until the date rolls over.'\n : state === 'warn'\n ? `${formatUsd(cap - spend)} left before the loop idles today.`\n : `${formatUsd(cap - spend)} left today.`,\n };\n}\n\n/** Epoch ms of the next expected tick, or null when it cannot be derived. */\nexport function nextTickAt(lastTickAt: string | null | undefined, intervalMs: number | null | undefined): number | null {\n if (!lastTickAt) return null;\n const last = Date.parse(lastTickAt);\n if (Number.isNaN(last)) return null;\n if (typeof intervalMs !== 'number' || !Number.isFinite(intervalMs) || intervalMs <= 0) return null;\n return last + intervalMs;\n}\n\n/** \"in 4m 12s\" / \"due now\" / \"overdue by 2m 03s\". */\nexport function countdownLabel(nextAt: number | null, now: number): string {\n if (nextAt === null) return UNKNOWN;\n const delta = nextAt - now;\n if (delta > 1000) return `in ${formatDuration(delta)}`;\n if (delta > -1000) return 'due now';\n return `overdue by ${formatDuration(-delta)}`;\n}\n\n/** Last tick outcome, phrased the way a person would say it out loud. */\nexport function describeTickOutcome(reason: string | null | undefined): { label: string; tone: 'success' | 'warning' | 'danger' | 'neutral' | 'unknown' } {\n if (!reason) return { label: 'no tick recorded', tone: 'unknown' };\n switch (reason) {\n case 'ok':\n return { label: 'ok', tone: 'success' };\n case 'kill-switch':\n return { label: 'refused — emergency stop engaged', tone: 'danger' };\n case 'budget-exhausted':\n return { label: 'stopped — daily budget spent', tone: 'warning' };\n case 'no-enrolled-repos':\n return { label: 'nothing to do — no repos enrolled', tone: 'warning' };\n case 'no-backlog':\n return { label: 'nothing to do — backlog empty', tone: 'neutral' };\n case 'dry-run':\n return { label: 'dry run', tone: 'neutral' };\n default:\n return { label: reason, tone: 'neutral' };\n }\n}\n"],"names":["UNKNOWN","formatUsd","value","formatCount","formatDuration","ms","total","minutes","seconds","formatAge","iso","hours","formatRelative","age","formatClock","localDateKey","at","y","m","d","isCurrentLedgerDay","todayDate","now","formatStamp","clock","formatInterval","intervalMs","BUDGET_WARN_PERCENT","budgetMeter","spentUsd","capUsd","spend","cap","stale","raw","percent","state","nextTickAt","lastTickAt","last","countdownLabel","nextAt","delta","describeTickOutcome","reason"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
._wrap_14qw9_1{min-height:100%;display:flex;align-items:center;justify-content:center;padding:var(--space-6);background:var(--bg-canvas)}._card_14qw9_10{width:min(480px,100%);background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-xl);box-shadow:var(--shadow-lg);padding:var(--space-8)}._badge_14qw9_19{font-size:28px;margin-bottom:var(--space-4)}._heading_14qw9_24{font-size:var(--text-2xl-size);line-height:var(--text-2xl-line);font-weight:var(--font-weight-semibold);margin-bottom:var(--space-3)}._body_14qw9_31{font-size:var(--text-sm-size);line-height:var(--text-sm-line);color:var(--text-secondary);margin-bottom:var(--space-3)}._code_14qw9_38{font-family:var(--font-mono);font-size:var(--text-sm-size);background:var(--bg-code);border:1px solid var(--border-subtle);border-radius:var(--radius-sm);padding:var(--space-3);margin-bottom:var(--space-3);overflow-x:auto}._example_14qw9_49{font-family:var(--font-mono);background:var(--bg-code);padding:1px 4px;border-radius:var(--radius-xs)}._form_14qw9_56{display:flex;flex-direction:column;gap:var(--space-2);margin-top:var(--space-5)}._label_14qw9_63{font-size:var(--text-sm-size);font-weight:var(--font-weight-medium)}._input_14qw9_68{padding:var(--space-3);border-radius:var(--radius-md);border:1px solid var(--border-default);background:var(--bg-input);font-family:var(--font-mono);font-size:var(--text-sm-size)}._input_14qw9_68:focus-visible{border-color:var(--border-focus);box-shadow:var(--shadow-focus-ring)}._error_14qw9_82{color:var(--status-danger-fg);font-size:var(--text-sm-size)}._submit_14qw9_87{margin-top:var(--space-2);padding:var(--space-3) var(--space-4);background:var(--accent-500);color:var(--text-on-accent);border-radius:var(--radius-md);font-weight:var(--font-weight-medium);text-align:center;transition:background var(--duration-fast) var(--ease-standard)}._submit_14qw9_87:hover:not(:disabled){background:var(--accent-600)}._submit_14qw9_87:disabled{opacity:.5;cursor:not-allowed}._optional_14qw9_107{font-weight:var(--font-weight-normal, 400);color:var(--text-tertiary)}._skipLink_1uply_1{position:fixed;top:-100px;left:var(--space-3);z-index:var(--z-toast);background:var(--accent-500);color:var(--text-on-accent);padding:var(--space-2) var(--space-4);border-radius:var(--radius-md);font-size:var(--text-sm-size);font-weight:var(--font-weight-medium);transition:top var(--duration-fast) var(--ease-standard)}._skipLink_1uply_1:focus-visible{top:var(--space-3);box-shadow:var(--shadow-focus-ring)}:root{color-scheme:light;--gray-0: #ffffff;--gray-50: #fafafa;--gray-100: #f4f4f5;--gray-200: #e8e8ea;--gray-300: #d6d6da;--gray-400: #a1a1aa;--gray-500: #71717a;--gray-600: #52525b;--gray-700: #3f3f46;--gray-800: #27272a;--gray-900: #18181b;--gray-950: #09090b;--accent-h: 245;--accent-s: 72%;--accent-l: 58%;--accent-l-shift: 0%;--accent-l-resolved: calc(var(--accent-l) + var(--accent-l-shift));--accent-50: hsl(var(--accent-h) var(--accent-s) 96%);--accent-100: hsl(var(--accent-h) var(--accent-s) 92%);--accent-300: hsl(var(--accent-h) var(--accent-s) calc(var(--accent-l-resolved) + 14%));--accent-500: hsl(var(--accent-h) var(--accent-s) var(--accent-l-resolved));--accent-600: hsl(var(--accent-h) var(--accent-s) calc(var(--accent-l-resolved) - 8%));--accent-700: hsl(var(--accent-h) var(--accent-s) calc(var(--accent-l-resolved) - 16%));--accent-contrast: #ffffff;--status-neutral-bg: #f1f1f2;--status-neutral-fg: #52525b;--status-neutral-solid: #a1a1aa;--status-info-bg: #ebeef7;--status-info-fg: #2b46bf;--status-info-solid: #3d5afe;--status-running-bg: #f6f0e4;--status-running-fg: #7a5406;--status-running-solid: #e8a417;--status-success-bg: #eaf2ec;--status-success-fg: #15633f;--status-success-solid: #22a866;--status-warning-bg: #f6efe7;--status-warning-fg: #8a4408;--status-warning-solid: #f2882e;--status-danger-bg: #f7ecec;--status-danger-fg: #a32219;--status-danger-solid: #e5473a;--status-unknown-bg: #f0edf8;--status-unknown-fg: #5b3aab;--status-unknown-solid: #8b5cf6;--phase-1: #3d5afe;--phase-2: #8b5cf6;--phase-3: #ec4899;--phase-4: #f2882e;--phase-5: #14b8a6;--phase-6: #64748b;--engine-claude: #c96442;--engine-codex: #10a37f;--engine-grok: #6b7280;--engine-local: #7c5cff;--bg-canvas: var(--gray-50);--bg-surface: #ffffff;--bg-surface-raised: #ffffff;--bg-overlay: rgba(9, 9, 11, .44);--bg-input: #ffffff;--bg-hover: var(--gray-100);--bg-active: var(--gray-200);--bg-selected: var(--gray-200);--bg-code: var(--gray-100);--meter-track: color-mix(in srgb, var(--text-tertiary) 45%, transparent);--text-primary: var(--gray-950);--text-secondary: var(--gray-600);--text-tertiary: var(--gray-500);--text-disabled: var(--gray-400);--text-on-accent: var(--accent-contrast);--text-link: var(--accent-600);--border-default: var(--gray-200);--border-subtle: #ececee;--border-strong: var(--gray-300);--border-focus: var(--accent-500);--font-grotesk: "Space Grotesk", "Avenir Next", -apple-system, BlinkMacSystemFont, sans-serif;--font-ui: "IBM Plex Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;--font-mono: ui-monospace, "SF Mono", "Cascadia Code", "JetBrains Mono", Menlo, Consolas, monospace;--font-display: var(--font-grotesk);--text-2xs-size: 11px;--text-2xs-line: 14px;--text-xs-size: 12px;--text-xs-line: 16px;--text-sm-size: 13px;--text-sm-line: 18px;--text-base-size: 14px;--text-base-line: 20px;--text-md-size: 15px;--text-md-line: 22px;--text-lg-size: 17px;--text-lg-line: 24px;--text-xl-size: 20px;--text-xl-line: 28px;--text-2xl-size: 24px;--text-2xl-line: 32px;--text-3xl-size: 30px;--text-3xl-line: 38px;--text-xs-line-relaxed: 18px;--text-sm-line-relaxed: 20px;--text-base-line-relaxed: 24px;--tracking-display: -.02em;--tracking-label: .06em;--font-weight-regular: 400;--font-weight-medium: 500;--font-weight-semibold: 600;--font-weight-bold: 700;--space-0: 0px;--space-px: 1px;--space-1: 4px;--space-2: 8px;--space-3: 12px;--space-4: 16px;--space-5: 20px;--space-6: 24px;--space-8: 32px;--space-10: 40px;--space-12: 48px;--space-16: 64px;--space-20: 80px;--space-24: 96px;--density-row: 32px;--density-row-sm: 26px;--density-pad: var(--space-3);--control-h: 32px;--control-h-sm: 28px;--control-h-xs: 24px;--engine-marker-h: 14px;--rail-width: 56px;--strip-height: 48px;--measure-read: 720px;--radius-xs: 3px;--radius-sm: 5px;--radius-md: 7px;--radius-lg: 10px;--radius-xl: 14px;--radius-full: 9999px;--shadow-menu: 0 4px 16px rgba(9, 9, 11, .1), 0 1px 2px rgba(9, 9, 11, .06);--shadow-dialog: 0 16px 48px rgba(9, 9, 11, .18), 0 2px 8px rgba(9, 9, 11, .08);--shadow-toast: 0 8px 24px rgba(9, 9, 11, .12), 0 1px 3px rgba(9, 9, 11, .08);--shadow-sm: 0 1px 2px rgba(9, 9, 11, .05);--shadow-md: 0 2px 8px rgba(9, 9, 11, .07), 0 1px 2px rgba(9, 9, 11, .05);--shadow-lg: var(--shadow-toast);--shadow-xl: var(--shadow-dialog);--shadow-focus-ring: 0 0 0 2px var(--bg-surface), 0 0 0 4px var(--border-focus);--duration-instant: 80ms;--duration-fast: .12s;--duration-base: .16s;--duration-slow: .22s;--ease-standard: cubic-bezier(.2, 0, 0, 1);--ease-decelerate: cubic-bezier(0, 0, 0, 1);--ease-accelerate: cubic-bezier(.3, 0, 1, 1);--z-base: 0;--z-sticky: 10;--z-dropdown: 20;--z-overlay: 30;--z-dialog: 40;--z-command-palette: 50;--z-toast: 60}@media(prefers-color-scheme:dark){:root:not([data-theme=light]){color-scheme:dark;--gray-0: #09090b;--gray-50: #0f0f11;--gray-100: #17171a;--gray-200: #232328;--gray-300: #33333a;--gray-400: #52525b;--gray-500: #71717a;--gray-600: #a1a1aa;--gray-700: #c4c4cc;--gray-800: #dededf;--gray-900: #f4f4f5;--gray-950: #fafafa;--accent-l-shift: 6%;--accent-50: hsl(var(--accent-h) var(--accent-s) 16%);--accent-100: hsl(var(--accent-h) var(--accent-s) 22%);--accent-300: hsl(var(--accent-h) var(--accent-s) calc(var(--accent-l-resolved) - 12%));--accent-600: hsl(var(--accent-h) var(--accent-s) calc(var(--accent-l-resolved) + 8%));--accent-700: hsl(var(--accent-h) var(--accent-s) calc(var(--accent-l-resolved) + 16%));--accent-contrast: #09090b;--status-neutral-bg: rgba(161, 161, 170, .14);--status-neutral-fg: #b4b4bd;--status-neutral-solid: #a1a1aa;--status-info-bg: rgba(91, 124, 255, .14);--status-info-fg: #9db0ff;--status-info-solid: #5b7cff;--status-running-bg: rgba(232, 164, 23, .14);--status-running-fg: #f2c353;--status-running-solid: #e8a417;--status-success-bg: rgba(46, 194, 126, .14);--status-success-fg: #5fd6a0;--status-success-solid: #2ec27e;--status-warning-bg: rgba(242, 136, 46, .14);--status-warning-fg: #f5a259;--status-warning-solid: #f2882e;--status-danger-bg: rgba(239, 90, 76, .14);--status-danger-fg: #f79189;--status-danger-solid: #ef5a4c;--status-unknown-bg: rgba(162, 118, 255, .14);--status-unknown-fg: #c4a6ff;--status-unknown-solid: #a276ff;--phase-1: #5b7cff;--phase-2: #a276ff;--phase-3: #f472b6;--phase-4: #f2a35e;--phase-5: #2dd4bf;--phase-6: #94a3b8;--engine-claude: #d97757;--engine-codex: #19c79b;--engine-grok: #9ca3af;--engine-local: #9b82ff;--bg-canvas: #0b0b0d;--bg-surface: #121214;--bg-surface-raised: #171719;--bg-overlay: rgba(0, 0, 0, .62);--bg-input: #111113;--bg-hover: #1c1c20;--bg-active: #232328;--bg-selected: #26262c;--bg-code: #0f0f11;--text-primary: #f4f4f5;--text-secondary: #a1a1aa;--text-tertiary: #8a8a93;--text-disabled: #52525b;--text-on-accent: var(--accent-contrast);--text-link: var(--accent-600);--border-default: #2a2a30;--border-subtle: #1f1f23;--border-strong: #3a3a42;--border-focus: var(--accent-500);--shadow-menu: 0 4px 16px rgba(0, 0, 0, .5);--shadow-dialog: 0 16px 48px rgba(0, 0, 0, .62), 0 2px 8px rgba(0, 0, 0, .4);--shadow-toast: 0 8px 24px rgba(0, 0, 0, .55);--shadow-sm: 0 1px 2px rgba(0, 0, 0, .4);--shadow-md: 0 2px 8px rgba(0, 0, 0, .45);--shadow-lg: var(--shadow-toast);--shadow-xl: var(--shadow-dialog);--shadow-focus-ring: 0 0 0 2px var(--bg-canvas), 0 0 0 4px var(--border-focus)}}:root[data-theme=dark]{color-scheme:dark;--gray-0: #09090b;--gray-50: #0f0f11;--gray-100: #17171a;--gray-200: #232328;--gray-300: #33333a;--gray-400: #52525b;--gray-500: #71717a;--gray-600: #a1a1aa;--gray-700: #c4c4cc;--gray-800: #dededf;--gray-900: #f4f4f5;--gray-950: #fafafa;--accent-l-shift: 6%;--accent-50: hsl(var(--accent-h) var(--accent-s) 16%);--accent-100: hsl(var(--accent-h) var(--accent-s) 22%);--accent-300: hsl(var(--accent-h) var(--accent-s) calc(var(--accent-l-resolved) - 12%));--accent-600: hsl(var(--accent-h) var(--accent-s) calc(var(--accent-l-resolved) + 8%));--accent-700: hsl(var(--accent-h) var(--accent-s) calc(var(--accent-l-resolved) + 16%));--accent-contrast: #09090b;--status-neutral-bg: rgba(161, 161, 170, .14);--status-neutral-fg: #b4b4bd;--status-neutral-solid: #a1a1aa;--status-info-bg: rgba(91, 124, 255, .14);--status-info-fg: #9db0ff;--status-info-solid: #5b7cff;--status-running-bg: rgba(232, 164, 23, .14);--status-running-fg: #f2c353;--status-running-solid: #e8a417;--status-success-bg: rgba(46, 194, 126, .14);--status-success-fg: #5fd6a0;--status-success-solid: #2ec27e;--status-warning-bg: rgba(242, 136, 46, .14);--status-warning-fg: #f5a259;--status-warning-solid: #f2882e;--status-danger-bg: rgba(239, 90, 76, .14);--status-danger-fg: #f79189;--status-danger-solid: #ef5a4c;--status-unknown-bg: rgba(162, 118, 255, .14);--status-unknown-fg: #c4a6ff;--status-unknown-solid: #a276ff;--phase-1: #5b7cff;--phase-2: #a276ff;--phase-3: #f472b6;--phase-4: #f2a35e;--phase-5: #2dd4bf;--phase-6: #94a3b8;--engine-claude: #d97757;--engine-codex: #19c79b;--engine-grok: #9ca3af;--engine-local: #9b82ff;--bg-canvas: #0b0b0d;--bg-surface: #121214;--bg-surface-raised: #171719;--bg-overlay: rgba(0, 0, 0, .62);--bg-input: #111113;--bg-hover: #1c1c20;--bg-active: #232328;--bg-selected: #26262c;--bg-code: #0f0f11;--text-primary: #f4f4f5;--text-secondary: #a1a1aa;--text-tertiary: #8a8a93;--text-disabled: #52525b;--text-on-accent: var(--accent-contrast);--text-link: var(--accent-600);--border-default: #2a2a30;--border-subtle: #1f1f23;--border-strong: #3a3a42;--border-focus: var(--accent-500);--shadow-menu: 0 4px 16px rgba(0, 0, 0, .5);--shadow-dialog: 0 16px 48px rgba(0, 0, 0, .62), 0 2px 8px rgba(0, 0, 0, .4);--shadow-toast: 0 8px 24px rgba(0, 0, 0, .55);--shadow-sm: 0 1px 2px rgba(0, 0, 0, .4);--shadow-md: 0 2px 8px rgba(0, 0, 0, .45);--shadow-lg: var(--shadow-toast);--shadow-xl: var(--shadow-dialog);--shadow-focus-ring: 0 0 0 2px var(--bg-canvas), 0 0 0 4px var(--border-focus)}:root[data-density=compact]{--density-row: 26px;--density-row-sm: 22px;--density-pad: var(--space-2);--control-h: 28px;--control-h-sm: 24px;--control-h-xs: 20px;--engine-marker-h: 12px}:root[data-radius=sharp]{--radius-xs: 0px;--radius-sm: 2px;--radius-md: 3px;--radius-lg: 4px;--radius-xl: 6px}:root[data-radius=soft]{--radius-xs: 5px;--radius-sm: 8px;--radius-md: 11px;--radius-lg: 15px;--radius-xl: 20px}@media(prefers-reduced-motion:reduce){:root:not([data-motion=full]){--duration-instant: 1ms;--duration-fast: 1ms;--duration-base: 1ms;--duration-slow: 1ms}}:root[data-motion=reduce]{--duration-instant: 1ms;--duration-fast: 1ms;--duration-base: 1ms;--duration-slow: 1ms}@font-face{font-family:Space Grotesk;src:url(/next/assets/SpaceGrotesk-Dcw4Q7AU.ttf) format("truetype");font-style:normal;font-weight:300 700;font-display:swap}@font-face{font-family:IBM Plex Sans;src:url(/next/assets/IBMPlexSans-B8AMtGvj.ttf) format("truetype");font-style:normal;font-weight:100 700;font-display:swap}*,*:before,*:after{box-sizing:border-box}html,body,#root{height:100%}body{margin:0;background:var(--bg-canvas);color:var(--text-primary);font-family:var(--font-ui);font-size:var(--text-base-size);line-height:var(--text-base-line);font-weight:var(--font-weight-regular);-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}img,svg{display:block;max-width:100%}button,input,select,textarea{font:inherit;color:inherit}button{background:none;border:none;padding:0;cursor:pointer}a{color:var(--text-link)}code,pre,kbd{font-family:var(--font-mono)}ul,ol{margin:0;padding:0;list-style:none}h1,h2,h3,h4,h5,h6,p{margin:0}h1,h2,h3{font-family:var(--font-display);letter-spacing:var(--tracking-display)}:focus{outline:none}:focus-visible{outline:none;box-shadow:var(--shadow-focus-ring)}::selection{background:var(--accent-100);color:var(--text-primary)}*{scrollbar-width:thin;scrollbar-color:var(--border-strong) transparent}*::-webkit-scrollbar{width:10px;height:10px}*::-webkit-scrollbar-track{background:transparent}*::-webkit-scrollbar-thumb{background:var(--border-strong);border-radius:var(--radius-full);border:2px solid transparent;background-clip:padding-box}.text-2xs{font-size:var(--text-2xs-size);line-height:var(--text-2xs-line)}.text-xs{font-size:var(--text-xs-size);line-height:var(--text-xs-line)}.text-sm{font-size:var(--text-sm-size);line-height:var(--text-sm-line)}.text-base{font-size:var(--text-base-size);line-height:var(--text-base-line)}.text-md{font-size:var(--text-md-size);line-height:var(--text-md-line)}.text-lg{font-size:var(--text-lg-size);line-height:var(--text-lg-line)}.text-xl{font-size:var(--text-xl-size);line-height:var(--text-xl-line);font-weight:var(--font-weight-semibold)}.text-2xl{font-size:var(--text-2xl-size);line-height:var(--text-2xl-line);font-weight:var(--font-weight-semibold)}.text-3xl{font-size:var(--text-3xl-size);line-height:var(--text-3xl-line);font-weight:var(--font-weight-bold)}.label-micro{font-family:var(--font-display);font-size:var(--text-2xs-size);line-height:var(--text-2xs-line);letter-spacing:var(--tracking-label);text-transform:uppercase;font-weight:var(--font-weight-medium);color:var(--text-tertiary)}.numeral{font-family:var(--font-display);font-variant-numeric:tabular-nums;letter-spacing:var(--tracking-display)}.text-secondary{color:var(--text-secondary)}.text-tertiary{color:var(--text-tertiary)}.font-mono{font-family:var(--font-mono)}.line-clamp-2{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.visually-hidden{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}@keyframes skeleton-shimmer{0%{background-position:-200% 0}to{background-position:200% 0}}.skeleton{background:linear-gradient(90deg,var(--bg-hover) 25%,var(--bg-active) 37%,var(--bg-hover) 63%);background-size:400% 100%;animation:skeleton-shimmer 1.4s ease infinite;border-radius:var(--radius-sm)}@media(prefers-reduced-motion:reduce){:root:not([data-motion=full]) .skeleton{animation:none}}:root[data-motion=reduce] .skeleton{animation:none}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{r as c,j as i,i as $e}from"./index-BkUlX2Se.js";const Ae={data:void 0,error:void 0,status:"idle",updatedAt:null},m=new Map,qe=4;let H=0;const V=[];let U=0;function Ge(){H-=1;const e=V.shift();e&&e()}function Ke(e){const t=()=>{const n=U;H+=1;const s=()=>{n===U&&Ge()};let r;try{r=e()}catch(o){return s(),Promise.reject(o instanceof Error?o:new Error(String(o)))}return r.then(o=>(s(),o),o=>{throw s(),o})};return H<qe?t():new Promise((n,s)=>{V.push(()=>{t().then(n,s)})})}function Qe(){U+=1,H=0;for(const e of V.splice(0))e()}function B(e){let t=m.get(e);return t||(t={snapshot:Ae,inFlight:null,fetcher:null,subscribers:new Set,issued:0,issuedAt:0,applied:0},m.set(e,t)),t}function P(e,t){e.snapshot={...e.snapshot,...t}}function $(e){const t=m.get(e);if(t)for(const n of t.subscribers)n()}function q(e,t){if(Object.is(e,t))return!0;if(typeof e!=typeof t||e===null||t===null||typeof e!="object"||Array.isArray(e)!==Array.isArray(t))return!1;if(Array.isArray(e)&&Array.isArray(t))return e.length!==t.length?!1:e.every((o,a)=>q(o,t[a]));const n=e,s=t,r=Object.keys(n);return r.length!==Object.keys(s).length?!1:r.every(o=>Object.prototype.hasOwnProperty.call(s,o)&&q(n[o],s[o]))}function Je(e,t){const n=B(e);return n.subscribers.add(t),()=>{n.subscribers.delete(t)}}function _e(e){return B(e).snapshot}const Ye=250;function Ve(e,t){return e.snapshot.updatedAt===null||e.snapshot.status==="error"?!1:Date.now()-e.snapshot.updatedAt<t}function Be(e,t){return e.inFlight===null?!1:t.joinWindowMs===void 0?!0:Date.now()-e.issuedAt<t.joinWindowMs}function W(e,t,n){const s=B(e);if(s.fetcher=t,!n.force){if(Be(s,n))return s.inFlight;if(n.maxAgeMs!==void 0&&Ve(s,n.maxAgeMs))return Promise.resolve()}const r=s.issued+=1;s.issuedAt=Date.now(),P(s,{status:s.snapshot.data!==void 0?"refreshing":"loading"}),$(e);const o=Ke(t).then(a=>{if(r<s.applied)return;s.applied=r;const d=s.snapshot.data!==void 0&&q(s.snapshot.data,a);P(s,{data:d?s.snapshot.data:a,error:void 0,status:"success",updatedAt:Date.now()})}).catch(a=>{r<s.applied||(s.applied=r,P(s,{error:a instanceof Error?a:new Error(String(a)),status:"error"}))}).finally(()=>{s.issued===r&&(s.inFlight=null),$(e)});return s.inFlight=o,o}function We(e,t){return W(e,t,{})}function ze(e,t,n){return W(e,t,{maxAgeMs:n})}function ve(e,t,n=!1){return W(e,t,n?{force:!0}:{joinWindowMs:Ye})}function Me(e){const t=m.get(e);!t||!t.fetcher||We(e,t.fetcher)}function G(e){for(const t of m.keys())t.startsWith(e)&&Me(t)}function Xe(e,t){t.snapshot=Ae,t.fetcher=null,t.inFlight=null,t.issued+=1,t.applied=t.issued,$(e)}function Ze(){Qe();for(const[e,t]of[...m]){if(t.subscribers.size===0){m.delete(e);continue}Xe(e,t)}}const A="ashlr.verse.drafts.v1",v="ashlr.verse.sent.v1",et=40,I=64*1024;function j(e,t){const n={};try{const s=localStorage.getItem(e),r=s?JSON.parse(s):null;if(r&&typeof r=="object"&&!Array.isArray(r))for(const[o,a]of Object.entries(r)){const d=t(a);d!==null&&(n[o]=d)}}catch{}return n}function z(e,t){const n=Object.keys(t);for(const s of n.slice(0,Math.max(0,n.length-et)))delete t[s];try{localStorage.setItem(e,JSON.stringify(t))}catch{}}const X=e=>typeof e=="string"&&e.length>0?e.slice(0,I):null,Z=e=>{if(!Array.isArray(e))return null;const t=e.filter(n=>typeof n=="string"&&n.length>0).slice(-25).map(n=>n.slice(0,I));return t.length>0?t:null};function zt(e){return e?j(A,X)[e]??"":""}function Xt(e,t){if(!e)return;const n=j(A,X);delete n[e],t.trim().length>0&&(n[e]=t.slice(0,I)),z(A,n)}function Zt(e){return e?j(v,Z)[e]??[]:[]}function en(e,t){if(!e||t.trim().length===0)return;const n=j(v,Z),s=n[e]??[];delete n[e];const r=s[s.length-1]===t?s:[...s,t.slice(0,I)];n[e]=r.slice(-25),z(v,n)}function tn(e){if(e)for(const[t,n]of[[A,X],[v,Z]]){const s=j(t,n);e in s&&(delete s[e],z(t,s))}}function tt(){for(const e of[A,v])try{localStorage.removeItem(e)}catch{}}const nt=4;function st(e){const t=e.trim();return t.length===0?0:Math.max(1,Math.ceil(t.length/nt))}function nn(e,t,n,s,r){const o=st(e);if(o===0)return null;const a=typeof n=="number"&&n>0?n:null;if(a===null)return null;const d=typeof t=="number"&&Number.isFinite(t)?Math.max(0,t):0,y=Math.min(999,Math.round((d+o)/a*100));return y<s?null:{draftTokens:o,projectedPercent:y,tone:y>=r?"danger":"warn"}}const Se="ashlr.readClientProof.v1",rt=/^[a-f0-9]{64}$/,ee=1200*1e3;function ot(){const e=new Uint8Array(32);return crypto.getRandomValues(e),Array.from(e,t=>t.toString(16).padStart(2,"0")).join("")}function at(){try{const t=sessionStorage.getItem(Se);if(t&&rt.test(t))return t}catch{}const e=ot();try{sessionStorage.setItem(Se,e)}catch{}return e}let te=at(),T=null,b=null,ne=!1,M=null,x=null,k=null,O={phase:"checking",checked:!1,mutationTokenHeldUntil:null};const K=new Set;function it(){for(const e of K)e()}function E(e){O={...O,...e},it()}function se(e){return K.add(e),()=>K.delete(e)}function p(){return O}function re(){return te}async function oe(e){const t=e.trim();if(!/^[a-f0-9]{64}$/.test(t))throw new Error("That does not look like a read token — expected 64 hex characters.");const n=await fetch("/api/session",{method:"POST",credentials:"same-origin",headers:{"x-ashlr-token":t,"x-ashlr-read-client":te}});if(n.status===204){M=t,E({phase:"authenticated",checked:!0});return}throw n.status===401?new Error("Server rejected that token. Double-check the read token printed by this server at startup."):new Error(`Unexpected response establishing session (HTTP ${n.status}).`)}async function sn(){try{await fetch("/api/session",{method:"DELETE",credentials:"same-origin",headers:{"x-ashlr-read-client":te}})}catch{}M=null,k=null,ae()}function ae(){ce(),Ze(),tt(),E({phase:"unauthenticated",checked:!0})}function Ne(){if(O.phase==="authenticated"){if(M){ct();return}ae()}}function ct(){if(x)return x;const e=M;return e?(x=(async()=>{try{return await oe(e),G(""),!0}catch{return M=null,k=null,ae(),!1}finally{x=null}})(),x):Promise.resolve(!1)}function rn(e){E({phase:e?"authenticated":"unauthenticated",checked:!0})}function lt(){const e=window.__ASHLR_TOKENS__;if(e&&typeof e=="object"){try{delete window.__ASHLR_TOKENS__}catch{window.__ASHLR_TOKENS__=void 0}typeof e.readToken=="string"&&(k={readToken:e.readToken,...typeof e.token=="string"?{token:e.token}:{}})}return k?{...k}:null}async function ut(){const e=lt();if(!e)return!1;try{await oe(e.readToken)}catch{return k=null,!1}return e.token&&/^[a-f0-9]{64}$/.test(e.token.trim())&&ie(e.token.trim(),{host:!0}),!0}function dt(){return T}function ft(){return T!==null}function Ce(){b&&clearTimeout(b),b=setTimeout(()=>{ne&&T!==null?je():ce()},ee)}function ie(e,t={}){T=e,ne=t.host===!0;const n=Date.now()+ee;E({mutationTokenHeldUntil:n}),Ce()}function je(){if(T===null)return;const e=Date.now()+ee;E({mutationTokenHeldUntil:e}),Ce()}function ce(){T=null,ne=!1,b&&(clearTimeout(b),b=null),E({mutationTokenHeldUntil:null})}const ht="_wrap_14qw9_1",mt="_card_14qw9_10",pt="_badge_14qw9_19",yt="_heading_14qw9_24",gt="_body_14qw9_31",bt="_code_14qw9_38",kt="_example_14qw9_49",wt="_form_14qw9_56",_t="_label_14qw9_63",St="_input_14qw9_68",Tt="_error_14qw9_82",Et="_submit_14qw9_87",xt="_optional_14qw9_107",l={wrap:ht,card:mt,badge:pt,heading:yt,body:gt,code:bt,example:kt,form:wt,label:_t,input:St,error:Tt,submit:Et,optional:xt},At=/^[a-f0-9]{64}$/;function on({onAuthenticated:e,heading:t="Connect to ashlr serve",command:n="ashlr serve",subject:s="This dashboard",mutationField:r=!1}){const[o,a]=c.useState(""),[d,y]=c.useState(""),[R,D]=c.useState(null),[he,me]=c.useState(!1),pe=c.useRef(null),ye=c.useId(),ge=c.useId(),be=c.useId(),ke=c.useRef(e);ke.current=e,c.useEffect(()=>{let f=!1;return ut().then(g=>{g&&!f&&ke.current?.()}),()=>{f=!0}},[]);async function Ue(f){f.preventDefault(),D(null);const g=d.trim();if(r&&g&&!At.test(g)){D("That does not look like a mutation token — expected 64 hex characters (leave it empty to unlock later).");return}me(!0);try{await oe(o),a(""),r&&g&&ie(g),y(""),e?.()}catch(we){D(we instanceof Error?we.message:"Could not establish a session."),pe.current?.select()}finally{me(!1)}}return i.jsx("div",{className:l.wrap,children:i.jsxs("div",{className:l.card,children:[i.jsx("div",{className:l.badge,"aria-hidden":"true",children:"⛺"}),i.jsx("h1",{className:l.heading,children:t}),i.jsxs("p",{className:l.body,children:[s," only ever talks to the ashlr server running on this machine (",i.jsx("code",{children:"127.0.0.1"}),", never a remote host). To read data, paste the"," ",i.jsx("strong",{children:"read token"})," printed in the terminal where you ran:"]}),i.jsx("pre",{className:l.code,children:n}),i.jsxs("p",{className:l.body,children:["It looks like a 64-character hex string, printed once at startup — something like"," ",i.jsx("code",{className:l.example,children:"a1b2c3…"}),". This dialog never stores it: it is exchanged once for a short-lived session cookie, then discarded."]}),i.jsxs("form",{onSubmit:Ue,className:l.form,noValidate:!0,children:[i.jsx("label",{htmlFor:ye,className:l.label,children:"Read token"}),i.jsx("input",{ref:pe,id:ye,name:"read-token",type:"password",autoComplete:"off",autoCorrect:"off",spellCheck:!1,inputMode:"text",className:l.input,value:o,onChange:f=>a(f.target.value),"aria-invalid":R?!0:void 0,"aria-describedby":R?be:void 0,placeholder:"64 hex characters",autoFocus:!0}),r?i.jsxs(i.Fragment,{children:[i.jsxs("label",{htmlFor:ge,className:l.label,children:["Mutation token ",i.jsx("span",{className:l.optional,children:"optional — paste it now to enable sending"})]}),i.jsx("input",{id:ge,name:"mutation-token",type:"password",autoComplete:"off",autoCorrect:"off",spellCheck:!1,inputMode:"text",className:l.input,value:d,onChange:f=>y(f.target.value),placeholder:"64 hex characters, printed under “Dispatch enabled”"})]}):null,R?i.jsx("p",{id:be,className:l.error,role:"alert",children:R}):null,i.jsx("button",{type:"submit",className:l.submit,disabled:he||o.trim().length===0,children:he?"Connecting…":"Connect"})]})]})})}const vt=["runs","swarms","inbox","daemon","daemon-observation","fleet-activity-ping","fleet-activity-observation","snapshot","session-expired"],Mt={runs:["runs"],swarms:["swarms"],inbox:["inbox"],daemon:["daemon","control-snapshot","verse-control","verse-usage-control","verse-caps"],"daemon-observation":["daemon","control-snapshot","verse-control","verse-usage-control","verse-caps"],"fleet-activity-ping":["fleet-activity","fleet"],"fleet-activity-observation":["fleet-activity","fleet"],snapshot:["dashboard-snapshot","agent-os-snapshot"],"session-expired":[]},Nt=new Map;let w=null,_=null,L=0;function Ct(){return Math.min(1e3*2**L,15e3)}function Q(){w&&(w.close(),w=null),_&&(clearTimeout(_),_=null)}function jt(){_||(_=setTimeout(()=>{_=null,L+=1,Re()},Ct()))}function Re(){if($e()){Q();return}if(p().phase!=="authenticated")return;Q();const e=`/api/events?client=${encodeURIComponent(re())}`,t=new EventSource(e,{withCredentials:!0});w=t,t.onopen=()=>{L=0};for(const n of vt)t.addEventListener(n,s=>{const r=s;let o;try{o=JSON.parse(r.data)}catch{o=void 0}for(const d of Mt[n])Me(d);n==="inbox"&&(G("inbox-list:"),G("proposal-detail-")),n==="session-expired"&&Ne();const a=Nt.get(n);if(a)for(const d of a)d(o)});t.onerror=()=>{t.close(),w===t&&(w=null),p().phase==="authenticated"&&jt()}}se(()=>{p().phase==="authenticated"?(L=0,Re()):Q()});const He="ashlr.theme.v1";function Oe(e){const t=document.documentElement;e==="system"?t.removeAttribute("data-theme"):t.setAttribute("data-theme",e)}function Rt(){try{const e=localStorage.getItem(He);if(e==="light"||e==="dark"||e==="system")return e}catch{}return"system"}let h=Rt();Oe(h);const J=new Set;function Ht(){for(const e of J)e()}function Ie(e){return J.add(e),()=>J.delete(e)}function N(){return h}function F(e){h=e,Oe(h);try{localStorage.setItem(He,h)}catch{}Ht()}function Ot(){F(h==="system"?"light":h==="light"?"dark":"system")}const le="ashlr.verse.appearance.v1",Le=30,Fe=78,S={h:245,s:72,l:58},It=[{id:"indigo",label:"Indigo",h:245,s:72,l:58},{id:"blue",label:"Blue",h:212,s:80,l:50},{id:"cyan",label:"Cyan",h:190,s:80,l:40},{id:"teal",label:"Teal",h:168,s:70,l:38},{id:"green",label:"Green",h:145,s:60,l:38},{id:"amber",label:"Amber",h:38,s:80,l:42},{id:"rose",label:"Rose",h:348,s:72,l:50},{id:"violet",label:"Violet",h:278,s:66,l:55}];function De(){try{return typeof window<"u"&&typeof window.matchMedia=="function"?window.matchMedia("(prefers-reduced-motion: reduce)").matches:!1}catch{return!1}}function ue(){return{theme:"system",accentH:S.h,accentS:S.s,accentL:S.l,density:"comfortable",displayFont:"grotesk",radius:"default",motion:"system",reduceMotion:De()}}function Te(e){return e==="system"||e==="reduce"||e==="full"}function Lt(e){return e==="reduce"?!0:e==="full"?!1:De()}const Ee=(e,t,n)=>Math.min(n,Math.max(t,e));function Ft(e){return e==="comfortable"||e==="compact"}function Dt(e){return e==="grotesk"||e==="ui"||e==="mono"}function Pt(e){return e==="sharp"||e==="default"||e==="soft"}function Pe(e,t){const n={...e,...t},s=t.reduceMotion,r=Te(t.motion)?t.motion:typeof s=="boolean"?s?"reduce":"full":Te(e.motion)?e.motion:"system";return{theme:n.theme==="light"||n.theme==="dark"?n.theme:"system",accentH:Number.isFinite(n.accentH)?(Math.round(n.accentH)%360+360)%360:S.h,accentS:Number.isFinite(n.accentS)?Ee(Math.round(n.accentS),0,100):S.s,accentL:Number.isFinite(n.accentL)?Ee(Math.round(n.accentL),Le,Fe):S.l,density:Ft(n.density)?n.density:"comfortable",displayFont:Dt(n.displayFont)?n.displayFont:"grotesk",radius:Pt(n.radius)?n.radius:"default",motion:r,reduceMotion:Lt(r)}}function Ut(){const e=ue();let t={};try{const r=localStorage.getItem(le);if(r){const o=JSON.parse(r);o&&typeof o=="object"&&(t=o)}}catch{}const{reduceMotion:n,...s}=t;return Pe(e,{...s,theme:N()})}function $t(e){try{const{theme:t,reduceMotion:n,...s}=e;localStorage.setItem(le,JSON.stringify(s))}catch{}}const qt={grotesk:null,ui:"var(--font-ui)",mono:"var(--font-mono)"};function de(e){if(typeof document>"u")return;const t=document.documentElement;t.style.setProperty("--accent-h",String(e.accentH)),t.style.setProperty("--accent-s",`${e.accentS}%`),t.style.setProperty("--accent-l",`${e.accentL}%`);const n=qt[e.displayFont];n===null?t.style.removeProperty("--font-display"):t.style.setProperty("--font-display",n),t.setAttribute("data-density",e.density),t.setAttribute("data-radius",e.radius),e.motion==="system"?t.removeAttribute("data-motion"):t.setAttribute("data-motion",e.motion)}let u=Ut();de(u);const Y=new Set;function fe(){for(const e of Y)e()}Ie(()=>{u.theme!==N()&&(u={...u,theme:N()},fe())});function Gt(e){return Y.add(e),()=>Y.delete(e)}function xe(){return u}function Kt(e){const t=Pe(u,e),n=t.theme!==u.theme;u=t,de(u),$t(u),n&&F(u.theme),fe()}function Qt(){try{localStorage.removeItem(le)}catch{}u=ue(),de(u),F("system"),fe()}function an(e=u){const t=ue();return e.theme===t.theme&&e.accentH===t.accentH&&e.accentS===t.accentS&&e.accentL===t.accentL&&e.density===t.density&&e.displayFont===t.displayFont&&e.radius===t.radius&&e.motion===t.motion}function cn(e=u){return It.find(t=>t.h===e.accentH&&t.s===e.accentS&&t.l===e.accentL)??null}const ln={min:Le,max:Fe},Jt=1e4;function un(e,t){const n=t?.freshMs??Jt,s=c.useSyncExternalStore(c.useCallback(r=>Je(e.key,r),[e.key]),()=>_e(e.key),()=>_e(e.key));return c.useEffect(()=>{ze(e.key,()=>e.fetch(),n)},[e.key,n]),s}function dn(e){return c.useCallback(()=>{ve(e.key,()=>e.fetch())},[e])}function fn(e){return c.useCallback(()=>{ve(e.key,()=>e.fetch(),!0)},[e])}function hn(){return c.useSyncExternalStore(se,()=>p().phase,()=>p().phase)}function mn(){const e=c.useSyncExternalStore(se,()=>p().mutationTokenHeldUntil,()=>p().mutationTokenHeldUntil);return{hasHold:ft(),token:dt(),heldUntil:e,setToken:ie,touch:je,clear:ce}}function pn(){return{theme:c.useSyncExternalStore(Ie,N,N),set:F,cycle:Ot}}function yn(){return{appearance:c.useSyncExternalStore(Gt,xe,xe),set:Kt,reset:Qt}}class C extends Error{constructor(t,n,s,r=null){super(t),this.status=n,this.path=s,this.detail=r,this.name="ApiError"}}class Yt extends C{constructor(t){super("This server was started without --allow-dispatch, so mutations are disabled.",404,t),this.name="DispatchDisabledError"}}async function gn(e,t){const n=await fetch(e,{method:"GET",credentials:"same-origin",headers:{"x-ashlr-read-client":re()},signal:t});if(n.status===401)throw Ne(),new C("Read session expired.",401,e);if(!n.ok)throw new C(`GET ${e} failed (HTTP ${n.status}).`,n.status,e);return await n.json()}async function bn(e,t,n,s){const r=await fetch(e,{method:"POST",credentials:"same-origin",headers:{"Content-Type":"application/json","x-ashlr-token":n},body:JSON.stringify(t??{}),signal:s});if(r.status===404)throw new Yt(e);if(r.status===401)throw new C("Mutation token was rejected.",401,e);if(!r.ok){let o="";try{const a=await r.json();o=typeof a.error=="string"&&a.error?a.error:typeof a.note=="string"?a.note:""}catch{}throw new C(`POST ${e} failed (HTTP ${r.status})${o?`: ${o}`:""}.`,r.status,e,o||null)}if(r.status!==204)return await r.json()}function kn(){return`/api/events?client=${encodeURIComponent(re())}`}const Vt="_skipLink_1uply_1",Bt={skipLink:Vt};function wn(){return i.jsx("button",{type:"button",className:Bt.skipLink,onClick:()=>{const e=document.getElementById("main-content");e?.focus(),e?.scrollIntoView({block:"start"})},children:"Skip to content"})}export{C as A,Zt as B,nn as C,Yt as D,Xt as E,en as F,tn as G,cn as H,It as I,ln as J,an as K,yn as L,on as S,gn as a,bn as b,ce as c,re as d,p as e,un as f,dt as g,hn as h,_e as i,dn as j,pn as k,wn as l,rn as m,sn as n,Me as o,ut as p,Ot as q,We as r,se as s,je as t,mn as u,ft as v,fn as w,G as x,kn as y,zt as z};
|
|
2
|
-
//# sourceMappingURL=global-CNVapoV1.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"global-CNVapoV1.js","sources":["../../../../../../src/web-ui/data/cache.ts","../../../../../../src/web-ui/routes/verse/chat/composer-state.ts","../../../../../../src/web-ui/data/auth-store.ts","../../../../../../src/web-ui/components/auth/SessionGate.tsx","../../../../../../src/web-ui/data/sse.ts","../../../../../../src/web-ui/data/theme-store.ts","../../../../../../src/web-ui/data/appearance-store.ts","../../../../../../src/web-ui/data/hooks.ts","../../../../../../src/web-ui/data/client.ts","../../../../../../src/web-ui/app/SkipToContent.tsx"],"sourcesContent":["/**\n * data/cache.ts — the resource cache every view reads through. This is what\n * makes tab switches instant instead of blanking to \"Loading…\": a resource\n * fetched once stays in the cache and renders immediately (possibly stale)\n * on the next mount, with a background refresh kicked off silently behind\n * it. No framework dependency — a plain keyed store consumed via\n * useSyncExternalStore (see hooks.ts), matching the pattern already used\n * for auth-store.ts.\n *\n * Design choices, spelled out because a future agent will extend this:\n * - In-flight de-dupe: two components requesting the same key at once\n * share one fetch, not two.\n * - Never clears `data` on refetch. `status` distinguishes 'loading'\n * (no data yet — show a skeleton) from 'refreshing' (stale data still\n * shown — show a subtle indicator) from 'success'/'error'.\n * - `invalidate(key)` is how the SSE layer (sse.ts) tells the cache \"the\n * server just told us this changed\" without either module importing\n * React.\n * - useSyncExternalStore requires getSnapshot() to return a NEW object\n * reference whenever the observable state actually changes (it detects\n * updates via Object.is, not deep equality) — mutating one long-lived\n * entry object in place and handing back the same reference would fire\n * subscribers that never actually re-render. Every state transition\n * below therefore replaces `bookkeeping.snapshot` wholesale instead of\n * patching fields on it.\n *\n * ---------------------------------------------------------------------------\n * V2.1 — the request burst, and the three defects that lived in this file\n * ---------------------------------------------------------------------------\n *\n * Mounting the Usage section fires SEVEN reads at once (accounts, control,\n * bootstrap, usage-series, local-models, /api/control, /api/usage), several\n * of which spawn short-lived probe processes server-side. That burst starved\n * the 2s local-runtime probe timeout and made a healthy Ollama report as\n * unreachable roughly one burst in ten. The probe timeouts were hardened\n * server-side; the burst itself was still this file's problem. Three changes:\n *\n * 1. CONCURRENCY GATE. Every fetch this cache issues passes through a\n * process-wide gate capped at QUERY_CONCURRENCY_LIMIT. The same seven\n * reads still happen, but at most four are outstanding at once, so the\n * server is never asked to run seven probe-spawning handlers in\n * parallel. Nothing is dropped, nothing is delayed by a timer — a\n * queued task starts the microtask a slot frees. The gate is what makes\n * the probe timeout survivable, and it costs the caller nothing: the\n * browser already caps at ~6 sockets per origin, so this is a cap the\n * requests were going to hit anyway, applied where we can order it.\n *\n * 2. FRESHNESS. `ensureQuery(key, fetcher, maxAgeMs)` is the MOUNT path: it\n * skips the request entirely when the cached value is younger than\n * `maxAgeMs`. Re-entering a section you were just in is now zero\n * requests instead of seven. `runQuery` keeps its old always-fetch\n * semantics because the SSE layer and the prefetchers depend on it.\n *\n * 3. REFRESH THAT ACTUALLY RE-READS. `runQuery`'s in-flight de-dupe is\n * right for two components mounting at once and WRONG for a person\n * pressing Refresh: a click landing while a read is in flight joined\n * the running promise and returned data fetched before the click — a\n * silent no-op, and with local-models taking seconds under load the\n * window was real. `refetchQuery` fixes it in two steps, because the\n * same hook backs both a person's button and a 3s poll loop:\n * - by default it joins an in-flight read ONLY while that read is\n * younger than REFETCH_JOIN_WINDOW_MS. A poll tick landing on a\n * read that started milliseconds ago still coalesces (which is the\n * documented contract UniverseView's poll relies on); a click\n * landing on a read that started seconds ago issues a new request.\n * - with `force` it never joins at all. That is the strict guarantee\n * for an explicit user-initiated refresh (hooks.ts `useRefresh`).\n * Either way a newer request SUPERSEDES an older one by sequence\n * number, so the last read ISSUED is the one whose result is kept even\n * if an earlier one lands later.\n *\n * And the latent one: `evictAll()` used to call `store.clear()`, which\n * dropped the Bookkeeping objects that mounted components had registered\n * their `useSyncExternalStore` listeners on. Those components stayed\n * subscribed to an orphan Set that nothing would ever notify again — they\n * would never re-render for the rest of their life. Evict now RESETS an\n * entry that still has subscribers (and deletes only the unobserved ones),\n * so a subscriber always survives an evict.\n */\n\nexport type QueryStatus = 'idle' | 'loading' | 'refreshing' | 'success' | 'error';\n\nexport interface QueryEntry<T> {\n data: T | undefined;\n error: Error | undefined;\n status: QueryStatus;\n updatedAt: number | null;\n}\n\ninterface Bookkeeping<T = unknown> {\n snapshot: QueryEntry<T>;\n inFlight: Promise<void> | null;\n fetcher: (() => Promise<T>) | null;\n subscribers: Set<() => void>;\n /** Sequence number of the newest request ISSUED for this key. */\n issued: number;\n /** `Date.now()` when that newest request was issued. */\n issuedAt: number;\n /** Highest sequence number whose result has been written to `snapshot`. */\n applied: number;\n}\n\nconst IDLE_SNAPSHOT: QueryEntry<unknown> = { data: undefined, error: undefined, status: 'idle', updatedAt: null };\n\nconst store = new Map<string, Bookkeeping>();\n\n// ---------------------------------------------------------------------------\n// Concurrency gate\n// ---------------------------------------------------------------------------\n\n/**\n * How many cache-issued fetches may be outstanding at once, process-wide.\n *\n * Four, not one: the point is to stop a seven-way burst from spawning seven\n * concurrent server-side probes, not to serialize the app. Four keeps a\n * section's first paint fast while leaving the server two of its six sockets\n * for the SSE stream and any non-cache request.\n */\nexport const QUERY_CONCURRENCY_LIMIT = 4;\n\nlet activeFetches = 0;\nconst gateQueue: (() => void)[] = [];\n/** Peak concurrency observed since the gate was last reset. */\nlet peakConcurrency = 0;\n/**\n * Bumped by `resetQueryGate`. A fetch issued under an older generation no\n * longer owns a slot, so its eventual settlement must not decrement the new\n * generation's count — otherwise a request that never settles (a hung\n * socket, or a test stubbing fetch with a promise that never resolves) would\n * permanently consume capacity and starve every later read.\n */\nlet gateGeneration = 0;\n\nfunction releaseSlot(): void {\n activeFetches -= 1;\n const next = gateQueue.shift();\n if (next) next();\n}\n\n/**\n * Run `task` when a slot is free. Purely promise-driven — no timers, so this\n * is safe under fake timers and adds no latency beyond one microtask.\n */\nfunction schedule<T>(task: () => Promise<T>): Promise<T> {\n const start = (): Promise<T> => {\n const generation = gateGeneration;\n activeFetches += 1;\n if (activeFetches > peakConcurrency) peakConcurrency = activeFetches;\n const done = (): void => {\n if (generation === gateGeneration) releaseSlot();\n };\n let running: Promise<T>;\n try {\n running = task();\n } catch (err) {\n // A fetcher that throws synchronously must still free its slot.\n done();\n return Promise.reject(err instanceof Error ? err : new Error(String(err)));\n }\n return running.then(\n (value) => {\n done();\n return value;\n },\n (err: unknown) => {\n done();\n throw err;\n },\n );\n };\n\n if (activeFetches < QUERY_CONCURRENCY_LIMIT) return start();\n return new Promise<T>((resolve, reject) => {\n gateQueue.push(() => {\n start().then(resolve, reject);\n });\n });\n}\n\n/**\n * Abandon the gate's accounting: forget how many fetches are outstanding and\n * let everything queued run now. Called from `evictAll()`, which is both the\n * logout path and every test's `beforeEach` — a read that outlives the state\n * it was issued for has no claim on the next one's capacity.\n *\n * Nothing is cancelled. Requests already in flight still settle and still\n * write their result unless the key was evicted underneath them.\n */\nexport function resetQueryGate(): void {\n gateGeneration += 1;\n activeFetches = 0;\n peakConcurrency = 0;\n for (const start of gateQueue.splice(0)) start();\n}\n\n/** Observability for the tests: what the gate is doing right now. */\nexport function queryGateStats(): { active: number; queued: number; peak: number } {\n return { active: activeFetches, queued: gateQueue.length, peak: peakConcurrency };\n}\n\n// ---------------------------------------------------------------------------\n// Entries\n// ---------------------------------------------------------------------------\n\nfunction entryFor<T>(key: string): Bookkeeping<T> {\n let e = store.get(key) as Bookkeeping<T> | undefined;\n if (!e) {\n e = {\n snapshot: IDLE_SNAPSHOT as QueryEntry<T>,\n inFlight: null,\n fetcher: null,\n subscribers: new Set(),\n issued: 0,\n issuedAt: 0,\n applied: 0,\n };\n store.set(key, e);\n }\n return e;\n}\n\nfunction setSnapshot<T>(e: Bookkeeping<T>, patch: Partial<QueryEntry<T>>): void {\n e.snapshot = { ...e.snapshot, ...patch };\n}\n\nfunction notify(key: string): void {\n const e = store.get(key);\n if (!e) return;\n for (const s of e.subscribers) s();\n}\n\n/**\n * Structural equality for API payloads, used to decide whether a refresh\n * actually changed anything.\n *\n * A Refresh that re-reads seven routes and finds six of them byte-identical\n * should re-render nothing for those six. Keeping the PREVIOUS `data`\n * reference when the new body is deep-equal does exactly that: the snapshot\n * object still changes (status and updatedAt did), so useSyncExternalStore\n * still fires, but every `useMemo`/`memo` keyed on `data` downstream skips —\n * and the Usage section's models are all keyed on `data`.\n *\n * Deliberately structural rather than JSON.stringify: key order is not\n * significant in these payloads, and stringify would also throw on the\n * (never-seen, but cheap to survive) cyclic body.\n */\nfunction sameData(a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return true;\n if (typeof a !== typeof b) return false;\n if (a === null || b === null || typeof a !== 'object') return false;\n if (Array.isArray(a) !== Array.isArray(b)) return false;\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n return a.every((item, i) => sameData(item, b[i]));\n }\n const left = a as Record<string, unknown>;\n const right = b as Record<string, unknown>;\n const leftKeys = Object.keys(left);\n if (leftKeys.length !== Object.keys(right).length) return false;\n return leftKeys.every((k) => Object.prototype.hasOwnProperty.call(right, k) && sameData(left[k], right[k]));\n}\n\nexport function subscribeQuery(key: string, listener: () => void): () => void {\n const e = entryFor(key);\n e.subscribers.add(listener);\n return () => {\n e.subscribers.delete(listener);\n };\n}\n\nexport function getQuerySnapshot<T>(key: string): QueryEntry<T> {\n return entryFor<T>(key).snapshot;\n}\n\n/**\n * How long an in-flight read is accepted as already satisfying a refetch.\n *\n * A poll tick (UniverseView refreshes every 3–15s and again on\n * visibilitychange) that lands on a read issued milliseconds ago should\n * coalesce with it — that is the behavior its own comment and tests pin\n * down. A person's Refresh landing on a read that has been running for\n * seconds should not: they are asking about the world as of the click.\n */\nexport const REFETCH_JOIN_WINDOW_MS = 250;\n\nexport interface QueryRunOptions {\n /**\n * Skip the request when the cached value is younger than this many ms.\n * Undefined (the default) always fetches.\n */\n maxAgeMs?: number;\n /**\n * Join an in-flight read only while it is younger than this. Undefined\n * joins any in-flight read (the original de-dupe).\n */\n joinWindowMs?: number;\n /**\n * User-initiated. Never joins an in-flight read and never honours\n * `maxAgeMs` — pressing Refresh must produce a request that was issued\n * after the click.\n */\n force?: boolean;\n}\n\nfunction isFresh(e: Bookkeeping, maxAgeMs: number): boolean {\n if (e.snapshot.updatedAt === null) return false;\n if (e.snapshot.status === 'error') return false;\n return Date.now() - e.snapshot.updatedAt < maxAgeMs;\n}\n\nfunction mayJoin(e: Bookkeeping, options: QueryRunOptions): boolean {\n if (e.inFlight === null) return false;\n if (options.joinWindowMs === undefined) return true;\n return Date.now() - e.issuedAt < options.joinWindowMs;\n}\n\nfunction execute<T>(key: string, fetcher: () => Promise<T>, options: QueryRunOptions): Promise<void> {\n const e = entryFor<T>(key);\n e.fetcher = fetcher;\n\n if (!options.force) {\n // Two components mounting at once share one request.\n if (mayJoin(e, options)) return e.inFlight!;\n // Already fresh enough for this caller: no request at all.\n if (options.maxAgeMs !== undefined && isFresh(e, options.maxAgeMs)) return Promise.resolve();\n }\n\n const seq = (e.issued += 1);\n e.issuedAt = Date.now();\n setSnapshot(e, { status: e.snapshot.data !== undefined ? 'refreshing' : 'loading' });\n notify(key);\n\n const run = schedule(fetcher)\n .then((data) => {\n // A superseded request (a forced refresh was issued after this one, or\n // the key was evicted) must never overwrite a newer answer.\n if (seq < e.applied) return;\n e.applied = seq;\n const unchanged = e.snapshot.data !== undefined && sameData(e.snapshot.data, data);\n setSnapshot(e, {\n data: unchanged ? e.snapshot.data : data,\n error: undefined,\n status: 'success',\n updatedAt: Date.now(),\n });\n })\n .catch((err: unknown) => {\n if (seq < e.applied) return;\n e.applied = seq;\n setSnapshot(e, { error: err instanceof Error ? err : new Error(String(err)), status: 'error' });\n })\n .finally(() => {\n // Only the newest request owns `inFlight`; an older, superseded one\n // finishing must not clear the newer one's slot.\n if (e.issued === seq) e.inFlight = null;\n notify(key);\n });\n\n e.inFlight = run;\n return run;\n}\n\n/**\n * Kick off (or join) a fetch for `key`. Safe to call redundantly — repeated\n * calls with an in-flight fetch are no-ops. Registers `fetcher` on the\n * entry so invalidate() can re-run it later without the caller re-supplying\n * it (e.g. from the SSE layer, which doesn't have per-view fetch closures).\n */\nexport function runQuery<T>(key: string, fetcher: () => Promise<T>): Promise<void> {\n return execute(key, fetcher, {});\n}\n\n/**\n * The MOUNT path (hooks.ts `useQuery`). Identical to `runQuery` except that a\n * value fetched less than `maxAgeMs` ago is accepted as-is and no request is\n * made. This is what turns \"switch to Usage and back\" from fourteen requests\n * into seven.\n */\nexport function ensureQuery<T>(key: string, fetcher: () => Promise<T>, maxAgeMs: number): Promise<void> {\n return execute(key, fetcher, { maxAgeMs });\n}\n\n/**\n * The REFRESH path (hooks.ts `useRefetch` / `useRefresh`).\n *\n * Default (`force: false`): joins an in-flight read only while that read is\n * younger than REFETCH_JOIN_WINDOW_MS, so a burst of poll ticks still\n * collapses into one request while a click on a long-running read gets a\n * real re-read.\n *\n * `force: true`: never joins. Always issues a request made after the call,\n * and wins the race against anything already running regardless of which\n * lands first. This is the guarantee an explicit user-initiated refresh\n * needs.\n */\nexport function refetchQuery<T>(key: string, fetcher: () => Promise<T>, force = false): Promise<void> {\n return execute(key, fetcher, force ? { force: true } : { joinWindowMs: REFETCH_JOIN_WINDOW_MS });\n}\n\n/** Re-run the last fetcher registered for `key`, if any (used by sse.ts). */\nexport function invalidate(key: string): void {\n const e = store.get(key);\n if (!e || !e.fetcher) return;\n void runQuery(key, e.fetcher);\n}\n\n/** Re-run the fetcher for every cached key starting with `prefix`. For\n * parameterized resources (per-filter inbox list queries, per-id inbox\n * detail queries) the exact live key isn't known ahead of time — used by\n * sse.ts and data/mutations.ts so those views live-update too. */\nexport function invalidatePrefix(prefix: string): void {\n for (const key of store.keys()) {\n if (key.startsWith(prefix)) invalidate(key);\n }\n}\n\n/**\n * Reset one entry to idle WITHOUT dropping its subscriber set, and supersede\n * anything still in flight for it so a late result cannot repopulate the key\n * after it was deliberately cleared.\n */\nfunction resetEntry(key: string, e: Bookkeeping): void {\n e.snapshot = IDLE_SNAPSHOT as QueryEntry<unknown>;\n e.fetcher = null;\n e.inFlight = null;\n e.issued += 1;\n e.applied = e.issued;\n notify(key);\n}\n\n/** Drop a key entirely (rare — e.g. on logout, so stale data never leaks\n * into the next session's first paint). An entry that still has mounted\n * subscribers is RESET rather than deleted: deleting it would orphan the\n * listener set those components registered on, leaving them subscribed to\n * something nothing can notify. */\nexport function evict(key: string): void {\n const e = store.get(key);\n if (!e) return;\n if (e.subscribers.size === 0) {\n store.delete(key);\n return;\n }\n resetEntry(key, e);\n}\n\nexport function evictAll(): void {\n resetQueryGate();\n for (const [key, e] of [...store]) {\n if (e.subscribers.size === 0) {\n store.delete(key);\n continue;\n }\n resetEntry(key, e);\n }\n}\n","/**\n * routes/verse/chat/composer-state.ts — the composer's memory, and an\n * honest estimate of what a message will cost.\n *\n * Two things a long agentic day needs from a message box and does not get\n * from a bare `useState`:\n *\n * 1. A draft survives switching chats and reloading the app. Half-written\n * instructions are expensive to re-type and are lost today the moment\n * ⌘K lands somewhere else.\n * 2. What was already sent can be recalled with ↑, the way every shell\n * works, because the second attempt at a prompt is usually the first\n * one plus a clause.\n *\n * Both are per-session and stored in `localStorage`, bounded so a long-lived\n * browser cannot accumulate an unbounded map. Every access is wrapped: a\n * private window with storage blocked still runs the app.\n */\n\nconst DRAFT_KEY = 'ashlr.verse.drafts.v1';\nconst SENT_KEY = 'ashlr.verse.sent.v1';\n\n/** Sessions remembered, newest last. */\nconst SESSION_LIMIT = 40;\n/** Messages recalled per session. */\nconst HISTORY_LIMIT = 25;\n/** Matches VERSE_MAX_TURN_TEXT_BYTES; a draft over it cannot be sent anyway. */\nconst TEXT_LIMIT = 64 * 1024;\n\nfunction readMap<T>(key: string, parse: (value: unknown) => T | null): Record<string, T> {\n const out: Record<string, T> = {};\n try {\n const raw = localStorage.getItem(key);\n const parsed = raw ? (JSON.parse(raw) as unknown) : null;\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {\n for (const [id, value] of Object.entries(parsed as Record<string, unknown>)) {\n const clean = parse(value);\n if (clean !== null) out[id] = clean;\n }\n }\n } catch {\n /* storage unavailable or corrupt — an empty memory is a valid one */\n }\n return out;\n}\n\nfunction writeMap(key: string, map: Record<string, unknown>): void {\n // Insertion order is recency order: every write deletes before re-inserting.\n const ids = Object.keys(map);\n for (const stale of ids.slice(0, Math.max(0, ids.length - SESSION_LIMIT))) delete map[stale];\n try {\n localStorage.setItem(key, JSON.stringify(map));\n } catch {\n /* best-effort */\n }\n}\n\nconst asText = (value: unknown): string | null =>\n typeof value === 'string' && value.length > 0 ? value.slice(0, TEXT_LIMIT) : null;\n\nconst asHistory = (value: unknown): string[] | null => {\n if (!Array.isArray(value)) return null;\n const list = value.filter((v): v is string => typeof v === 'string' && v.length > 0)\n .slice(-HISTORY_LIMIT)\n .map((v) => v.slice(0, TEXT_LIMIT));\n return list.length > 0 ? list : null;\n};\n\n/** The unsent draft for this chat, or `''`. */\nexport function loadDraft(sessionId: string | null | undefined): string {\n if (!sessionId) return '';\n return readMap(DRAFT_KEY, asText)[sessionId] ?? '';\n}\n\n/** Persist (or clear, when empty) the draft for this chat. */\nexport function saveDraft(sessionId: string | null | undefined, text: string): void {\n if (!sessionId) return;\n const map = readMap(DRAFT_KEY, asText);\n delete map[sessionId];\n if (text.trim().length > 0) map[sessionId] = text.slice(0, TEXT_LIMIT);\n writeMap(DRAFT_KEY, map);\n}\n\n/** Messages sent in this chat, oldest first. */\nexport function loadHistory(sessionId: string | null | undefined): string[] {\n if (!sessionId) return [];\n return readMap(SENT_KEY, asHistory)[sessionId] ?? [];\n}\n\n/** Record a sent message; consecutive duplicates are not stored twice. */\nexport function pushHistory(sessionId: string | null | undefined, text: string): void {\n if (!sessionId || text.trim().length === 0) return;\n const map = readMap(SENT_KEY, asHistory);\n const current = map[sessionId] ?? [];\n delete map[sessionId];\n const next = current[current.length - 1] === text ? current : [...current, text.slice(0, TEXT_LIMIT)];\n map[sessionId] = next.slice(-HISTORY_LIMIT);\n writeMap(SENT_KEY, map);\n}\n\n/**\n * Forget everything this chat remembered — its unsent draft and its sent\n * messages.\n *\n * Deleting a chat removes its transcript from disk, but the verbatim prompts\n * stayed in browser storage under the dead session id until forty newer\n * sessions rotated them out.\n */\nexport function forgetComposerMemory(sessionId: string | null | undefined): void {\n if (!sessionId) return;\n for (const [key, parse] of [\n [DRAFT_KEY, asText],\n [SENT_KEY, asHistory],\n ] as const) {\n const map: Record<string, unknown> = readMap(key, parse as (v: unknown) => unknown);\n if (!(sessionId in map)) continue;\n delete map[sessionId];\n writeMap(key, map);\n }\n}\n\n/**\n * Drop every remembered draft and sent message, for every session.\n *\n * Called on disconnect/logout alongside the query cache's `evictAll`, whose\n * contract is that stale data must not leak into the next session's first\n * paint. Prompt text is precisely the content that wipe exists for, and it is\n * the one thing that used to survive it.\n */\nexport function clearComposerMemory(): void {\n for (const key of [DRAFT_KEY, SENT_KEY]) {\n try {\n localStorage.removeItem(key);\n } catch {\n /* storage unavailable — nothing to clear */\n }\n }\n}\n\nexport const VERSE_DRAFT_STORAGE_KEY = DRAFT_KEY;\nexport const VERSE_SENT_STORAGE_KEY = SENT_KEY;\n\n// ---------------------------------------------------------------------------\n// Cost\n// ---------------------------------------------------------------------------\n\n/**\n * Characters per token. A rough English/code average, and rough is the\n * point: the real count is the provider's, which this app does not have\n * before the turn runs. Every surface that shows this figure marks it `≈`\n * and never presents it as measured (DESIGN §6, VERSE-TELEMETRY-V2).\n */\nconst CHARS_PER_TOKEN = 4;\n\nexport function estimateTokens(text: string): number {\n const trimmed = text.trim();\n if (trimmed.length === 0) return 0;\n return Math.max(1, Math.ceil(trimmed.length / CHARS_PER_TOKEN));\n}\n\nexport type CostTone = 'ok' | 'warn' | 'danger';\n\nexport interface CostHint {\n /** Estimated tokens for the draft itself. */\n draftTokens: number;\n /** Context occupancy this message would push the session to, as a percent. */\n projectedPercent: number;\n tone: CostTone;\n}\n\n/**\n * What sending this draft would do to the context window — or null when the\n * window is unknown, the box is empty, or the session is still comfortable.\n * The thresholds are the ContextMeter's, so the hint and the 2px line under\n * the header never disagree about when things are getting tight.\n */\nexport function costHint(\n text: string,\n contextTokens: number | null | undefined,\n contextWindow: number | null | undefined,\n warnPercent: number,\n dangerPercent: number,\n): CostHint | null {\n const draftTokens = estimateTokens(text);\n if (draftTokens === 0) return null;\n const window = typeof contextWindow === 'number' && contextWindow > 0 ? contextWindow : null;\n if (window === null) return null;\n const used = typeof contextTokens === 'number' && Number.isFinite(contextTokens) ? Math.max(0, contextTokens) : 0;\n const projectedPercent = Math.min(999, Math.round(((used + draftTokens) / window) * 100));\n if (projectedPercent < warnPercent) return null;\n return {\n draftTokens,\n projectedPercent,\n tone: projectedPercent >= dangerPercent ? 'danger' : 'warn',\n };\n}\n","/**\n * data/auth-store.ts — the read-session + mutation-token authority for the\n * whole app. Mirrors the two-token model in src/core/web/server.ts exactly:\n *\n * READ authority — exchanged via POST /api/session (raw readToken + a\n * per-tab client proof) for an HttpOnly cookie good\n * for 15 minutes. The raw read token is remembered in\n * MEMORY ONLY (never any storage) for the tab's\n * lifetime so the 15-minute ticket can be renewed\n * silently when a GET/SSE comes back 401 — a live\n * chat must not turn into \"paste a 64-hex token every\n * 15 minutes\". The client proof is not a secret by\n * itself (it has no authority without the signed\n * cookie) and is kept in sessionStorage so a page\n * reload can keep using the still-valid cookie instead\n * of forcing re-entry.\n *\n * MUTATION authority — the raw x-ashlr-token header, required on every\n * mutating call, NEVER exchanged for a session and\n * NEVER persisted to any storage. Held in memory only,\n * for a bounded \"hold\" window, so approving a run of\n * proposals isn't one window.prompt() per click. The\n * hold is cleared on tab close, explicit lock, or\n * idle timeout.\n *\n * This module has no React dependency — it is a plain external store\n * consumed via useSyncExternalStore in hooks.ts, per the same pattern used\n * for the query cache.\n */\n\nimport { evictAll, invalidatePrefix } from './cache.js';\n// The composer owns its own storage format, so the key names live with it\n// rather than being duplicated here where they could drift.\nimport { clearComposerMemory } from '../routes/verse/chat/composer-state.js';\n\nconst READ_CLIENT_STORAGE_KEY = 'ashlr.readClientProof.v1';\nconst READ_CLIENT_RE = /^[a-f0-9]{64}$/;\nconst MUTATION_HOLD_MS = 20 * 60 * 1000; // 20 minutes of inactivity clears the hold.\n\nexport type AuthPhase = 'checking' | 'unauthenticated' | 'authenticated';\n\ninterface AuthState {\n phase: AuthPhase;\n /** True once a session check has actually completed (vs. still booting). */\n checked: boolean;\n mutationTokenHeldUntil: number | null;\n}\n\nfunction randomHex64(): string {\n const bytes = new Uint8Array(32);\n crypto.getRandomValues(bytes);\n return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');\n}\n\nfunction loadOrCreateClientProof(): string {\n try {\n const existing = sessionStorage.getItem(READ_CLIENT_STORAGE_KEY);\n if (existing && READ_CLIENT_RE.test(existing)) return existing;\n } catch {\n // sessionStorage unavailable (privacy mode, etc.) — fall through to an\n // in-memory-only proof for this page life.\n }\n const proof = randomHex64();\n try {\n sessionStorage.setItem(READ_CLIENT_STORAGE_KEY, proof);\n } catch {\n /* best-effort persistence only */\n }\n return proof;\n}\n\nlet clientProof = loadOrCreateClientProof();\n\n/** In-memory only. Never touches storage. Cleared by clearMutationToken(). */\nlet mutationToken: string | null = null;\nlet mutationHoldTimer: ReturnType<typeof setTimeout> | null = null;\n/**\n * The mutation token came from the desktop host (window.__ASHLR_TOKENS__).\n * The operator never saw it, so an idle timeout must re-arm the hold rather\n * than clear it — a cleared hold would ask for a token nobody can paste.\n */\nlet mutationHeldByHost = false;\n\n/**\n * Last read token that the server accepted (typed or host-injected). Memory\n * only, same trust boundary as the mutation hold; used to renew the cookie\n * silently on 401. Dropped on explicit disconnect or when renewal fails.\n */\nlet rememberedReadToken: string | null = null;\nlet renewal: Promise<boolean> | null = null;\n/** Desktop-injected tokens kept for re-adoption (see the host-injected section below). */\nlet hostTokens: InjectedTokens | null = null;\n\nlet state: AuthState = { phase: 'checking', checked: false, mutationTokenHeldUntil: null };\nconst listeners = new Set<() => void>();\n\nfunction emit(): void {\n for (const l of listeners) l();\n}\n\nfunction setState(patch: Partial<AuthState>): void {\n state = { ...state, ...patch };\n emit();\n}\n\nexport function subscribeAuth(listener: () => void): () => void {\n listeners.add(listener);\n return () => listeners.delete(listener);\n}\n\nexport function getAuthSnapshot(): AuthState {\n return state;\n}\n\nexport function getReadClientProof(): string {\n return clientProof;\n}\n\n/** Regenerate the client proof (e.g. after an explicit \"forget this device\"). */\nexport function resetReadClientProof(): string {\n clientProof = randomHex64();\n try {\n sessionStorage.setItem(READ_CLIENT_STORAGE_KEY, clientProof);\n } catch {\n /* best-effort */\n }\n return clientProof;\n}\n\n/**\n * Exchange a raw read token for a session cookie. Throws on failure with a\n * message safe to show the operator. The raw token never leaves this call.\n */\nexport async function establishReadSession(rawReadToken: string): Promise<void> {\n const trimmed = rawReadToken.trim();\n if (!/^[a-f0-9]{64}$/.test(trimmed)) {\n throw new Error('That does not look like a read token — expected 64 hex characters.');\n }\n const res = await fetch('/api/session', {\n method: 'POST',\n credentials: 'same-origin',\n headers: {\n 'x-ashlr-token': trimmed,\n 'x-ashlr-read-client': clientProof,\n },\n });\n if (res.status === 204) {\n rememberedReadToken = trimmed;\n setState({ phase: 'authenticated', checked: true });\n return;\n }\n if (res.status === 401) {\n throw new Error('Server rejected that token. Double-check the read token printed by this server at startup.');\n }\n throw new Error(`Unexpected response establishing session (HTTP ${res.status}).`);\n}\n\nexport async function clearReadSession(): Promise<void> {\n try {\n await fetch('/api/session', {\n method: 'DELETE',\n credentials: 'same-origin',\n headers: { 'x-ashlr-read-client': clientProof },\n });\n } catch {\n /* best-effort */\n }\n rememberedReadToken = null;\n hostTokens = null;\n expireNow();\n}\n\nfunction expireNow(): void {\n clearMutationToken();\n evictAll();\n // `evictAll` clears the query cache so nothing stale leaks into the next\n // session's first paint. The composer's drafts and sent-message history live\n // in localStorage, not in that cache, and they are verbatim prompt text —\n // exactly the kind of content this wipe exists for.\n clearComposerMemory();\n setState({ phase: 'unauthenticated', checked: true });\n}\n\n/**\n * Called by data/client.ts whenever any authenticated GET comes back 401\n * (and by sse.ts on `session-expired`). With a remembered read token the\n * cookie is re-issued silently and the phase never flips; the failed call's\n * own retry/reconnect (SSE backoff, the next refetch) then succeeds. Only\n * when renewal fails (server restarted with fresh tokens) does the gate show.\n */\nexport function reportSessionExpired(): void {\n if (state.phase !== 'authenticated') return;\n if (rememberedReadToken) {\n void renewReadSession();\n return;\n }\n expireNow();\n}\n\n/** Re-exchange the remembered read token for a fresh cookie; one flight at a time. */\nexport function renewReadSession(): Promise<boolean> {\n if (renewal) return renewal;\n const token = rememberedReadToken;\n if (!token) return Promise.resolve(false);\n renewal = (async () => {\n try {\n await establishReadSession(token);\n // Whatever 401ed while the ticket was lapsed refetches against the new cookie.\n invalidatePrefix('');\n return true;\n } catch {\n rememberedReadToken = null;\n hostTokens = null;\n expireNow();\n return false;\n } finally {\n renewal = null;\n }\n })();\n return renewal;\n}\n\n/** Test hygiene: forget the remembered read token and host tokens without a network call. */\nexport function forgetRememberedTokens(): void {\n rememberedReadToken = null;\n hostTokens = null;\n renewal = null;\n}\n\nexport function markCheckComplete(authenticated: boolean): void {\n setState({ phase: authenticated ? 'authenticated' : 'unauthenticated', checked: true });\n}\n\n// ---------------------------------------------------------------------------\n// Host-injected tokens (desktop wrapper). The Tauri shell starts the server\n// itself and hands both tokens to the page through an initialization script\n// (`window.__ASHLR_TOKENS__`, see desktop/README.md) so the operator never\n// pastes them. The object is removed from `window` before either token is\n// used and kept in this module's memory instead (`hostTokens`), because the\n// desktop operator has no terminal to paste from: when the 15-minute cookie\n// lapses or the gate re-mounts, adoption re-runs from memory. The read token\n// is exchanged for the cookie exactly like a typed one and the mutation token\n// goes into the memory-only hold. Nothing is persisted and nothing is logged.\n// ---------------------------------------------------------------------------\n\nexport interface InjectedTokens {\n readToken: string;\n /** Mutation token; absent or null when the host started the server without dispatch. */\n token?: string | null;\n}\n\ndeclare global {\n interface Window {\n __ASHLR_TOKENS__?: InjectedTokens;\n }\n}\n\n/**\n * Take host-injected tokens: from `window` (cleared from the page on first\n * sight, remembered here) or, on later calls, from memory.\n */\nexport function takeInjectedTokens(): InjectedTokens | null {\n const injected = window.__ASHLR_TOKENS__;\n if (injected && typeof injected === 'object') {\n try {\n delete window.__ASHLR_TOKENS__;\n } catch {\n window.__ASHLR_TOKENS__ = undefined;\n }\n if (typeof injected.readToken === 'string') {\n hostTokens = { readToken: injected.readToken, ...(typeof injected.token === 'string' ? { token: injected.token } : {}) };\n }\n }\n return hostTokens ? { ...hostTokens } : null;\n}\n\n/**\n * Establish the read session (and set the mutation hold) from host-injected\n * tokens. Resolves true when a session was established; false when nothing\n * was injected or the server rejected the read token (the caller then falls\n * back to the normal SessionGate flow — the failure is never surfaced with\n * the token in it).\n */\nexport async function adoptInjectedTokens(): Promise<boolean> {\n const injected = takeInjectedTokens();\n if (!injected) return false;\n try {\n await establishReadSession(injected.readToken);\n } catch {\n // Wrong or stale tokens are not worth retrying from memory.\n hostTokens = null;\n return false;\n }\n if (injected.token && /^[a-f0-9]{64}$/.test(injected.token.trim())) setMutationToken(injected.token.trim(), { host: true });\n return true;\n}\n\n// ---------------------------------------------------------------------------\n// Mutation token — session-scoped hold, memory-only.\n// ---------------------------------------------------------------------------\n\nexport function getMutationToken(): string | null {\n return mutationToken;\n}\n\nexport function hasMutationHold(): boolean {\n return mutationToken !== null;\n}\n\nfunction armIdleClear(): void {\n if (mutationHoldTimer) clearTimeout(mutationHoldTimer);\n mutationHoldTimer = setTimeout(() => {\n // A host-held token has no operator to re-paste it: keep the hold alive.\n if (mutationHeldByHost && mutationToken !== null) touchMutationHold();\n else clearMutationToken();\n }, MUTATION_HOLD_MS);\n}\n\n/**\n * Set the mutation token for this tab's session hold. Never persisted.\n * `host` marks a token injected by the desktop wrapper (see adoptInjectedTokens).\n */\nexport function setMutationToken(token: string, opts: { host?: boolean } = {}): void {\n mutationToken = token;\n mutationHeldByHost = opts.host === true;\n const heldUntil = Date.now() + MUTATION_HOLD_MS;\n setState({ mutationTokenHeldUntil: heldUntil });\n armIdleClear();\n}\n\n/** Extend the hold window on real use (called after every mutating call). */\nexport function touchMutationHold(): void {\n if (mutationToken === null) return;\n const heldUntil = Date.now() + MUTATION_HOLD_MS;\n setState({ mutationTokenHeldUntil: heldUntil });\n armIdleClear();\n}\n\nexport function clearMutationToken(): void {\n mutationToken = null;\n mutationHeldByHost = false;\n if (mutationHoldTimer) {\n clearTimeout(mutationHoldTimer);\n mutationHoldTimer = null;\n }\n setState({ mutationTokenHeldUntil: null });\n}\n","/**\n * components/auth/SessionGate.tsx — replaces the old bare \"⚠ HTTP 401 +\n * Retry\" first-run experience (foundation brief item 5). Shown whenever\n * auth-store's phase is 'unauthenticated': on first load before any read\n * token has been entered, AND any time a session expires mid-use (SSE\n * disconnects, the next GET reports 401).\n *\n * By default this screen owns nothing about the MUTATION token — that is a\n * separate, later step (MutationTokenDialog), because read authority and\n * dispatch authority are genuinely different capabilities on this server\n * (see server.ts). Surfaces where sending is the whole point (Verse) can opt\n * into an optional second field with `mutationField`; the authorities stay\n * separate (the mutation token only ever enters the memory-only hold), it\n * just spares the operator a second paste screen on the first Enter.\n */\nimport { useEffect, useId, useRef, useState, type FormEvent } from 'react';\nimport { adoptInjectedTokens, establishReadSession, setMutationToken } from '../../data/auth-store.js';\nimport styles from './SessionGate.module.css';\n\nconst TOKEN_RE = /^[a-f0-9]{64}$/;\n\nexport interface SessionGateProps {\n onAuthenticated?: () => void;\n heading?: string;\n command?: string;\n /** What the copy calls this surface (\"This dashboard\", \"Ashlr Verse\"). */\n subject?: string;\n /**\n * Also offer the mutation token on the same screen. The two authorities\n * stay separate (a pasted mutation token goes straight into the memory-only\n * hold, exactly as MutationTokenDialog would set it); this only collapses\n * two paste screens into one for surfaces where sending is the whole point.\n */\n mutationField?: boolean;\n}\n\nexport function SessionGate({ onAuthenticated, heading = 'Connect to ashlr serve', command = 'ashlr serve', subject = 'This dashboard', mutationField = false }: SessionGateProps) {\n const [token, setToken] = useState('');\n const [mutation, setMutation] = useState('');\n const [error, setError] = useState<string | null>(null);\n const [busy, setBusy] = useState(false);\n const inputRef = useRef<HTMLInputElement>(null);\n const inputId = useId();\n const mutationId = useId();\n const errorId = useId();\n const authenticatedRef = useRef(onAuthenticated);\n authenticatedRef.current = onAuthenticated;\n\n // Desktop wrapper: when the host injected tokens (window.__ASHLR_TOKENS__),\n // establish the session silently instead of asking for a paste.\n useEffect(() => {\n let cancelled = false;\n void adoptInjectedTokens().then((adopted) => {\n if (adopted && !cancelled) authenticatedRef.current?.();\n });\n return () => { cancelled = true; };\n }, []);\n\n async function onSubmit(e: FormEvent) {\n e.preventDefault();\n setError(null);\n const mutationTrimmed = mutation.trim();\n if (mutationField && mutationTrimmed && !TOKEN_RE.test(mutationTrimmed)) {\n setError('That does not look like a mutation token — expected 64 hex characters (leave it empty to unlock later).');\n return;\n }\n setBusy(true);\n try {\n await establishReadSession(token);\n setToken('');\n if (mutationField && mutationTrimmed) setMutationToken(mutationTrimmed);\n setMutation('');\n onAuthenticated?.();\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Could not establish a session.');\n inputRef.current?.select();\n } finally {\n setBusy(false);\n }\n }\n\n return (\n <div className={styles.wrap}>\n <div className={styles.card}>\n <div className={styles.badge} aria-hidden=\"true\">\n ⛺\n </div>\n <h1 className={styles.heading}>{heading}</h1>\n <p className={styles.body}>\n {subject} only ever talks to the ashlr server running on this machine\n (<code>127.0.0.1</code>, never a remote host). To read data, paste the{' '}\n <strong>read token</strong> printed in the terminal where you ran:\n </p>\n <pre className={styles.code}>{command}</pre>\n <p className={styles.body}>\n It looks like a 64-character hex string, printed once at startup — something like{' '}\n <code className={styles.example}>a1b2c3…</code>. This dialog never stores it: it is\n exchanged once for a short-lived session cookie, then discarded.\n </p>\n <form onSubmit={onSubmit} className={styles.form} noValidate>\n <label htmlFor={inputId} className={styles.label}>\n Read token\n </label>\n <input\n ref={inputRef}\n id={inputId}\n name=\"read-token\"\n type=\"password\"\n autoComplete=\"off\"\n autoCorrect=\"off\"\n spellCheck={false}\n inputMode=\"text\"\n className={styles.input}\n value={token}\n onChange={(e) => setToken(e.target.value)}\n aria-invalid={error ? true : undefined}\n aria-describedby={error ? errorId : undefined}\n placeholder=\"64 hex characters\"\n autoFocus\n />\n {mutationField ? (\n <>\n <label htmlFor={mutationId} className={styles.label}>\n Mutation token <span className={styles.optional}>optional — paste it now to enable sending</span>\n </label>\n <input\n id={mutationId}\n name=\"mutation-token\"\n type=\"password\"\n autoComplete=\"off\"\n autoCorrect=\"off\"\n spellCheck={false}\n inputMode=\"text\"\n className={styles.input}\n value={mutation}\n onChange={(e) => setMutation(e.target.value)}\n placeholder=\"64 hex characters, printed under “Dispatch enabled”\"\n />\n </>\n ) : null}\n {error ? (\n <p id={errorId} className={styles.error} role=\"alert\">\n {error}\n </p>\n ) : null}\n <button type=\"submit\" className={styles.submit} disabled={busy || token.trim().length === 0}>\n {busy ? 'Connecting…' : 'Connect'}\n </button>\n </form>\n </div>\n </div>\n );\n}\n","/**\n * data/sse.ts — the live channel. One EventSource against /api/events for\n * the whole app, fanning out NAMED events (see api.ts handleSseEvents:\n * 'runs' | 'swarms' | 'inbox' | 'daemon' | 'daemon-observation' |\n * 'fleet-activity-ping' | 'fleet-activity-observation' | 'snapshot') to\n * subscribers, and invalidating the matching cache key so any mounted\n * useQuery(key) re-fetches in the background.\n *\n * The server polls disk at a fixed interval (api.ts SSE_POLL_MS, ~1.5s at\n * time of writing) — that is the real latency floor for \"live\". This layer\n * does not, and should not, try to beat that; it just makes sure the UI\n * never polls MORE often than the server already pushes.\n */\nimport { getReadClientProof, getAuthSnapshot, reportSessionExpired, subscribeAuth } from './auth-store.js';\nimport { invalidate, invalidatePrefix } from './cache.js';\nimport { isScopedConsolePath } from '../app/console-mode.js';\n\nexport const SSE_EVENT_NAMES = [\n 'runs',\n 'swarms',\n 'inbox',\n 'daemon',\n 'daemon-observation',\n 'fleet-activity-ping',\n 'fleet-activity-observation',\n 'snapshot',\n 'session-expired',\n] as const;\nexport type SseEventName = (typeof SSE_EVENT_NAMES)[number];\n\n/** Cache keys (see data/queries.ts) invalidated when a given SSE event fires. */\nconst EVENT_TO_CACHE_KEYS: Record<SseEventName, string[]> = {\n runs: ['runs'],\n swarms: ['swarms'],\n inbox: ['inbox'],\n // 'daemon' was previously mapped but nothing subscribed to a 'daemon'-keyed\n // query (control/daemon didn't exist yet) — now daemonObservationQuery\n // (data/queries.ts) does. Also refreshing 'control-snapshot' here so\n // /control/daemon and /control/security (both read off controlSnapshotQuery)\n // stay live on the same daemon-tick cadence instead of only on page load.\n // `/api/verse/control` and `/api/verse/audit` ARE the daemon projection, so\n // they ride the same signal. Without this the Autonomy cockpit — running\n // state, last tick outcome, today's spend against the cap — was frozen at\n // whatever was true when the section mounted, and DEFAULT_QUERY_FRESH_MS\n // then made a rail switch away and back inside 10s a no-op too.\n //\n // Deliberately NOT a coarse `verse-` prefix sweep: `verse-usage-accounts`\n // and `verse-usage-local-models` spawn provider probes, and firing those on\n // every daemon tick would turn a liveness fix into a process storm. Those\n // two poll on their own visibility-gated 30s cadence in UsageSection,\n // matching the collector's cycle.\n daemon: ['daemon', 'control-snapshot', 'verse-control', 'verse-usage-control', 'verse-caps'],\n 'daemon-observation': ['daemon', 'control-snapshot', 'verse-control', 'verse-usage-control', 'verse-caps'],\n // fleet-activity pings are the closest live signal fleet state changed;\n // 'fleet' (fleetStatusQuery, /control/fleet's queue depth + lease health)\n // has no dedicated SSE event of its own, so it rides these instead of only\n // refreshing on mount.\n 'fleet-activity-ping': ['fleet-activity', 'fleet'],\n 'fleet-activity-observation': ['fleet-activity', 'fleet'],\n // Agent OS snapshots are immutable records but their current authenticated\n // projection advances alongside the server's general snapshot signal.\n snapshot: ['dashboard-snapshot', 'agent-os-snapshot'],\n 'session-expired': [],\n};\n\ntype RawListener = (payload: unknown) => void;\nconst rawListeners = new Map<SseEventName, Set<RawListener>>();\n\nlet source: EventSource | null = null;\nlet reconnectTimer: ReturnType<typeof setTimeout> | null = null;\nlet reconnectAttempt = 0;\n\nfunction backoffMs(): number {\n // 1s, 2s, 4s, 8s, capped at 15s — the server's own poll floor is ~1.5s so\n // there is no benefit reconnecting faster than that even on attempt 0.\n return Math.min(1000 * 2 ** reconnectAttempt, 15_000);\n}\n\nfunction teardown(): void {\n if (source) {\n source.close();\n source = null;\n }\n if (reconnectTimer) {\n clearTimeout(reconnectTimer);\n reconnectTimer = null;\n }\n}\n\nfunction scheduleReconnect(): void {\n if (reconnectTimer) return;\n reconnectTimer = setTimeout(() => {\n reconnectTimer = null;\n reconnectAttempt += 1;\n connect();\n }, backoffMs());\n}\n\nfunction connect(): void {\n // Shared hooks import this module, but the dedicated console has no global\n // Hub event channel. Check on every reconnect, not just the initial import.\n if (isScopedConsolePath()) { teardown(); return; }\n if (getAuthSnapshot().phase !== 'authenticated') return;\n teardown();\n\n const url = `/api/events?client=${encodeURIComponent(getReadClientProof())}`;\n const es = new EventSource(url, { withCredentials: true });\n source = es;\n\n es.onopen = () => {\n reconnectAttempt = 0;\n };\n\n for (const name of SSE_EVENT_NAMES) {\n es.addEventListener(name, (evt) => {\n const messageEvent = evt as MessageEvent<string>;\n let payload: unknown = undefined;\n try {\n payload = JSON.parse(messageEvent.data);\n } catch {\n payload = undefined;\n }\n for (const key of EVENT_TO_CACHE_KEYS[name]) invalidate(key);\n if (name === 'inbox') {\n // Parameterized inbox queries (one cache key per filter combo / per\n // proposal id) aren't in EVENT_TO_CACHE_KEYS's static map — sweep\n // both prefixes so the list (any filter) and any open detail view\n // (proposalDetailQuery, data/queries.ts) live-update on the same\n // 'inbox' event as the unfiltered badge.\n invalidatePrefix('inbox-list:');\n invalidatePrefix('proposal-detail-');\n }\n if (name === 'session-expired') reportSessionExpired();\n const listeners = rawListeners.get(name);\n if (listeners) for (const l of listeners) l(payload);\n });\n }\n\n es.onerror = () => {\n // EventSource auto-retries on its own, but its built-in retry delay is\n // opaque and can hammer a server that just lost its read session. Close\n // it and drive reconnection ourselves so we stop entirely once\n // auth-store reports the session as expired (see the subscribeAuth\n // wiring at the bottom of this file).\n es.close();\n if (source === es) source = null;\n if (getAuthSnapshot().phase === 'authenticated') scheduleReconnect();\n };\n}\n\n/** Subscribe to one named SSE event's raw payload (rarely needed directly —\n * most views should prefer useQuery + the cache-key mapping above). */\nexport function onSseEvent(name: SseEventName, listener: RawListener): () => void {\n let set = rawListeners.get(name);\n if (!set) {\n set = new Set();\n rawListeners.set(name, set);\n }\n set.add(listener);\n return () => set!.delete(listener);\n}\n\n// Connect once authenticated; disconnect the instant the session drops so a\n// stale connection never masks a 401 the rest of the app needs to react to.\nsubscribeAuth(() => {\n const phase = getAuthSnapshot().phase;\n if (phase === 'authenticated') {\n reconnectAttempt = 0;\n connect();\n } else {\n teardown();\n }\n});\n","/**\n * data/theme-store.ts — the theme preference, extracted out of Topbar.tsx\n * so the command palette's \"Toggle theme\" action can read/write the same\n * source of truth Topbar's own button uses (same module-level state +\n * subscribe pattern as auth-store.ts — commands.ts's `run` closures have no\n * React context to call useState from, so this can't live in a component).\n */\n\nexport type ThemePreference = 'system' | 'light' | 'dark';\n\nconst THEME_STORAGE_KEY = 'ashlr.theme.v1';\n\nfunction applyTheme(pref: ThemePreference): void {\n const root = document.documentElement;\n if (pref === 'system') root.removeAttribute('data-theme');\n else root.setAttribute('data-theme', pref);\n}\n\nfunction loadTheme(): ThemePreference {\n try {\n const v = localStorage.getItem(THEME_STORAGE_KEY);\n if (v === 'light' || v === 'dark' || v === 'system') return v;\n } catch {\n /* ignore */\n }\n return 'system';\n}\n\nlet theme: ThemePreference = loadTheme();\napplyTheme(theme);\n\nconst listeners = new Set<() => void>();\n\nfunction emit(): void {\n for (const l of listeners) l();\n}\n\nexport function subscribeTheme(listener: () => void): () => void {\n listeners.add(listener);\n return () => listeners.delete(listener);\n}\n\nexport function getTheme(): ThemePreference {\n return theme;\n}\n\nexport function setTheme(next: ThemePreference): void {\n theme = next;\n applyTheme(theme);\n try {\n localStorage.setItem(THEME_STORAGE_KEY, theme);\n } catch {\n /* best-effort persistence only */\n }\n emit();\n}\n\nexport function cycleTheme(): void {\n setTheme(theme === 'system' ? 'light' : theme === 'light' ? 'dark' : 'system');\n}\n","/**\n * data/appearance-store.ts — the operator's appearance preferences: the\n * \"Linux for agentic engineering\" part of the design language\n * (docs/VERSE-DESIGN-V2.md §3). Theme, accent, density, display face,\n * radius, and motion, applied live by writing custom properties and\n * data-attributes on <html>.\n *\n * Same shape as ./theme-store.ts on purpose — module-level state + a\n * listener set, zero React — so it is unit-testable without a DOM renderer\n * and usable from non-component code (the command palette's closures, the\n * rail's quick-toggle). data/hooks.ts binds it into React via\n * `useAppearance()`, exactly as it does for the theme.\n *\n * THEME IS NOT FORKED. `theme-store.ts` remains the single authority for the\n * light/dark/system preference (key `ashlr.theme.v1`, already read by the\n * topbar and the command palette); this module re-exposes it as one field of\n * the appearance snapshot and delegates writes to `setTheme`. Two stores\n * writing `data-theme` would race; one store that owns it does not.\n *\n * Everything is applied at MODULE LOAD (before React's first render, since\n * hooks.ts imports this and every view imports hooks.ts), so the app never\n * paints the default accent for a frame before swapping to the chosen one.\n */\nimport {\n getTheme,\n setTheme,\n subscribeTheme,\n type ThemePreference,\n} from './theme-store.js';\n\nexport type { ThemePreference };\n\nexport type Density = 'comfortable' | 'compact';\n/**\n * The operator's motion choice. Tri-state ON PURPOSE.\n *\n * A boolean cannot distinguish \"I want full motion\" from \"I never touched\n * this\", and `persist()` writes the whole snapshot on every appearance change\n * — so dragging the accent hue once used to store `reduceMotion: false` and\n * permanently override the OS `prefers-reduced-motion` preference, because\n * `applyAppearance` then wrote `data-motion=\"full\"`, which is exactly the\n * value every `:root:not([data-motion=\"full\"])` guard exists to defer to.\n *\n * 'system' removes the attribute entirely and lets the media queries decide,\n * which is what DESIGN-V2 §3 and §6 ask for by default.\n */\nexport type MotionPreference = 'system' | 'reduce' | 'full';\nexport type DisplayFont = 'grotesk' | 'ui' | 'mono';\nexport type RadiusScale = 'sharp' | 'default' | 'soft';\n\nexport interface Appearance {\n /** Delegated to theme-store.ts — persisted under its own key. */\n theme: ThemePreference;\n /** Accent hue 0-360. */\n accentH: number;\n /** Accent saturation 0-100 (%). */\n accentS: number;\n /** Accent lightness, clamped to a readable band (%). */\n accentL: number;\n density: Density;\n displayFont: DisplayFont;\n radius: RadiusScale;\n /** The stored choice: 'system' follows `prefers-reduced-motion`. */\n motion: MotionPreference;\n /**\n * DERIVED, never stored — what the Settings switch shows and what the app\n * behaves as right now. `setAppearance({ reduceMotion })` is the only call\n * that turns this back into an explicit 'reduce'/'full'.\n */\n reduceMotion: boolean;\n}\n\n/**\n * The persisted subset. `theme` lives in theme-store, and `reduceMotion` is\n * derived from `motion` — storing it is what created the override bug.\n */\ntype StoredAppearance = Omit<Appearance, 'theme' | 'reduceMotion'>;\n\nconst STORAGE_KEY = 'ashlr.verse.appearance.v1';\n\n/**\n * Accent lightness band. Below ~30% the accent stops reading as a color on a\n * dark canvas; above ~78% it disappears on a light one. The hue and\n * saturation are unconstrained — this is the one axis where a free slider\n * can make the app genuinely unusable.\n */\nconst ACCENT_L_MIN = 30;\nconst ACCENT_L_MAX = 78;\n\n/** Must match the bare `:root` values in design/tokens.css. */\nexport const DEFAULT_ACCENT = { h: 245, s: 72, l: 58 } as const;\n\nexport interface AccentPreset {\n id: string;\n label: string;\n h: number;\n s: number;\n l: number;\n}\n\n/**\n * Eight presets (design doc §3). Each was checked with design/contrast.ts so\n * its derived link color clears 4.5:1 in BOTH themes — the hue slider can\n * still be dragged anywhere, and the Settings panel warns when it lands\n * somewhere unreadable.\n */\nexport const ACCENT_PRESETS: readonly AccentPreset[] = [\n { id: 'indigo', label: 'Indigo', h: 245, s: 72, l: 58 },\n { id: 'blue', label: 'Blue', h: 212, s: 80, l: 50 },\n { id: 'cyan', label: 'Cyan', h: 190, s: 80, l: 40 },\n { id: 'teal', label: 'Teal', h: 168, s: 70, l: 38 },\n { id: 'green', label: 'Green', h: 145, s: 60, l: 38 },\n { id: 'amber', label: 'Amber', h: 38, s: 80, l: 42 },\n { id: 'rose', label: 'Rose', h: 348, s: 72, l: 50 },\n { id: 'violet', label: 'Violet', h: 278, s: 66, l: 55 },\n] as const;\n\nfunction prefersReducedMotion(): boolean {\n try {\n return typeof window !== 'undefined' && typeof window.matchMedia === 'function'\n ? window.matchMedia('(prefers-reduced-motion: reduce)').matches\n : false;\n } catch {\n // Some embedded webviews throw on an unsupported media feature.\n return false;\n }\n}\n\nexport function defaultAppearance(): Appearance {\n return {\n theme: 'system',\n accentH: DEFAULT_ACCENT.h,\n accentS: DEFAULT_ACCENT.s,\n accentL: DEFAULT_ACCENT.l,\n density: 'comfortable',\n displayFont: 'grotesk',\n radius: 'default',\n motion: 'system',\n reduceMotion: prefersReducedMotion(),\n };\n}\n\nfunction isMotion(v: unknown): v is MotionPreference {\n return v === 'system' || v === 'reduce' || v === 'full';\n}\n\n/** What 'system' resolves to right now; an explicit choice answers itself. */\nfunction resolveReduceMotion(motion: MotionPreference): boolean {\n return motion === 'reduce' ? true : motion === 'full' ? false : prefersReducedMotion();\n}\n\nconst clamp = (n: number, min: number, max: number): number => Math.min(max, Math.max(min, n));\n\nfunction isDensity(v: unknown): v is Density {\n return v === 'comfortable' || v === 'compact';\n}\nfunction isDisplayFont(v: unknown): v is DisplayFont {\n return v === 'grotesk' || v === 'ui' || v === 'mono';\n}\nfunction isRadius(v: unknown): v is RadiusScale {\n return v === 'sharp' || v === 'default' || v === 'soft';\n}\n\n/** Coerce anything (stored JSON, a caller's patch) into a valid Appearance. */\nfunction normalize(base: Appearance, patch: Partial<Appearance> | Partial<StoredAppearance>): Appearance {\n const merged = { ...base, ...patch } as Appearance;\n // `reduceMotion` in a PATCH is the Settings switch and is authoritative —\n // it is the one call that converts the derived boolean back into a stored\n // choice. `reduceMotion` in a stored blob is not a choice at all, which is\n // why load() strips it before it ever gets here.\n const asked = (patch as Partial<Appearance>).reduceMotion;\n const motion: MotionPreference = isMotion((patch as Partial<Appearance>).motion)\n ? ((patch as Partial<Appearance>).motion as MotionPreference)\n : typeof asked === 'boolean'\n ? (asked ? 'reduce' : 'full')\n : isMotion(base.motion)\n ? base.motion\n : 'system';\n return {\n theme: merged.theme === 'light' || merged.theme === 'dark' ? merged.theme : 'system',\n accentH: Number.isFinite(merged.accentH) ? ((Math.round(merged.accentH) % 360) + 360) % 360 : DEFAULT_ACCENT.h,\n accentS: Number.isFinite(merged.accentS) ? clamp(Math.round(merged.accentS), 0, 100) : DEFAULT_ACCENT.s,\n accentL: Number.isFinite(merged.accentL)\n ? clamp(Math.round(merged.accentL), ACCENT_L_MIN, ACCENT_L_MAX)\n : DEFAULT_ACCENT.l,\n density: isDensity(merged.density) ? merged.density : 'comfortable',\n displayFont: isDisplayFont(merged.displayFont) ? merged.displayFont : 'grotesk',\n radius: isRadius(merged.radius) ? merged.radius : 'default',\n motion,\n reduceMotion: resolveReduceMotion(motion),\n };\n}\n\nfunction load(): Appearance {\n const base = defaultAppearance();\n let stored: Partial<StoredAppearance> = {};\n try {\n const raw = localStorage.getItem(STORAGE_KEY);\n if (raw) {\n const parsed: unknown = JSON.parse(raw);\n if (parsed && typeof parsed === 'object') stored = parsed as Partial<StoredAppearance>;\n }\n } catch {\n // Unavailable storage, quota errors, or corrupt JSON all mean the same\n // thing here: fall back to defaults rather than failing to boot.\n }\n // Drop any `reduceMotion` a pre-tri-state build left behind: it was written\n // by every unrelated appearance change, so it records nothing the operator\n // decided. Without `motion`, the media query decides — as it should.\n const { reduceMotion: _legacy, ...rest } = stored as Partial<StoredAppearance> & { reduceMotion?: unknown };\n return normalize(base, { ...rest, theme: getTheme() });\n}\n\nfunction persist(value: Appearance): void {\n try {\n // theme is deliberately excluded — theme-store.ts owns its own key.\n // reduceMotion is excluded because it is derived: persisting it is what\n // let an accent drag silently override the OS preference.\n const { theme: _theme, reduceMotion: _derived, ...rest } = value;\n localStorage.setItem(STORAGE_KEY, JSON.stringify(rest satisfies StoredAppearance));\n } catch {\n /* best-effort persistence only */\n }\n}\n\nconst FONT_VAR: Record<DisplayFont, string | null> = {\n // null = remove the inline override and fall back to the token default.\n grotesk: null,\n ui: 'var(--font-ui)',\n mono: 'var(--font-mono)',\n};\n\n/**\n * Write the settings onto <html>. The accent is written as the three raw\n * channels tokens.css derives its whole ramp from, so one paint recolors\n * links, focus rings, primary buttons, meters and charts together.\n */\nexport function applyAppearance(value: Appearance): void {\n if (typeof document === 'undefined') return;\n const root = document.documentElement;\n root.style.setProperty('--accent-h', String(value.accentH));\n root.style.setProperty('--accent-s', `${value.accentS}%`);\n root.style.setProperty('--accent-l', `${value.accentL}%`);\n\n const font = FONT_VAR[value.displayFont];\n if (font === null) root.style.removeProperty('--font-display');\n else root.style.setProperty('--font-display', font);\n\n root.setAttribute('data-density', value.density);\n root.setAttribute('data-radius', value.radius);\n // 'system' removes the attribute so the `prefers-reduced-motion` media\n // queries decide on their own. \"full\" is meaningful, not a no-op: it opts an\n // operator explicitly OUT of the OS preference (tokens.css and the four\n // Verse stylesheets guard their media query with :not([data-motion=\"full\"])),\n // which is precisely why it must never be written by default.\n if (value.motion === 'system') root.removeAttribute('data-motion');\n else root.setAttribute('data-motion', value.motion);\n}\n\nlet current: Appearance = load();\napplyAppearance(current);\n\nconst listeners = new Set<() => void>();\n\nfunction emit(): void {\n for (const l of listeners) l();\n}\n\n// The theme can also be changed from the rail's quick-toggle and the command\n// palette (both go through theme-store). Mirror those into the snapshot so a\n// Settings panel rendered at the same time never shows a stale radio.\nsubscribeTheme(() => {\n if (current.theme === getTheme()) return;\n current = { ...current, theme: getTheme() };\n emit();\n});\n\nexport function subscribeAppearance(listener: () => void): () => void {\n listeners.add(listener);\n return () => listeners.delete(listener);\n}\n\n/** Stable snapshot — a NEW object on every change (useSyncExternalStore uses Object.is). */\nexport function getAppearance(): Appearance {\n return current;\n}\n\n/** Apply and persist a partial change. Unknown/out-of-range values are coerced. */\nexport function setAppearance(patch: Partial<Appearance>): void {\n const next = normalize(current, patch);\n const themeChanged = next.theme !== current.theme;\n current = next;\n applyAppearance(current);\n persist(current);\n // Delegated last so theme-store's own listeners see the final snapshot.\n if (themeChanged) setTheme(current.theme);\n emit();\n}\n\n/** Back to the shipped defaults, including the theme and the stored blob. */\nexport function resetAppearance(): void {\n try {\n localStorage.removeItem(STORAGE_KEY);\n } catch {\n /* best-effort */\n }\n current = defaultAppearance();\n applyAppearance(current);\n setTheme('system');\n emit();\n}\n\n/** True when nothing has been customized — drives the \"Reset\" affordance. */\nexport function isDefaultAppearance(value: Appearance = current): boolean {\n const base = defaultAppearance();\n return (\n value.theme === base.theme &&\n value.accentH === base.accentH &&\n value.accentS === base.accentS &&\n value.accentL === base.accentL &&\n value.density === base.density &&\n value.displayFont === base.displayFont &&\n value.radius === base.radius &&\n value.motion === base.motion\n );\n}\n\n/** The preset whose channels match the current accent, if any. */\nexport function matchingAccentPreset(value: Appearance = current): AccentPreset | null {\n return (\n ACCENT_PRESETS.find((p) => p.h === value.accentH && p.s === value.accentS && p.l === value.accentL) ?? null\n );\n}\n\nexport const ACCENT_LIGHTNESS_RANGE = { min: ACCENT_L_MIN, max: ACCENT_L_MAX } as const;\nexport const APPEARANCE_STORAGE_KEY = STORAGE_KEY;\n","/**\n * data/hooks.ts — React bindings over the framework-free stores in\n * cache.ts / auth-store.ts / sse.ts. This is the only file in the data\n * layer that imports React; every module underneath it stays plain\n * TypeScript so it's trivially unit-testable without a DOM.\n */\nimport { useCallback, useEffect, useSyncExternalStore } from 'react';\nimport { ensureQuery, getQuerySnapshot, refetchQuery, subscribeQuery, type QueryEntry } from './cache.js';\nimport type { QueryDef } from './queries.js';\nimport {\n getAuthSnapshot,\n subscribeAuth,\n type AuthPhase,\n hasMutationHold,\n getMutationToken,\n setMutationToken as storeSetMutationToken,\n touchMutationHold,\n clearMutationToken,\n} from './auth-store.js';\nimport { onSseEvent, type SseEventName } from './sse.js';\nimport { subscribeTheme, getTheme, setTheme, cycleTheme, type ThemePreference } from './theme-store.js';\nimport {\n getAppearance,\n resetAppearance,\n setAppearance,\n subscribeAppearance,\n type Appearance,\n} from './appearance-store.js';\n\n/**\n * How long a cached value is accepted on MOUNT without re-reading it.\n *\n * A section that re-mounts inside this window (rail switch, a parent\n * re-keying, React 18's double-invoked effects in StrictMode) issues no\n * request at all. Ten seconds is short enough that any surface a human\n * revisits deliberately still re-reads, and long enough that the Usage\n * section's seven probe-spawning reads happen ONCE per visit rather than\n * once per mount.\n *\n * This does not make data stale: keys with an SSE event still refresh the\n * instant the server says they changed (sse.ts → invalidate, which is not\n * freshness-gated), and `useRefetch` always ignores it.\n */\nexport const DEFAULT_QUERY_FRESH_MS = 10_000;\n\nexport interface UseQueryOptions {\n /** Override `DEFAULT_QUERY_FRESH_MS` for this subscription. 0 = always fetch. */\n freshMs?: number;\n}\n\n/**\n * Subscribe to a resource. Fetches on mount (and on `key` change) unless the\n * cached value is still fresh, stays subscribed to background refreshes\n * triggered by sse.ts, and NEVER clears `data` while refetching —\n * `status === 'refreshing'` is the signal to show a subtle indicator instead\n * of a skeleton, `status === 'loading'` (no data yet at all) is the only\n * state that should render a skeleton.\n */\nexport function useQuery<T>(def: QueryDef<T>, options?: UseQueryOptions): QueryEntry<T> {\n const freshMs = options?.freshMs ?? DEFAULT_QUERY_FRESH_MS;\n const snapshot = useSyncExternalStore(\n useCallback((listener) => subscribeQuery(def.key, listener), [def.key]),\n () => getQuerySnapshot<T>(def.key),\n () => getQuerySnapshot<T>(def.key),\n );\n\n useEffect(() => {\n // Cache refreshers outlive any one component. Binding the stored fetcher\n // to this component's AbortSignal poisons later SSE invalidations after\n // unmount, so the shared cache owns the request lifetime.\n void ensureQuery(def.key, () => def.fetch(), freshMs);\n // Re-fetch only when the resource identity changes, not on every render.\n // eslint-disable-next-line react-hooks/exhaustive-deps -- def.fetch is re-created per render on purpose; only def.key identifies the resource.\n }, [def.key, freshMs]);\n\n return snapshot;\n}\n\n/**\n * Manually trigger a re-fetch without waiting for SSE — a \"Retry\" button, a\n * \"Refresh\" button, or a poll loop.\n *\n * Ignores freshness always, and joins an in-flight read only while that read\n * is younger than `REFETCH_JOIN_WINDOW_MS`. Both halves matter, because this\n * one hook backs two different callers:\n *\n * - A poll (UniverseView ticks every 3–15s and again on visibilitychange)\n * landing on a read that started milliseconds ago SHOULD coalesce with\n * it; issuing a second request there is pure waste.\n * - A person's click landing on a read that has been running for seconds\n * SHOULD NOT. That was the old behavior: the click joined the running\n * promise and returned data fetched before the click — a silent no-op,\n * and with local-model probes taking seconds under load the window was\n * wide enough to hit in ordinary use.\n *\n * For the strict guarantee — a request issued after the click, every time,\n * no matter what else is running — use `useRefresh`.\n */\nexport function useRefetch<T>(def: QueryDef<T>): () => void {\n return useCallback(() => {\n void refetchQuery(def.key, () => def.fetch());\n }, [def]);\n}\n\n/**\n * The explicit user-initiated refresh: ALWAYS issues a new request, never\n * joins one already running, and the read it issues is the one whose result\n * is kept even if an older read lands after it.\n *\n * Use this behind anything a person presses that promises them a fresh\n * reading — a Refresh button on a surface with no SSE invalidation, where\n * the whole reason the button exists is that nothing else will re-read.\n */\nexport function useRefresh<T>(def: QueryDef<T>): () => void {\n return useCallback(() => {\n void refetchQuery(def.key, () => def.fetch(), true);\n }, [def]);\n}\n\nexport function useAuthPhase(): AuthPhase {\n return useSyncExternalStore(subscribeAuth, () => getAuthSnapshot().phase, () => getAuthSnapshot().phase);\n}\n\nexport interface MutationHold {\n hasHold: boolean;\n token: string | null;\n heldUntil: number | null;\n setToken: (token: string) => void;\n touch: () => void;\n clear: () => void;\n}\n\nexport function useMutationHold(): MutationHold {\n const heldUntil = useSyncExternalStore(\n subscribeAuth,\n () => getAuthSnapshot().mutationTokenHeldUntil,\n () => getAuthSnapshot().mutationTokenHeldUntil,\n );\n return {\n hasHold: hasMutationHold(),\n token: getMutationToken(),\n heldUntil,\n setToken: storeSetMutationToken,\n touch: touchMutationHold,\n clear: clearMutationToken,\n };\n}\n\n/** Subscribe to a raw named SSE event for the lifetime of the component. */\nexport function useSseEvent(name: SseEventName, onEvent: (payload: unknown) => void): void {\n useEffect(() => onSseEvent(name, onEvent), [name, onEvent]);\n}\n\nexport interface ThemeControl {\n theme: ThemePreference;\n set: (next: ThemePreference) => void;\n cycle: () => void;\n}\n\n/** Shared with Topbar's theme button and the command palette's \"Toggle\n * theme\" action — both read/write data/theme-store.ts through this hook so\n * neither can drift out of sync with the other. */\nexport function useTheme(): ThemeControl {\n const theme = useSyncExternalStore(subscribeTheme, getTheme, getTheme);\n return { theme, set: setTheme, cycle: cycleTheme };\n}\n\nexport interface AppearanceControl {\n appearance: Appearance;\n /** Partial change; applied to <html> and persisted immediately (no save button). */\n set: (patch: Partial<Appearance>) => void;\n reset: () => void;\n}\n\n/**\n * The Settings section's appearance controls and anything that needs to READ\n * the current density/accent (e.g. a chart sizing rows) share this hook, for\n * the same reason useTheme exists: two readers of one external store, never\n * a second copy of the state.\n *\n * Importing this module is also what applies the stored appearance before\n * the first paint — every view imports hooks.ts, so appearance-store.ts is\n * evaluated (and has written <html>) before React renders anything.\n */\nexport function useAppearance(): AppearanceControl {\n const appearance = useSyncExternalStore(subscribeAppearance, getAppearance, getAppearance);\n return { appearance, set: setAppearance, reset: resetAppearance };\n}\n","/**\n * data/client.ts — thin typed HTTP client for the existing /api/* surface.\n * No redefinition of response shapes here — callers type each call with the\n * interfaces re-exported from ./api-types.ts.\n */\nimport { getReadClientProof, reportSessionExpired } from './auth-store.js';\n\nexport class ApiError extends Error {\n constructor(\n message: string,\n public readonly status: number,\n public readonly path: string,\n /**\n * The server's own sentence about the refusal, when it sent one, without\n * the \"POST … failed (HTTP N)\" wrapper. A surface that wants to show the\n * operator why an action was refused should prefer this over `message`:\n * it is the text the route author wrote for a person to read.\n */\n public readonly detail: string | null = null,\n ) {\n super(message);\n this.name = 'ApiError';\n }\n}\n\n/** Raised specifically for 404s on mutation routes: --allow-dispatch is off. */\nexport class DispatchDisabledError extends ApiError {\n constructor(path: string) {\n super('This server was started without --allow-dispatch, so mutations are disabled.', 404, path);\n this.name = 'DispatchDisabledError';\n }\n}\n\n/** GET an authenticated read route. 401 reports session-expired and throws. */\nexport async function apiGet<T>(path: string, signal?: AbortSignal): Promise<T> {\n const res = await fetch(path, {\n method: 'GET',\n credentials: 'same-origin',\n headers: { 'x-ashlr-read-client': getReadClientProof() },\n signal,\n });\n if (res.status === 401) {\n reportSessionExpired();\n throw new ApiError('Read session expired.', 401, path);\n }\n if (!res.ok) {\n throw new ApiError(`GET ${path} failed (HTTP ${res.status}).`, res.status, path);\n }\n return (await res.json()) as T;\n}\n\n/**\n * POST a mutating route with the raw mutation token. `mutationToken` must\n * come from the caller (data/mutations.ts pulls it from auth-store) — this\n * function does not read auth-store itself so it stays trivially testable.\n */\nexport async function apiPost<T>(\n path: string,\n body: unknown,\n mutationToken: string,\n signal?: AbortSignal,\n): Promise<T> {\n const res = await fetch(path, {\n method: 'POST',\n credentials: 'same-origin',\n headers: {\n 'Content-Type': 'application/json',\n 'x-ashlr-token': mutationToken,\n },\n body: JSON.stringify(body ?? {}),\n signal,\n });\n if (res.status === 404) {\n throw new DispatchDisabledError(path);\n }\n if (res.status === 401) {\n throw new ApiError('Mutation token was rejected.', 401, path);\n }\n if (!res.ok) {\n let detail = '';\n try {\n // `error` is the documented refusal field, but the Verse control plane\n // answers a refused daemon/scope action with a full result body whose\n // plain-language sentence lives in `note` and which has NO `error` key\n // at all (see VerseDaemonActionResult). Reading only `error` threw away\n // sentences like \"no repositories are enrolled, so the loop would do\n // nothing\" and left the caller with a bare status code to guess from.\n const j = (await res.json()) as { error?: unknown; note?: unknown };\n const message = typeof j.error === 'string' && j.error ? j.error : typeof j.note === 'string' ? j.note : '';\n detail = message;\n } catch {\n /* body wasn't JSON */\n }\n throw new ApiError(\n `POST ${path} failed (HTTP ${res.status})${detail ? `: ${detail}` : ''}.`,\n res.status,\n path,\n detail || null,\n );\n }\n if (res.status === 204) return undefined as T;\n return (await res.json()) as T;\n}\n\n/** Build the SSE URL, carrying the client proof as the query-string proof\n * EventSource can't attach as a header (see server.ts readSessionClientProof). */\nexport function eventsUrl(): string {\n return `/api/events?client=${encodeURIComponent(getReadClientProof())}`;\n}\n","/**\n * app/SkipToContent.tsx — a real skip link, deliberately NOT `<a\n * href=\"#main-content\">`: this app uses HashRouter, so a literal `#`\n * anchor would rewrite location.hash and hijack client-side routing\n * instead of just scrolling. A button that imperatively focuses the\n * `#main-content` element (set on <main> in components/layout/Shell.tsx)\n * gets the same keyboard behavior without touching the route.\n */\nimport styles from './SkipToContent.module.css';\n\nexport function SkipToContent() {\n return (\n <button\n type=\"button\"\n className={styles.skipLink}\n onClick={() => {\n const main = document.getElementById('main-content');\n main?.focus();\n main?.scrollIntoView({ block: 'start' });\n }}\n >\n Skip to content\n </button>\n );\n}\n"],"names":["IDLE_SNAPSHOT","store","QUERY_CONCURRENCY_LIMIT","activeFetches","gateQueue","gateGeneration","releaseSlot","next","schedule","task","start","generation","done","running","err","value","resolve","reject","resetQueryGate","entryFor","key","e","setSnapshot","patch","notify","s","sameData","a","b","item","i","left","right","leftKeys","k","subscribeQuery","listener","getQuerySnapshot","REFETCH_JOIN_WINDOW_MS","isFresh","maxAgeMs","mayJoin","options","execute","fetcher","seq","run","data","unchanged","runQuery","ensureQuery","refetchQuery","force","invalidate","invalidatePrefix","prefix","resetEntry","evictAll","DRAFT_KEY","SENT_KEY","SESSION_LIMIT","TEXT_LIMIT","readMap","parse","out","raw","parsed","id","clean","writeMap","map","ids","stale","asText","asHistory","list","v","loadDraft","sessionId","saveDraft","text","loadHistory","pushHistory","current","forgetComposerMemory","clearComposerMemory","CHARS_PER_TOKEN","estimateTokens","trimmed","costHint","contextTokens","contextWindow","warnPercent","dangerPercent","draftTokens","window","used","projectedPercent","READ_CLIENT_STORAGE_KEY","READ_CLIENT_RE","MUTATION_HOLD_MS","randomHex64","bytes","loadOrCreateClientProof","existing","proof","clientProof","mutationToken","mutationHoldTimer","mutationHeldByHost","rememberedReadToken","renewal","hostTokens","state","listeners","emit","l","setState","subscribeAuth","getAuthSnapshot","getReadClientProof","establishReadSession","rawReadToken","res","clearReadSession","expireNow","clearMutationToken","reportSessionExpired","renewReadSession","token","markCheckComplete","authenticated","takeInjectedTokens","injected","adoptInjectedTokens","setMutationToken","getMutationToken","hasMutationHold","armIdleClear","touchMutationHold","opts","heldUntil","TOKEN_RE","SessionGate","onAuthenticated","heading","command","subject","mutationField","setToken","useState","mutation","setMutation","error","setError","busy","setBusy","inputRef","useRef","inputId","useId","mutationId","errorId","authenticatedRef","useEffect","cancelled","adopted","onSubmit","mutationTrimmed","jsx","styles","jsxs","Fragment","SSE_EVENT_NAMES","EVENT_TO_CACHE_KEYS","rawListeners","source","reconnectTimer","reconnectAttempt","backoffMs","teardown","scheduleReconnect","connect","isScopedConsolePath","url","es","name","evt","messageEvent","payload","THEME_STORAGE_KEY","applyTheme","pref","root","loadTheme","theme","subscribeTheme","getTheme","setTheme","cycleTheme","STORAGE_KEY","ACCENT_L_MIN","ACCENT_L_MAX","DEFAULT_ACCENT","ACCENT_PRESETS","prefersReducedMotion","defaultAppearance","isMotion","resolveReduceMotion","motion","clamp","n","min","max","isDensity","isDisplayFont","isRadius","normalize","base","merged","asked","load","stored","_legacy","rest","persist","_theme","_derived","FONT_VAR","applyAppearance","font","subscribeAppearance","getAppearance","setAppearance","themeChanged","resetAppearance","isDefaultAppearance","matchingAccentPreset","p","ACCENT_LIGHTNESS_RANGE","DEFAULT_QUERY_FRESH_MS","useQuery","def","freshMs","snapshot","useSyncExternalStore","useCallback","useRefetch","useRefresh","useAuthPhase","useMutationHold","storeSetMutationToken","useTheme","useAppearance","ApiError","message","status","path","detail","DispatchDisabledError","apiGet","signal","apiPost","body","j","eventsUrl","SkipToContent","main"],"mappings":"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"}
|