@songsid/agend 2.1.12 → 2.2.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/README.zh-TW.md +4 -2
- package/dist/agent-cli.js +6 -1
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.js +68 -12
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-token-header.d.ts +13 -0
- package/dist/agent-token-header.js +16 -0
- package/dist/agent-token-header.js.map +1 -0
- package/dist/auth/one-time-code.d.ts +19 -0
- package/dist/auth/one-time-code.js +49 -0
- package/dist/auth/one-time-code.js.map +1 -0
- package/dist/auth-api.d.ts +52 -0
- package/dist/auth-api.js +346 -0
- package/dist/auth-api.js.map +1 -0
- package/dist/backend/antigravity.js +4 -3
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/claude-code.d.ts +4 -0
- package/dist/backend/claude-code.js +97 -0
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/codex.js +59 -30
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/grok.js +1 -1
- package/dist/backend/grok.js.map +1 -1
- package/dist/backend/kiro-agent.d.ts +66 -0
- package/dist/backend/kiro-agent.js +302 -0
- package/dist/backend/kiro-agent.js.map +1 -0
- package/dist/backend/kiro-identity.d.ts +80 -0
- package/dist/backend/kiro-identity.js +537 -0
- package/dist/backend/kiro-identity.js.map +1 -0
- package/dist/backend/kiro-v2-store-worker.d.ts +1 -0
- package/dist/backend/kiro-v2-store-worker.js +87 -0
- package/dist/backend/kiro-v2-store-worker.js.map +1 -0
- package/dist/backend/kiro-v2-store.d.ts +54 -0
- package/dist/backend/kiro-v2-store.js +131 -0
- package/dist/backend/kiro-v2-store.js.map +1 -0
- package/dist/backend/kiro-v3-identity.d.ts +5 -0
- package/dist/backend/kiro-v3-identity.js +22 -0
- package/dist/backend/kiro-v3-identity.js.map +1 -1
- package/dist/backend/kiro.d.ts +90 -7
- package/dist/backend/kiro.js +500 -124
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/muse.js +1 -1
- package/dist/backend/muse.js.map +1 -1
- package/dist/backend/types.d.ts +51 -3
- package/dist/backend/types.js.map +1 -1
- package/dist/cache-analysis.d.ts +92 -0
- package/dist/cache-analysis.js +147 -0
- package/dist/cache-analysis.js.map +1 -0
- package/dist/cache-ledger.d.ts +108 -0
- package/dist/cache-ledger.js +230 -0
- package/dist/cache-ledger.js.map +1 -0
- package/dist/cache-prices.d.ts +32 -0
- package/dist/cache-prices.js +73 -0
- package/dist/cache-prices.js.map +1 -0
- package/dist/cache-scan.d.ts +39 -0
- package/dist/cache-scan.js +115 -0
- package/dist/cache-scan.js.map +1 -0
- package/dist/cache-service.d.ts +115 -0
- package/dist/cache-service.js +417 -0
- package/dist/cache-service.js.map +1 -0
- package/dist/channel/adapters/discord.d.ts +1 -0
- package/dist/channel/adapters/discord.js +29 -10
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +1 -0
- package/dist/channel/adapters/telegram.js +13 -1
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/types.d.ts +9 -1
- package/dist/classic-channel-manager.d.ts +18 -10
- package/dist/classic-channel-manager.js +45 -18
- package/dist/classic-channel-manager.js.map +1 -1
- package/dist/cli-entry.d.ts +19 -0
- package/dist/cli-entry.js +36 -0
- package/dist/cli-entry.js.map +1 -0
- package/dist/cli.js +471 -146
- package/dist/cli.js.map +1 -1
- package/dist/command-table.d.ts +5 -3
- package/dist/command-table.js +29 -27
- package/dist/command-table.js.map +1 -1
- package/dist/config-validator.js +46 -0
- package/dist/config-validator.js.map +1 -1
- package/dist/config.js +12 -0
- package/dist/config.js.map +1 -1
- package/dist/connection-secrets.d.ts +6 -0
- package/dist/connection-secrets.js +4 -0
- package/dist/connection-secrets.js.map +1 -1
- package/dist/daemon.d.ts +95 -4
- package/dist/daemon.js +748 -111
- package/dist/daemon.js.map +1 -1
- package/dist/darwin-memory.d.ts +6 -1
- package/dist/darwin-memory.js +17 -6
- package/dist/darwin-memory.js.map +1 -1
- package/dist/delivery-consumed-watch.d.ts +85 -0
- package/dist/delivery-consumed-watch.js +244 -0
- package/dist/delivery-consumed-watch.js.map +1 -0
- package/dist/delivery-outbox.d.ts +61 -0
- package/dist/delivery-outbox.js +120 -2
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/delivery-reconciliation.d.ts +14 -5
- package/dist/delivery-reconciliation.js +27 -4
- package/dist/delivery-reconciliation.js.map +1 -1
- package/dist/delivery-reconciliation.test.js +35 -1
- package/dist/delivery-reconciliation.test.js.map +1 -1
- package/dist/event-log.d.ts +4 -1
- package/dist/event-log.js +6 -3
- package/dist/event-log.js.map +1 -1
- package/dist/event-loop-watch.d.ts +28 -0
- package/dist/event-loop-watch.js +75 -3
- package/dist/event-loop-watch.js.map +1 -1
- package/dist/fleet-context.d.ts +22 -2
- package/dist/fleet-control-audit.d.ts +5 -0
- package/dist/fleet-control-audit.js +7 -0
- package/dist/fleet-control-audit.js.map +1 -1
- package/dist/fleet-level-config.d.ts +2 -2
- package/dist/fleet-level-config.js +28 -1
- package/dist/fleet-level-config.js.map +1 -1
- package/dist/fleet-manager.d.ts +339 -12
- package/dist/fleet-manager.js +2623 -461
- package/dist/fleet-manager.js.map +1 -1
- package/dist/full-restart.js +2 -3
- package/dist/full-restart.js.map +1 -1
- package/dist/general-knowledge/skills/persona-emoji/SKILL.md +3 -1
- package/dist/host-memory.d.ts +4 -0
- package/dist/host-memory.js.map +1 -1
- package/dist/install-lock.d.ts +49 -0
- package/dist/install-lock.js +200 -0
- package/dist/install-lock.js.map +1 -0
- package/dist/instance-config-impact.js +8 -2
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.d.ts +10 -5
- package/dist/instance-lifecycle.js +162 -53
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/locale.js +164 -16
- package/dist/locale.js.map +1 -1
- package/dist/login-controller.d.ts +2 -0
- package/dist/login-controller.js +17 -5
- package/dist/login-controller.js.map +1 -1
- package/dist/memory-pressure.d.ts +7 -8
- package/dist/memory-pressure.js +76 -15
- package/dist/memory-pressure.js.map +1 -1
- package/dist/monotonic-budget.d.ts +2 -0
- package/dist/monotonic-budget.js +28 -0
- package/dist/monotonic-budget.js.map +1 -0
- package/dist/needs-you-hub.d.ts +114 -0
- package/dist/needs-you-hub.js +370 -0
- package/dist/needs-you-hub.js.map +1 -0
- package/dist/needs-you-live.d.ts +116 -0
- package/dist/needs-you-live.js +228 -0
- package/dist/needs-you-live.js.map +1 -0
- package/dist/needs-you.d.ts +132 -0
- package/dist/needs-you.js +164 -0
- package/dist/needs-you.js.map +1 -0
- package/dist/node-version-guard.d.ts +30 -0
- package/dist/node-version-guard.js +50 -0
- package/dist/node-version-guard.js.map +1 -0
- package/dist/operator-control-paths.d.ts +4 -0
- package/dist/operator-control-paths.js +24 -0
- package/dist/operator-control-paths.js.map +1 -0
- package/dist/outbound-handlers.d.ts +2 -0
- package/dist/outbound-handlers.js +9 -2
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +5 -5
- package/dist/outbound-schemas.js +1 -1
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/package-preimage.d.ts +26 -0
- package/dist/package-preimage.js +96 -0
- package/dist/package-preimage.js.map +1 -0
- package/dist/profile-control.js +4 -20
- package/dist/profile-control.js.map +1 -1
- package/dist/public-web-gateway.d.ts +18 -0
- package/dist/public-web-gateway.js +141 -0
- package/dist/public-web-gateway.js.map +1 -0
- package/dist/public-web-link.d.ts +53 -0
- package/dist/public-web-link.js +175 -0
- package/dist/public-web-link.js.map +1 -0
- package/dist/quickstart-api.d.ts +8 -1
- package/dist/quickstart-api.js +86 -48
- package/dist/quickstart-api.js.map +1 -1
- package/dist/quickstart.d.ts +1 -0
- package/dist/quickstart.js +16 -6
- package/dist/quickstart.js.map +1 -1
- package/dist/restart-guard.d.ts +52 -0
- package/dist/restart-guard.js +109 -0
- package/dist/restart-guard.js.map +1 -0
- package/dist/scheduler/db.d.ts +7 -1
- package/dist/scheduler/db.js +44 -2
- package/dist/scheduler/db.js.map +1 -1
- package/dist/scheduler/index.d.ts +1 -1
- package/dist/scheduler/retry-label.d.ts +11 -0
- package/dist/scheduler/retry-label.js +24 -0
- package/dist/scheduler/retry-label.js.map +1 -0
- package/dist/scheduler/scheduler.d.ts +55 -2
- package/dist/scheduler/scheduler.js +220 -8
- package/dist/scheduler/scheduler.js.map +1 -1
- package/dist/scheduler/types.d.ts +22 -0
- package/dist/scheduler/types.js.map +1 -1
- package/dist/secret-store.d.ts +5 -0
- package/dist/secret-store.js +20 -0
- package/dist/secret-store.js.map +1 -1
- package/dist/service-activation.d.ts +152 -0
- package/dist/service-activation.js +407 -0
- package/dist/service-activation.js.map +1 -0
- package/dist/service-installer.d.ts +63 -1
- package/dist/service-installer.js +141 -16
- package/dist/service-installer.js.map +1 -1
- package/dist/service-plan.d.ts +65 -0
- package/dist/service-plan.js +108 -0
- package/dist/service-plan.js.map +1 -0
- package/dist/service-restart-selection.d.ts +6 -5
- package/dist/service-restart-selection.js +6 -5
- package/dist/service-restart-selection.js.map +1 -1
- package/dist/settings-api.d.ts +10 -2
- package/dist/settings-api.js +216 -72
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-baseline.d.ts +20 -0
- package/dist/settings-baseline.js +104 -0
- package/dist/settings-baseline.js.map +1 -0
- package/dist/settings-change.d.ts +21 -0
- package/dist/settings-change.js +161 -0
- package/dist/settings-change.js.map +1 -0
- package/dist/settings-confirmation.d.ts +105 -0
- package/dist/settings-confirmation.js +232 -0
- package/dist/settings-confirmation.js.map +1 -0
- package/dist/settings-control.d.ts +41 -0
- package/dist/settings-control.js +294 -0
- package/dist/settings-control.js.map +1 -0
- package/dist/settings-effect.d.ts +24 -0
- package/dist/settings-effect.js +169 -0
- package/dist/settings-effect.js.map +1 -0
- package/dist/settings-http-confirmation.d.ts +29 -0
- package/dist/settings-http-confirmation.js +123 -0
- package/dist/settings-http-confirmation.js.map +1 -0
- package/dist/settings-instance-patch.d.ts +4 -0
- package/dist/settings-instance-patch.js +26 -0
- package/dist/settings-instance-patch.js.map +1 -0
- package/dist/settings-request-capability.d.ts +16 -0
- package/dist/settings-request-capability.js +32 -0
- package/dist/settings-request-capability.js.map +1 -0
- package/dist/settings-transaction.d.ts +65 -0
- package/dist/settings-transaction.js +223 -0
- package/dist/settings-transaction.js.map +1 -0
- package/dist/setup-auth.d.ts +10 -18
- package/dist/setup-auth.js +14 -38
- package/dist/setup-auth.js.map +1 -1
- package/dist/setup-form.d.ts +1 -1
- package/dist/setup-form.js +75 -18
- package/dist/setup-form.js.map +1 -1
- package/dist/setup-guide.d.ts +13 -0
- package/dist/setup-guide.js +25 -0
- package/dist/setup-guide.js.map +1 -0
- package/dist/setup-host.d.ts +16 -0
- package/dist/setup-host.js +199 -35
- package/dist/setup-host.js.map +1 -1
- package/dist/setup-wizard.js +5 -1
- package/dist/setup-wizard.js.map +1 -1
- package/dist/slash-authz.d.ts +17 -3
- package/dist/slash-authz.js +5 -0
- package/dist/slash-authz.js.map +1 -1
- package/dist/spawn-gate.js +3 -3
- package/dist/spawn-gate.js.map +1 -1
- package/dist/sqlite-open-errors.d.ts +12 -0
- package/dist/sqlite-open-errors.js +13 -0
- package/dist/sqlite-open-errors.js.map +1 -0
- package/dist/statusline-watcher.d.ts +5 -0
- package/dist/statusline-watcher.js +2 -0
- package/dist/statusline-watcher.js.map +1 -1
- package/dist/steer-capability.d.ts +6 -0
- package/dist/steer-capability.js +8 -0
- package/dist/steer-capability.js.map +1 -1
- package/dist/sync-work-attribution.d.ts +14 -0
- package/dist/sync-work-attribution.js +43 -8
- package/dist/sync-work-attribution.js.map +1 -1
- package/dist/tips.js +10 -5
- package/dist/tips.js.map +1 -1
- package/dist/tmux-control.d.ts +57 -5
- package/dist/tmux-control.js +379 -61
- package/dist/tmux-control.js.map +1 -1
- package/dist/tmux-manager.d.ts +20 -6
- package/dist/tmux-manager.js +90 -47
- package/dist/tmux-manager.js.map +1 -1
- package/dist/tmux-read.d.ts +59 -0
- package/dist/tmux-read.js +213 -0
- package/dist/tmux-read.js.map +1 -0
- package/dist/topic-commands.d.ts +21 -10
- package/dist/topic-commands.js +205 -63
- package/dist/topic-commands.js.map +1 -1
- package/dist/transcript-delta-reader.js +2 -1
- package/dist/transcript-delta-reader.js.map +1 -1
- package/dist/tunnel/cloudflared-install.d.ts +2 -0
- package/dist/tunnel/cloudflared-install.js +1 -1
- package/dist/tunnel/cloudflared-install.js.map +1 -1
- package/dist/tunnel/purpose-lane.d.ts +14 -0
- package/dist/tunnel/purpose-lane.js +43 -0
- package/dist/tunnel/purpose-lane.js.map +1 -0
- package/dist/turn-reply-guard.d.ts +2 -0
- package/dist/turn-reply-guard.js +10 -1
- package/dist/turn-reply-guard.js.map +1 -1
- package/dist/types.d.ts +33 -0
- package/dist/ui/app.html +18 -0
- package/dist/ui/chat-commands.js +43 -0
- package/dist/ui/chat-render.js +478 -0
- package/dist/ui/chat-store.js +318 -0
- package/dist/ui/chat-strings.js +89 -0
- package/dist/ui/chat-thread.js +246 -0
- package/dist/ui/chat-tour.js +116 -0
- package/dist/ui/fleet-cache.js +168 -0
- package/dist/ui/fleet-org.js +142 -0
- package/dist/ui/panel-chat.js +504 -0
- package/dist/ui/panel-fleet.js +427 -0
- package/dist/ui/panel-needs.js +202 -0
- package/dist/ui/panel-settings.js +754 -0
- package/dist/ui/preview-panel.js +202 -0
- package/dist/ui/preview.js +270 -0
- package/dist/ui/settings-apply.js +239 -0
- package/dist/ui/settings-confirm.js +186 -0
- package/dist/ui/settings-dialogs.js +546 -0
- package/dist/ui/settings-model.js +277 -0
- package/dist/ui/settings-strings.js +654 -0
- package/dist/ui/settings-wizard.js +140 -0
- package/dist/ui/shared/agend-auth.js +93 -0
- package/dist/ui/shared/app-ctx.js +80 -0
- package/dist/ui/shared/app-html.js +9 -0
- package/dist/ui/shared/app-i18n.js +95 -0
- package/dist/ui/shared/app-nav.js +73 -0
- package/dist/ui/shared/app-needs.js +114 -0
- package/dist/ui/shared/app-route.js +73 -0
- package/dist/ui/shared/app-session.js +66 -0
- package/dist/ui/shared/app-shell.js +346 -0
- package/dist/ui/shared/app-store.js +51 -0
- package/dist/ui/shared/app-stream.js +201 -0
- package/dist/ui/shared/app.css +820 -0
- package/dist/ui/shared/app.js +88 -0
- package/dist/ui/shared/fonts/OFL.txt +92 -0
- package/dist/ui/shared/fonts/README.md +21 -0
- package/dist/ui/shared/fonts/inter.woff2 +0 -0
- package/dist/ui/shared/htm.module.js +1 -0
- package/dist/ui/shared/panel-view.js +655 -0
- package/dist/ui/shared/preact-hooks.module.js +2 -0
- package/dist/ui/shared/preact.module.js +2 -0
- package/dist/ui/shared/signin.js +142 -0
- package/dist/ui/shared/theme.js +29 -0
- package/dist/ui/shared/tokens.css +153 -0
- package/dist/ui/shared/ui-confirm.js +43 -0
- package/dist/ui/shared/ui-dialog.js +45 -0
- package/dist/ui/shared/ui-icons.js +63 -0
- package/dist/ui/shared/ui-menu.js +39 -0
- package/dist/ui/shared/ui-states.js +20 -0
- package/dist/ui/shared/ui-toast.js +18 -0
- package/dist/ui/shared/vendor/htm.LICENSE +202 -0
- package/dist/ui/shared/vendor/preact.LICENSE +21 -0
- package/dist/ui/shared/vendor/vendor.json +39 -0
- package/dist/ui/shared/view-strings.js +309 -0
- package/dist/ui/signin.html +52 -0
- package/dist/update-check.d.ts +6 -2
- package/dist/update-check.js +12 -3
- package/dist/update-check.js.map +1 -1
- package/dist/update-dispatch.d.ts +28 -0
- package/dist/update-dispatch.js +73 -0
- package/dist/update-dispatch.js.map +1 -0
- package/dist/update-install.d.ts +129 -0
- package/dist/update-install.js +262 -0
- package/dist/update-install.js.map +1 -0
- package/dist/upgrade-notices.d.ts +17 -0
- package/dist/upgrade-notices.js +93 -0
- package/dist/upgrade-notices.js.map +1 -0
- package/dist/usage/statusline-usage.d.ts +2 -0
- package/dist/usage/statusline-usage.js +14 -10
- package/dist/usage/statusline-usage.js.map +1 -1
- package/dist/view-api.d.ts +6 -1
- package/dist/view-api.js +62 -51
- package/dist/view-api.js.map +1 -1
- package/dist/web-api.d.ts +85 -14
- package/dist/web-api.js +557 -77
- package/dist/web-api.js.map +1 -1
- package/dist/web-auth.d.ts +71 -27
- package/dist/web-auth.js +169 -69
- package/dist/web-auth.js.map +1 -1
- package/dist/web-body.d.ts +3 -0
- package/dist/web-body.js +55 -0
- package/dist/web-body.js.map +1 -0
- package/dist/web-channel-echo.d.ts +9 -0
- package/dist/web-channel-echo.js +55 -0
- package/dist/web-channel-echo.js.map +1 -0
- package/dist/web-chat-history.d.ts +125 -0
- package/dist/web-chat-history.js +184 -0
- package/dist/web-chat-history.js.map +1 -0
- package/dist/web-commands.d.ts +77 -0
- package/dist/web-commands.js +81 -0
- package/dist/web-commands.js.map +1 -0
- package/dist/web-continuation.d.ts +7 -0
- package/dist/web-continuation.js +22 -0
- package/dist/web-continuation.js.map +1 -0
- package/dist/web-host-guard.d.ts +34 -0
- package/dist/web-host-guard.js +57 -1
- package/dist/web-host-guard.js.map +1 -1
- package/dist/web-login.d.ts +72 -0
- package/dist/web-login.js +132 -0
- package/dist/web-login.js.map +1 -0
- package/dist/web-org.d.ts +55 -0
- package/dist/web-org.js +67 -0
- package/dist/web-org.js.map +1 -0
- package/dist/web-preview.d.ts +77 -0
- package/dist/web-preview.js +240 -0
- package/dist/web-preview.js.map +1 -0
- package/dist/web-request-context.d.ts +10 -0
- package/dist/web-request-context.js +13 -0
- package/dist/web-request-context.js.map +1 -0
- package/dist/web-session.d.ts +167 -0
- package/dist/web-session.js +389 -0
- package/dist/web-session.js.map +1 -0
- package/dist/web-shell-routes.d.ts +59 -0
- package/dist/web-shell-routes.js +96 -0
- package/dist/web-shell-routes.js.map +1 -0
- package/dist/web-upload.d.ts +183 -0
- package/dist/web-upload.js +468 -0
- package/dist/web-upload.js.map +1 -0
- package/dist/website-tips-renderer.d.ts +2 -2
- package/dist/website-tips-renderer.js +3 -3
- package/dist/website-tips-renderer.js.map +1 -1
- package/launcher/agend +110 -0
- package/launcher/agend-agent +110 -0
- package/launcher/agend-agent.cjs +4 -0
- package/launcher/agend.cjs +4 -0
- package/launcher/install-admission.cjs +105 -0
- package/launcher/launch.cjs +64 -0
- package/launcher/postinstall.cjs +114 -0
- package/launcher/run-in-process.cjs +13 -0
- package/launcher/runtime-platform.cjs +86 -0
- package/launcher/runtime-select.cjs +217 -0
- package/package.json +18 -7
- package/scripts/preinstall-guard.cjs +55 -0
- package/templates/launchd.plist.ejs +2 -2
- package/templates/systemd.service.ejs +1 -1
- package/dist/ui/dashboard.html +0 -774
- package/dist/ui/settings.html +0 -2165
- package/dist/ui/view.html +0 -1139
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
|
+
export type SessionSurface = "local" | "gateway";
|
|
3
|
+
/** `admin` may do everything the panel can; `read` is reserved for the read-only tier (design D6). */
|
|
4
|
+
export type SessionTier = "admin" | "read";
|
|
5
|
+
export interface SessionPolicy {
|
|
6
|
+
/** Hard cap from creation; activity never extends it. */
|
|
7
|
+
readonly absoluteMs: number;
|
|
8
|
+
/** Slides with activity, never past the absolute cap. */
|
|
9
|
+
readonly idleMs: number;
|
|
10
|
+
}
|
|
11
|
+
/** Design D4. The gateway numbers are shorter because a session there is reachable from the internet. */
|
|
12
|
+
export declare const DEFAULT_SESSION_POLICY: Readonly<Record<SessionSurface, SessionPolicy>>;
|
|
13
|
+
/** More than this and the least recently used one is dropped. */
|
|
14
|
+
export declare const MAX_WEB_SESSIONS = 8;
|
|
15
|
+
/** `lastSeen` is only written to disk this often; creation and revocation always write. */
|
|
16
|
+
export declare const SESSION_PERSIST_DEBOUNCE_MS: number;
|
|
17
|
+
export interface SessionRecord {
|
|
18
|
+
/** Opaque, safe to show and to name in a revoke request. Not the id and not its hash. */
|
|
19
|
+
readonly handle: string;
|
|
20
|
+
readonly idHash: string;
|
|
21
|
+
readonly created: number;
|
|
22
|
+
lastSeen: number;
|
|
23
|
+
readonly absoluteExpiry: number;
|
|
24
|
+
idleExpiry: number;
|
|
25
|
+
readonly tier: SessionTier;
|
|
26
|
+
readonly surface: SessionSurface;
|
|
27
|
+
readonly exposureId?: string;
|
|
28
|
+
/** What the person sees in the device list. Display only, never an input to a decision. */
|
|
29
|
+
readonly label: string;
|
|
30
|
+
/** `sha256(web.token)` when the session was made; a mismatch means the token was rotated. */
|
|
31
|
+
readonly tokenEpoch: string;
|
|
32
|
+
}
|
|
33
|
+
export interface SessionSummary {
|
|
34
|
+
readonly handle: string;
|
|
35
|
+
readonly created: number;
|
|
36
|
+
readonly lastSeen: number;
|
|
37
|
+
readonly absoluteExpiry: number;
|
|
38
|
+
readonly idleExpiry: number;
|
|
39
|
+
readonly tier: SessionTier;
|
|
40
|
+
readonly surface: SessionSurface;
|
|
41
|
+
readonly label: string;
|
|
42
|
+
readonly current: boolean;
|
|
43
|
+
}
|
|
44
|
+
export declare function sessionIdHash(sessionId: string): string;
|
|
45
|
+
/** Binds a session to the `web.token` it was issued under. */
|
|
46
|
+
export declare function tokenEpoch(webToken: string): string;
|
|
47
|
+
/**
|
|
48
|
+
* The value a page sends back in `X-Agend-CSRF`.
|
|
49
|
+
*
|
|
50
|
+
* Derived from the session id so it needs no storage, and different from the
|
|
51
|
+
* cookie value so it can be handed to script that must never see the cookie.
|
|
52
|
+
*/
|
|
53
|
+
export declare function csrfTokenFor(sessionId: string): string;
|
|
54
|
+
export declare function isWellFormedSessionId(value: string): boolean;
|
|
55
|
+
/** Printable, bounded, and never empty: a label is drawn in a page by other people's code. */
|
|
56
|
+
export declare function sanitizeLabel(raw: string): string;
|
|
57
|
+
/** "Chrome on macOS" out of a User-Agent, coarse on purpose — it is a hint for a person, not a fingerprint. */
|
|
58
|
+
export declare function labelFromUserAgent(userAgent: string | undefined): string;
|
|
59
|
+
/** The three file operations the store makes, so a test can make one of them fail. */
|
|
60
|
+
export interface SessionFileOps {
|
|
61
|
+
writeFileSync: typeof writeFileSync;
|
|
62
|
+
renameSync: typeof renameSync;
|
|
63
|
+
unlinkSync: typeof unlinkSync;
|
|
64
|
+
}
|
|
65
|
+
export interface WebSessionStoreOptions {
|
|
66
|
+
/** Where `web-sessions.json` lives. Omit for a memory-only store (tests). */
|
|
67
|
+
readonly dataDir?: string;
|
|
68
|
+
/** Override individual file operations (tests only). */
|
|
69
|
+
readonly fileOps?: Partial<SessionFileOps>;
|
|
70
|
+
readonly now?: () => number;
|
|
71
|
+
readonly policy?: Partial<Record<SessionSurface, SessionPolicy>>;
|
|
72
|
+
readonly maxSessions?: number;
|
|
73
|
+
readonly onWarn?: (message: string) => void;
|
|
74
|
+
}
|
|
75
|
+
export declare class WebSessionStore {
|
|
76
|
+
private readonly pending;
|
|
77
|
+
private readonly byHash;
|
|
78
|
+
private readonly now;
|
|
79
|
+
private readonly policy;
|
|
80
|
+
private readonly maxSessions;
|
|
81
|
+
private readonly path;
|
|
82
|
+
private readonly warn;
|
|
83
|
+
private dirty;
|
|
84
|
+
private lastPersist;
|
|
85
|
+
private readonly ops;
|
|
86
|
+
/** The sessions the file on disk holds right now, as far as this process knows. */
|
|
87
|
+
private persisted;
|
|
88
|
+
constructor(opts?: WebSessionStoreOptions);
|
|
89
|
+
get size(): number;
|
|
90
|
+
/** How long a session on this surface may sit unused before it ends. For showing people, not for deciding. */
|
|
91
|
+
idleWindowMs(surface: SessionSurface): number;
|
|
92
|
+
/**
|
|
93
|
+
* Make a session. The id is returned once and never stored; the caller puts it
|
|
94
|
+
* in a cookie and forgets it.
|
|
95
|
+
*/
|
|
96
|
+
create(input: {
|
|
97
|
+
tier: SessionTier;
|
|
98
|
+
surface: SessionSurface;
|
|
99
|
+
label: string;
|
|
100
|
+
tokenEpoch: string;
|
|
101
|
+
exposureId?: string;
|
|
102
|
+
pending?: boolean;
|
|
103
|
+
}): {
|
|
104
|
+
sessionId: string;
|
|
105
|
+
record: SessionRecord;
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* The session this id belongs to, if it is still good.
|
|
109
|
+
*
|
|
110
|
+
* Expiry and a rotated `web.token` are decided here, on the server, on every
|
|
111
|
+
* call — nothing is taken on the cookie's word. An expired or stale record is
|
|
112
|
+
* deleted as it is found so it cannot be revived by a clock or config change.
|
|
113
|
+
*
|
|
114
|
+
* `touch: false` is for a stream that re-checks itself on a timer: counting
|
|
115
|
+
* that as activity would keep an idle session alive for as long as a tab is
|
|
116
|
+
* open.
|
|
117
|
+
*/
|
|
118
|
+
authenticate(sessionId: string | undefined, currentEpoch: string, opts?: {
|
|
119
|
+
touch?: boolean;
|
|
120
|
+
surface?: SessionSurface;
|
|
121
|
+
exposureId?: string;
|
|
122
|
+
}): SessionRecord | null;
|
|
123
|
+
/** Recheck a retained server record without retaining/replaying its bearer cookie. */
|
|
124
|
+
isCurrent(record: SessionRecord, currentEpoch: string): boolean;
|
|
125
|
+
/** A candidate has no usable credential until the public notice and final fence passed. */
|
|
126
|
+
activate(sessionId: string): boolean;
|
|
127
|
+
revokeExposure(exposureId: string): {
|
|
128
|
+
count: number;
|
|
129
|
+
durable: boolean;
|
|
130
|
+
};
|
|
131
|
+
/** Every live session, newest activity first. `currentIdHash` marks the caller's own. */
|
|
132
|
+
list(currentIdHash?: string): SessionSummary[];
|
|
133
|
+
revokeByHandle(handle: string): {
|
|
134
|
+
found: boolean;
|
|
135
|
+
durable: boolean;
|
|
136
|
+
};
|
|
137
|
+
revokeById(sessionId: string): {
|
|
138
|
+
removed: boolean;
|
|
139
|
+
durable: boolean;
|
|
140
|
+
};
|
|
141
|
+
/** Every session: how many there were, and whether the revocation is durable. */
|
|
142
|
+
revokeAll(): {
|
|
143
|
+
count: number;
|
|
144
|
+
durable: boolean;
|
|
145
|
+
};
|
|
146
|
+
/** Write anything pending. Called on shutdown. */
|
|
147
|
+
flush(): void;
|
|
148
|
+
private purgeExpired;
|
|
149
|
+
private evictLeastRecentlyUsed;
|
|
150
|
+
private persistIfDue;
|
|
151
|
+
/**
|
|
152
|
+
* Write the store. Returns whether it is DURABLE: whether a restart is now unable to bring back a
|
|
153
|
+
* session memory has dropped — true when the file was replaced, when the old file held nothing memory
|
|
154
|
+
* dropped (it is merely behind), or when it was removed; false only when a stale file is still there.
|
|
155
|
+
*
|
|
156
|
+
* The failure path matters more than the success path. Memory forgets a revoked
|
|
157
|
+
* session at once; if the file cannot be replaced it still holds that session, and a
|
|
158
|
+
* restart would read it back — a revoked session, alive again. So when a write fails:
|
|
159
|
+
* the store stays owing the disk a write (`dirty`, retried by the next change, the
|
|
160
|
+
* next debounce and shutdown), and if the old file holds anything memory has since
|
|
161
|
+
* dropped, the old file is removed. A restart then finds no sessions and everyone
|
|
162
|
+
* signs in again, which is the failure that is safe; it says so, loudly, when even
|
|
163
|
+
* that removal is impossible.
|
|
164
|
+
*/
|
|
165
|
+
private persistNow;
|
|
166
|
+
private load;
|
|
167
|
+
}
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side web sessions.
|
|
3
|
+
*
|
|
4
|
+
* The cookie is an opaque 256-bit random id and nothing else: it is not derived
|
|
5
|
+
* from `web.token`, from a login code, or from anything a guess could be
|
|
6
|
+
* checked against offline. The server keeps only `sha256(id)`, so neither the
|
|
7
|
+
* process nor the file on disk can be replayed as a cookie.
|
|
8
|
+
*
|
|
9
|
+
* What that buys over the old `sha256(web.token)` cookie:
|
|
10
|
+
* - it expires on the *server* (absolute cap + sliding idle), not only in the
|
|
11
|
+
* browser's `Max-Age`;
|
|
12
|
+
* - one device can be signed out without signing out the rest;
|
|
13
|
+
* - a stolen one is one session, not a value shared by every device.
|
|
14
|
+
*
|
|
15
|
+
* `tokenEpoch` keeps the property the old cookie had for free: `agend web-token
|
|
16
|
+
* rotate` (a separate process) changes `web.token`, every record made under the
|
|
17
|
+
* old one stops matching on its next request, and no restart is needed.
|
|
18
|
+
*
|
|
19
|
+
* Sessions are persisted (hashes only) because Settings can restart the fleet,
|
|
20
|
+
* and a session that lived only in memory would sign the operator out at the
|
|
21
|
+
* moment they pressed the button — with no way back except a new chat code.
|
|
22
|
+
*
|
|
23
|
+
* See `docs/design/web-unification-secure-login.zh-TW.md` §3.3.
|
|
24
|
+
*/
|
|
25
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
26
|
+
import { chmodSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
27
|
+
import { dirname, join } from "node:path";
|
|
28
|
+
const HOUR = 60 * 60 * 1000;
|
|
29
|
+
const MINUTE = 60 * 1000;
|
|
30
|
+
/** Design D4. The gateway numbers are shorter because a session there is reachable from the internet. */
|
|
31
|
+
export const DEFAULT_SESSION_POLICY = {
|
|
32
|
+
local: { absoluteMs: 12 * HOUR, idleMs: 2 * HOUR },
|
|
33
|
+
gateway: { absoluteMs: 4 * HOUR, idleMs: 30 * MINUTE },
|
|
34
|
+
};
|
|
35
|
+
/** More than this and the least recently used one is dropped. */
|
|
36
|
+
export const MAX_WEB_SESSIONS = 8;
|
|
37
|
+
/** `lastSeen` is only written to disk this often; creation and revocation always write. */
|
|
38
|
+
export const SESSION_PERSIST_DEBOUNCE_MS = 60 * 1000;
|
|
39
|
+
const SESSION_ID_PATTERN = /^[0-9a-f]{64}$/;
|
|
40
|
+
const HASH_PATTERN = /^[0-9a-f]{64}$/;
|
|
41
|
+
const HANDLE_PATTERN = /^[0-9a-f]{16}$/;
|
|
42
|
+
const LABEL_MAX = 80;
|
|
43
|
+
export function sessionIdHash(sessionId) {
|
|
44
|
+
return createHash("sha256").update(`agend-web-session-id-v2:${sessionId}`).digest("hex");
|
|
45
|
+
}
|
|
46
|
+
/** Binds a session to the `web.token` it was issued under. */
|
|
47
|
+
export function tokenEpoch(webToken) {
|
|
48
|
+
return createHash("sha256").update(`agend-web-epoch-v1:${webToken}`).digest("hex");
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The value a page sends back in `X-Agend-CSRF`.
|
|
52
|
+
*
|
|
53
|
+
* Derived from the session id so it needs no storage, and different from the
|
|
54
|
+
* cookie value so it can be handed to script that must never see the cookie.
|
|
55
|
+
*/
|
|
56
|
+
export function csrfTokenFor(sessionId) {
|
|
57
|
+
return createHash("sha256").update(`agend-web-csrf-v1:${sessionId}`).digest("hex");
|
|
58
|
+
}
|
|
59
|
+
export function isWellFormedSessionId(value) {
|
|
60
|
+
return SESSION_ID_PATTERN.test(value);
|
|
61
|
+
}
|
|
62
|
+
/** Printable, bounded, and never empty: a label is drawn in a page by other people's code. */
|
|
63
|
+
export function sanitizeLabel(raw) {
|
|
64
|
+
const cleaned = raw.replace(/[\u0000-\u001f\u007f<>"'`&]/g, "").replace(/\s+/g, " ").trim().slice(0, LABEL_MAX);
|
|
65
|
+
return cleaned || "unknown device";
|
|
66
|
+
}
|
|
67
|
+
/** "Chrome on macOS" out of a User-Agent, coarse on purpose — it is a hint for a person, not a fingerprint. */
|
|
68
|
+
export function labelFromUserAgent(userAgent) {
|
|
69
|
+
if (!userAgent)
|
|
70
|
+
return "unknown device";
|
|
71
|
+
const ua = userAgent;
|
|
72
|
+
const browser = /Edg\//.test(ua) ? "Edge"
|
|
73
|
+
: /OPR\/|Opera/.test(ua) ? "Opera"
|
|
74
|
+
: /Firefox\//.test(ua) ? "Firefox"
|
|
75
|
+
: /Chrome\/|CriOS\//.test(ua) ? "Chrome"
|
|
76
|
+
: /Safari\//.test(ua) ? "Safari"
|
|
77
|
+
: /curl\//.test(ua) ? "curl"
|
|
78
|
+
: "browser";
|
|
79
|
+
const os = /iPhone|iPad|iOS/.test(ua) ? "iOS"
|
|
80
|
+
: /Android/.test(ua) ? "Android"
|
|
81
|
+
: /Mac OS X|Macintosh/.test(ua) ? "macOS"
|
|
82
|
+
: /Windows/.test(ua) ? "Windows"
|
|
83
|
+
: /Linux|X11/.test(ua) ? "Linux"
|
|
84
|
+
: "";
|
|
85
|
+
return sanitizeLabel(os ? `${browser} on ${os}` : browser);
|
|
86
|
+
}
|
|
87
|
+
function isRecord(value) {
|
|
88
|
+
if (!value || typeof value !== "object")
|
|
89
|
+
return false;
|
|
90
|
+
const v = value;
|
|
91
|
+
return typeof v.handle === "string" && HANDLE_PATTERN.test(v.handle)
|
|
92
|
+
&& typeof v.idHash === "string" && HASH_PATTERN.test(v.idHash)
|
|
93
|
+
&& typeof v.created === "number" && Number.isFinite(v.created)
|
|
94
|
+
&& typeof v.lastSeen === "number" && Number.isFinite(v.lastSeen)
|
|
95
|
+
&& typeof v.absoluteExpiry === "number" && Number.isFinite(v.absoluteExpiry)
|
|
96
|
+
&& typeof v.idleExpiry === "number" && Number.isFinite(v.idleExpiry)
|
|
97
|
+
&& (v.tier === "admin" || v.tier === "read")
|
|
98
|
+
&& (v.surface === "local" || v.surface === "gateway")
|
|
99
|
+
&& (v.exposureId === undefined || (typeof v.exposureId === "string" && /^[0-9a-f]{32}$/.test(v.exposureId)))
|
|
100
|
+
&& typeof v.label === "string" && v.label.length <= LABEL_MAX
|
|
101
|
+
&& typeof v.tokenEpoch === "string" && HASH_PATTERN.test(v.tokenEpoch);
|
|
102
|
+
}
|
|
103
|
+
export class WebSessionStore {
|
|
104
|
+
pending = new Set();
|
|
105
|
+
byHash = new Map();
|
|
106
|
+
now;
|
|
107
|
+
policy;
|
|
108
|
+
maxSessions;
|
|
109
|
+
path;
|
|
110
|
+
warn;
|
|
111
|
+
dirty = false;
|
|
112
|
+
lastPersist = 0;
|
|
113
|
+
ops;
|
|
114
|
+
/** The sessions the file on disk holds right now, as far as this process knows. */
|
|
115
|
+
persisted = new Set();
|
|
116
|
+
constructor(opts = {}) {
|
|
117
|
+
this.now = opts.now ?? Date.now;
|
|
118
|
+
this.policy = {
|
|
119
|
+
local: opts.policy?.local ?? DEFAULT_SESSION_POLICY.local,
|
|
120
|
+
gateway: opts.policy?.gateway ?? DEFAULT_SESSION_POLICY.gateway,
|
|
121
|
+
};
|
|
122
|
+
this.maxSessions = opts.maxSessions ?? MAX_WEB_SESSIONS;
|
|
123
|
+
this.path = opts.dataDir ? join(opts.dataDir, "web-sessions.json") : null;
|
|
124
|
+
this.warn = opts.onWarn ?? (() => { });
|
|
125
|
+
this.ops = { writeFileSync, renameSync, unlinkSync, ...opts.fileOps };
|
|
126
|
+
this.load();
|
|
127
|
+
}
|
|
128
|
+
get size() { return this.byHash.size; }
|
|
129
|
+
/** How long a session on this surface may sit unused before it ends. For showing people, not for deciding. */
|
|
130
|
+
idleWindowMs(surface) { return this.policy[surface].idleMs; }
|
|
131
|
+
/**
|
|
132
|
+
* Make a session. The id is returned once and never stored; the caller puts it
|
|
133
|
+
* in a cookie and forgets it.
|
|
134
|
+
*/
|
|
135
|
+
create(input) {
|
|
136
|
+
const now = this.now();
|
|
137
|
+
this.purgeExpired(now);
|
|
138
|
+
const sessionId = randomBytes(32).toString("hex");
|
|
139
|
+
const policy = this.policy[input.surface];
|
|
140
|
+
const absoluteExpiry = now + policy.absoluteMs;
|
|
141
|
+
const record = {
|
|
142
|
+
handle: randomBytes(8).toString("hex"),
|
|
143
|
+
idHash: sessionIdHash(sessionId),
|
|
144
|
+
created: now,
|
|
145
|
+
lastSeen: now,
|
|
146
|
+
absoluteExpiry,
|
|
147
|
+
idleExpiry: Math.min(now + policy.idleMs, absoluteExpiry),
|
|
148
|
+
tier: input.tier,
|
|
149
|
+
surface: input.surface,
|
|
150
|
+
...(input.exposureId ? { exposureId: input.exposureId } : {}),
|
|
151
|
+
label: sanitizeLabel(input.label),
|
|
152
|
+
tokenEpoch: input.tokenEpoch,
|
|
153
|
+
};
|
|
154
|
+
this.byHash.set(record.idHash, record);
|
|
155
|
+
if (input.pending)
|
|
156
|
+
this.pending.add(record.idHash);
|
|
157
|
+
while (this.byHash.size > this.maxSessions)
|
|
158
|
+
this.evictLeastRecentlyUsed();
|
|
159
|
+
this.persistNow();
|
|
160
|
+
return { sessionId, record };
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* The session this id belongs to, if it is still good.
|
|
164
|
+
*
|
|
165
|
+
* Expiry and a rotated `web.token` are decided here, on the server, on every
|
|
166
|
+
* call — nothing is taken on the cookie's word. An expired or stale record is
|
|
167
|
+
* deleted as it is found so it cannot be revived by a clock or config change.
|
|
168
|
+
*
|
|
169
|
+
* `touch: false` is for a stream that re-checks itself on a timer: counting
|
|
170
|
+
* that as activity would keep an idle session alive for as long as a tab is
|
|
171
|
+
* open.
|
|
172
|
+
*/
|
|
173
|
+
authenticate(sessionId, currentEpoch, opts = {}) {
|
|
174
|
+
if (!sessionId || !SESSION_ID_PATTERN.test(sessionId))
|
|
175
|
+
return null;
|
|
176
|
+
const record = this.byHash.get(sessionIdHash(sessionId));
|
|
177
|
+
if (!record || this.pending.has(record.idHash))
|
|
178
|
+
return null;
|
|
179
|
+
if (record.surface !== (opts.surface ?? "local")
|
|
180
|
+
|| (record.surface === "gateway" && (!opts.exposureId || record.exposureId !== opts.exposureId)))
|
|
181
|
+
return null;
|
|
182
|
+
const now = this.now();
|
|
183
|
+
if (now >= record.absoluteExpiry || now >= record.idleExpiry || record.tokenEpoch !== currentEpoch) {
|
|
184
|
+
this.byHash.delete(record.idHash);
|
|
185
|
+
this.persistNow();
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
if (opts.touch !== false) {
|
|
189
|
+
record.lastSeen = now;
|
|
190
|
+
record.idleExpiry = Math.min(now + this.policy[record.surface].idleMs, record.absoluteExpiry);
|
|
191
|
+
this.dirty = true;
|
|
192
|
+
this.persistIfDue(now);
|
|
193
|
+
}
|
|
194
|
+
return record;
|
|
195
|
+
}
|
|
196
|
+
/** Recheck a retained server record without retaining/replaying its bearer cookie. */
|
|
197
|
+
isCurrent(record, currentEpoch) {
|
|
198
|
+
return this.byHash.get(record.idHash) === record && !this.pending.has(record.idHash)
|
|
199
|
+
&& record.tokenEpoch === currentEpoch && this.now() < record.absoluteExpiry && this.now() < record.idleExpiry;
|
|
200
|
+
}
|
|
201
|
+
/** A candidate has no usable credential until the public notice and final fence passed. */
|
|
202
|
+
activate(sessionId) {
|
|
203
|
+
const hash = sessionIdHash(sessionId);
|
|
204
|
+
if (!this.byHash.has(hash) || !this.pending.delete(hash))
|
|
205
|
+
return false;
|
|
206
|
+
this.persistNow();
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
revokeExposure(exposureId) {
|
|
210
|
+
let count = 0;
|
|
211
|
+
for (const [hash, record] of this.byHash) {
|
|
212
|
+
if (record.surface === "gateway" && record.exposureId === exposureId) {
|
|
213
|
+
this.byHash.delete(hash);
|
|
214
|
+
this.pending.delete(hash);
|
|
215
|
+
count++;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return { count, durable: this.persistNow() };
|
|
219
|
+
}
|
|
220
|
+
/** Every live session, newest activity first. `currentIdHash` marks the caller's own. */
|
|
221
|
+
list(currentIdHash) {
|
|
222
|
+
this.purgeExpired(this.now());
|
|
223
|
+
return [...this.byHash.values()].filter(r => !this.pending.has(r.idHash))
|
|
224
|
+
.sort((a, b) => b.lastSeen - a.lastSeen)
|
|
225
|
+
.map(r => ({
|
|
226
|
+
handle: r.handle,
|
|
227
|
+
created: r.created,
|
|
228
|
+
lastSeen: r.lastSeen,
|
|
229
|
+
absoluteExpiry: r.absoluteExpiry,
|
|
230
|
+
idleExpiry: r.idleExpiry,
|
|
231
|
+
tier: r.tier,
|
|
232
|
+
surface: r.surface,
|
|
233
|
+
label: r.label,
|
|
234
|
+
current: r.idHash === currentIdHash,
|
|
235
|
+
}));
|
|
236
|
+
}
|
|
237
|
+
/*
|
|
238
|
+
* Every revocation says whether it is DURABLE: whether a restart could bring the revoked session back.
|
|
239
|
+
* Memory forgets it at once; the file may not. A caller that reports "signed out" must not report it
|
|
240
|
+
* when it is not durable — the operator would believe a session is dead that the next start revives.
|
|
241
|
+
*/
|
|
242
|
+
revokeByHandle(handle) {
|
|
243
|
+
if (!HANDLE_PATTERN.test(handle))
|
|
244
|
+
return { found: false, durable: true };
|
|
245
|
+
for (const [hash, record] of this.byHash) {
|
|
246
|
+
if (record.handle === handle) {
|
|
247
|
+
this.byHash.delete(hash);
|
|
248
|
+
this.pending.delete(hash);
|
|
249
|
+
return { found: true, durable: this.persistNow() };
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return { found: false, durable: true };
|
|
253
|
+
}
|
|
254
|
+
revokeById(sessionId) {
|
|
255
|
+
if (!SESSION_ID_PATTERN.test(sessionId))
|
|
256
|
+
return { removed: false, durable: true };
|
|
257
|
+
const hash = sessionIdHash(sessionId);
|
|
258
|
+
this.pending.delete(hash);
|
|
259
|
+
const removed = this.byHash.delete(hash);
|
|
260
|
+
return { removed, durable: removed ? this.persistNow() : true };
|
|
261
|
+
}
|
|
262
|
+
/** Every session: how many there were, and whether the revocation is durable. */
|
|
263
|
+
revokeAll() {
|
|
264
|
+
const count = this.byHash.size;
|
|
265
|
+
this.byHash.clear();
|
|
266
|
+
this.pending.clear();
|
|
267
|
+
return { count, durable: this.persistNow() };
|
|
268
|
+
}
|
|
269
|
+
/** Write anything pending. Called on shutdown. */
|
|
270
|
+
flush() {
|
|
271
|
+
if (this.dirty)
|
|
272
|
+
this.persistNow();
|
|
273
|
+
}
|
|
274
|
+
purgeExpired(now) {
|
|
275
|
+
let removed = false;
|
|
276
|
+
for (const [hash, record] of this.byHash) {
|
|
277
|
+
if (now >= record.absoluteExpiry || now >= record.idleExpiry) {
|
|
278
|
+
this.byHash.delete(hash);
|
|
279
|
+
this.pending.delete(hash);
|
|
280
|
+
removed = true;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if (removed)
|
|
284
|
+
this.dirty = true;
|
|
285
|
+
}
|
|
286
|
+
evictLeastRecentlyUsed() {
|
|
287
|
+
let oldest = null;
|
|
288
|
+
for (const record of this.byHash.values()) {
|
|
289
|
+
if (!oldest || record.lastSeen < oldest.lastSeen)
|
|
290
|
+
oldest = record;
|
|
291
|
+
}
|
|
292
|
+
if (oldest) {
|
|
293
|
+
this.byHash.delete(oldest.idHash);
|
|
294
|
+
this.pending.delete(oldest.idHash);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
persistIfDue(now) {
|
|
298
|
+
if (this.dirty && now - this.lastPersist >= SESSION_PERSIST_DEBOUNCE_MS)
|
|
299
|
+
this.persistNow();
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Write the store. Returns whether it is DURABLE: whether a restart is now unable to bring back a
|
|
303
|
+
* session memory has dropped — true when the file was replaced, when the old file held nothing memory
|
|
304
|
+
* dropped (it is merely behind), or when it was removed; false only when a stale file is still there.
|
|
305
|
+
*
|
|
306
|
+
* The failure path matters more than the success path. Memory forgets a revoked
|
|
307
|
+
* session at once; if the file cannot be replaced it still holds that session, and a
|
|
308
|
+
* restart would read it back — a revoked session, alive again. So when a write fails:
|
|
309
|
+
* the store stays owing the disk a write (`dirty`, retried by the next change, the
|
|
310
|
+
* next debounce and shutdown), and if the old file holds anything memory has since
|
|
311
|
+
* dropped, the old file is removed. A restart then finds no sessions and everyone
|
|
312
|
+
* signs in again, which is the failure that is safe; it says so, loudly, when even
|
|
313
|
+
* that removal is impossible.
|
|
314
|
+
*/
|
|
315
|
+
persistNow() {
|
|
316
|
+
this.lastPersist = this.now();
|
|
317
|
+
if (!this.path) {
|
|
318
|
+
this.dirty = false;
|
|
319
|
+
return true;
|
|
320
|
+
}
|
|
321
|
+
const dir = dirname(this.path);
|
|
322
|
+
const temp = `${this.path}.tmp-${process.pid}-${randomBytes(6).toString("hex")}`;
|
|
323
|
+
try {
|
|
324
|
+
mkdirSync(dir, { recursive: true });
|
|
325
|
+
const body = JSON.stringify({ version: 1, sessions: [...this.byHash.values()].filter(r => !this.pending.has(r.idHash)) });
|
|
326
|
+
this.ops.writeFileSync(temp, body, { encoding: "utf8", mode: 0o600, flag: "wx" });
|
|
327
|
+
this.ops.renameSync(temp, this.path);
|
|
328
|
+
try {
|
|
329
|
+
chmodSync(this.path, 0o600);
|
|
330
|
+
}
|
|
331
|
+
catch { /* best effort */ }
|
|
332
|
+
this.persisted = new Set([...this.byHash.keys()].filter(hash => !this.pending.has(hash)));
|
|
333
|
+
this.dirty = false;
|
|
334
|
+
return true;
|
|
335
|
+
}
|
|
336
|
+
catch (err) {
|
|
337
|
+
try {
|
|
338
|
+
this.ops.unlinkSync(temp);
|
|
339
|
+
}
|
|
340
|
+
catch { /* never created or already moved */ }
|
|
341
|
+
this.dirty = true;
|
|
342
|
+
const message = err.message;
|
|
343
|
+
const staleOnDisk = [...this.persisted].some(hash => !this.byHash.has(hash));
|
|
344
|
+
if (!staleOnDisk) {
|
|
345
|
+
// Nothing on disk that memory has dropped: the file is merely behind, and a restart only loses the newest sign-ins.
|
|
346
|
+
this.warn(`web sessions could not be saved (will retry): ${message}`);
|
|
347
|
+
return true;
|
|
348
|
+
}
|
|
349
|
+
try {
|
|
350
|
+
this.ops.unlinkSync(this.path);
|
|
351
|
+
this.persisted = new Set();
|
|
352
|
+
this.warn(`web sessions could not be saved (${message}); removed the old file so a restart cannot bring back a revoked session — everyone will have to sign in again`);
|
|
353
|
+
return true;
|
|
354
|
+
}
|
|
355
|
+
catch {
|
|
356
|
+
this.warn(`web sessions could not be saved (${message}) and the old file could not be removed: a restart may restore sessions that were revoked since the last successful save. Fix the permissions on ${this.path}`);
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
load() {
|
|
362
|
+
if (!this.path)
|
|
363
|
+
return;
|
|
364
|
+
let parsed;
|
|
365
|
+
try {
|
|
366
|
+
parsed = JSON.parse(readFileSync(this.path, "utf8"));
|
|
367
|
+
}
|
|
368
|
+
catch (err) {
|
|
369
|
+
if (err.code !== "ENOENT")
|
|
370
|
+
this.warn("web sessions file is unreadable — starting with none");
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
const list = parsed?.sessions;
|
|
374
|
+
if (!Array.isArray(list))
|
|
375
|
+
return;
|
|
376
|
+
const now = this.now();
|
|
377
|
+
for (const entry of list) {
|
|
378
|
+
if (!isRecord(entry))
|
|
379
|
+
continue;
|
|
380
|
+
if (now >= entry.absoluteExpiry || now >= entry.idleExpiry)
|
|
381
|
+
continue;
|
|
382
|
+
this.byHash.set(entry.idHash, { ...entry });
|
|
383
|
+
}
|
|
384
|
+
while (this.byHash.size > this.maxSessions)
|
|
385
|
+
this.evictLeastRecentlyUsed();
|
|
386
|
+
this.persisted = new Set([...this.byHash.keys()].filter(hash => !this.pending.has(hash)));
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
//# sourceMappingURL=web-session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web-session.js","sourceRoot":"","sources":["../src/web-session.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #1408 §3: the one classifier for the web app's navigations. Every place that has to know "is this a page of the
|
|
3
|
+
* app?" asks it, and none keeps a list of its own:
|
|
4
|
+
* - web-api.ts serves the app shell for exactly these paths, never as a prefix fallback;
|
|
5
|
+
* - the gate's sign-in fallback answers a signed-out browser navigation with the sign-in page;
|
|
6
|
+
* - the public link's manifest (public-web-gateway.ts) admits these GETs.
|
|
7
|
+
*
|
|
8
|
+
* Client routes take only first segments the server's data routes never use (chat, fleet), so a page and a data
|
|
9
|
+
* read can never be confused (a test checks both directions). /view and /view/<name> are the View panel (step 2;
|
|
10
|
+
* view-api.ts serves them, open to anyone when `web.view_access` is open). /settings and /settings/<section> are the
|
|
11
|
+
* Settings panel (step 3; settings-api.ts serves them, signed in only).
|
|
12
|
+
*/
|
|
13
|
+
export declare const FLEET_TABS: readonly ["tasks", "schedules", "teams", "org", "cache", "config"];
|
|
14
|
+
export type FleetTab = typeof FLEET_TABS[number];
|
|
15
|
+
export declare const SETTINGS_SECTIONS: readonly ["agents", "bots", "classic", "general", "advanced"];
|
|
16
|
+
export type SettingsSection = typeof SETTINGS_SECTIONS[number];
|
|
17
|
+
export type ShellRoute = {
|
|
18
|
+
panel: "chat";
|
|
19
|
+
instance: string | null;
|
|
20
|
+
} | {
|
|
21
|
+
panel: "fleet";
|
|
22
|
+
tab: FleetTab;
|
|
23
|
+
} | {
|
|
24
|
+
panel: "view";
|
|
25
|
+
instance: string | null;
|
|
26
|
+
} | {
|
|
27
|
+
panel: "settings";
|
|
28
|
+
section: SettingsSection;
|
|
29
|
+
} | {
|
|
30
|
+
panel: "needs";
|
|
31
|
+
};
|
|
32
|
+
/** What a path is: the app shell (with its route), a malformed shell path (400), or not the shell at all (null). */
|
|
33
|
+
export type ShellMatch = {
|
|
34
|
+
kind: "shell";
|
|
35
|
+
route: ShellRoute;
|
|
36
|
+
} | {
|
|
37
|
+
kind: "malformed";
|
|
38
|
+
} | null;
|
|
39
|
+
/** Longest instance name a path may carry; real names are far shorter. */
|
|
40
|
+
export declare const MAX_INSTANCE_NAME = 128;
|
|
41
|
+
/**
|
|
42
|
+
* The same rule instance paths are held to (access-path.ts): not empty, no separator, no control character, no
|
|
43
|
+
* traversal. A name that passes may still be unknown; that is the panel's "not found" state, never an error page.
|
|
44
|
+
*/
|
|
45
|
+
export declare function isSafeInstanceName(name: string): boolean;
|
|
46
|
+
/** The app shell's routes (#1408 §3). Only GET and HEAD are navigations. */
|
|
47
|
+
export declare function shellRoute(method: string, path: string): ShellMatch;
|
|
48
|
+
/** The Settings panel's pages: what settings-api.ts serves (behind the session gate). */
|
|
49
|
+
export declare function isSettingsPage(path: string): boolean;
|
|
50
|
+
/** The View panel's pages: what view-api.ts serves, and what `web.view_access: open` lets anyone read. */
|
|
51
|
+
export declare function isViewPage(path: string): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* A browser navigation to a page of the web app: the shell's routes. The gate answers these, signed out, with the
|
|
54
|
+
* sign-in page instead of a JSON 401.
|
|
55
|
+
*/
|
|
56
|
+
export declare function isWebPageNavigation(path: string): boolean;
|
|
57
|
+
/** The app's client-side paths, for links the server builds (#1386 links, the tour). */
|
|
58
|
+
export declare function chatPath(instance: string): string;
|
|
59
|
+
export declare function viewPath(instance: string): string;
|