@songsid/agend 2.1.12-beta.9 → 2.2.0-alpha.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/README.md +2 -2
- package/README.zh-TW.md +2 -2
- 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/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/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 +83 -7
- package/dist/backend/kiro.js +418 -91
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/types.d.ts +45 -3
- package/dist/backend/types.js.map +1 -1
- 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.js +225 -127
- 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 +89 -4
- package/dist/daemon.js +661 -77
- 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-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-level-config.d.ts +2 -2
- package/dist/fleet-level-config.js +6 -1
- package/dist/fleet-level-config.js.map +1 -1
- package/dist/fleet-manager.d.ts +293 -12
- package/dist/fleet-manager.js +2364 -449
- package/dist/fleet-manager.js.map +1 -1
- package/dist/host-memory.d.ts +4 -0
- package/dist/host-memory.js.map +1 -1
- package/dist/instance-config-impact.js +5 -0
- 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 +150 -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/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 +6 -2
- package/dist/quickstart.js.map +1 -1
- package/dist/scheduler/db.d.ts +7 -1
- package/dist/scheduler/db.js +38 -0
- 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 +110 -0
- package/dist/service-activation.js +302 -0
- package/dist/service-activation.js.map +1 -0
- package/dist/service-installer.d.ts +7 -0
- package/dist/service-installer.js +7 -4
- package/dist/service-installer.js.map +1 -1
- 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 +175 -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 +133 -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 +197 -32
- package/dist/setup-host.js.map +1 -1
- package/dist/setup-wizard.js +3 -0
- 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/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 +35 -5
- package/dist/tmux-control.js +291 -57
- 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 +191 -58
- 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-render.js +478 -0
- package/dist/ui/chat-store.js +289 -0
- package/dist/ui/chat-strings.js +69 -0
- package/dist/ui/chat-thread.js +209 -0
- package/dist/ui/chat-tour.js +116 -0
- package/dist/ui/fleet-org.js +142 -0
- package/dist/ui/panel-chat.js +418 -0
- package/dist/ui/panel-fleet.js +413 -0
- package/dist/ui/panel-needs.js +202 -0
- package/dist/ui/panel-settings.js +750 -0
- package/dist/ui/preview.js +266 -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 +652 -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 +344 -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 +676 -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 +130 -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 +61 -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 +51 -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-install.d.ts +71 -0
- package/dist/update-install.js +162 -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 +71 -14
- package/dist/web-api.js +492 -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-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/package.json +8 -5
- package/scripts/preinstall-guard.cjs +36 -0
- package/dist/ui/dashboard.html +0 -774
- package/dist/ui/settings.html +0 -2165
- package/dist/ui/view.html +0 -1139
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { createServer } from "node:http";
|
|
2
|
+
import { bindGatewayRequest } from "./web-request-context.js";
|
|
3
|
+
import { applyWebSecurityHeaders } from "./web-host-guard.js";
|
|
4
|
+
import { isServedAsset } from "./auth-api.js";
|
|
5
|
+
import { shellRoute } from "./web-shell-routes.js";
|
|
6
|
+
/** Reviewed panel routes. No health, agent, issue-code, preview, legacy restart or SSE. */
|
|
7
|
+
export function isPublicWebRoute(method, path) {
|
|
8
|
+
const reads = method === "GET" || method === "HEAD";
|
|
9
|
+
if (reads && ["/", "/signin", "/ui", "/view", "/auth/session", "/auth/sessions", "/api/fleet", "/api/profiles", "/api/sort-order", "/api/ai-usage"].includes(path))
|
|
10
|
+
return true;
|
|
11
|
+
if (method === "GET" && path.startsWith("/assets/") && isServedAsset(path.slice("/assets/".length)))
|
|
12
|
+
return true;
|
|
13
|
+
// #1408: the app shell's pages, exactly as the classifier names them (a malformed one is answered 400 behind it).
|
|
14
|
+
if (reads && shellRoute(method, path) !== null)
|
|
15
|
+
return true;
|
|
16
|
+
if (method === "POST" && ["/auth/login", "/auth/logout"].includes(path))
|
|
17
|
+
return true;
|
|
18
|
+
if (method === "DELETE" && /^\/auth\/sessions(?:\/[0-9a-f]{16})?$/.test(path))
|
|
19
|
+
return true;
|
|
20
|
+
if (reads && /^\/api\/(pane|profile|avatar)\/[^/]+$/.test(path))
|
|
21
|
+
return true;
|
|
22
|
+
if (method === "POST" && (path === "/api/sort-order" || /^\/api\/(profile|avatar)\/[^/]+$/.test(path)))
|
|
23
|
+
return true;
|
|
24
|
+
if (method === "POST" && (/^\/stop\/[^/]+$/.test(path) || /^\/api\/instance\/[^/]+\/start$/.test(path)))
|
|
25
|
+
return true;
|
|
26
|
+
if (method === "GET" && /^\/ui\/js\/[a-z0-9_-]+\.js$/.test(path))
|
|
27
|
+
return true;
|
|
28
|
+
if (method === "GET" && /^\/ui\/(backends|poll|history|file\/[^/]+|prompts|instance\/[^/]+|instances|tasks(?:\/[^/]+)?|schedules(?:\/[^/]+)?|teams(?:\/[^/]+)?|config|org)$/.test(path))
|
|
29
|
+
return true;
|
|
30
|
+
if (method === "POST" && /^\/ui\/(send|upload|prompt|needs\/ack|cancel\/[^/]+|stop\/[^/]+|start\/[^/]+|instances\/[^/]+\/delete|restart\/[^/]+|instances|tasks|schedules|teams|config)$/.test(path))
|
|
31
|
+
return true;
|
|
32
|
+
if (((method === "POST" && /^\/ui\/tasks\/[^/]+$/.test(path))) || (method === "DELETE" && /^\/ui\/(schedules|teams)\/[^/]+$/.test(path)))
|
|
33
|
+
return true;
|
|
34
|
+
// Settings has an explicit method/subroute manifest (not an arbitrary /api prefix).
|
|
35
|
+
return isPublicSettingsRoute(method, path);
|
|
36
|
+
}
|
|
37
|
+
function isPublicSettingsRoute(method, path) {
|
|
38
|
+
if (method === "GET" && /^\/api\/settings\/pending(?:\/[0-9a-f]{32})?$/.test(path))
|
|
39
|
+
return true;
|
|
40
|
+
if (method === "DELETE" && /^\/api\/settings\/pending\/[0-9a-f]{32}$/.test(path))
|
|
41
|
+
return true;
|
|
42
|
+
if (method === "GET")
|
|
43
|
+
return /^\/api\/settings\/(schema|fleet|fleet\/raw|classic|status-emojis|status-emojis\/guild-emojis|connections|provider-secrets|secrets|apply\/[^/]+|quickstart\/environment)$/.test(path)
|
|
44
|
+
|| /^\/api\/settings\/(provider-secrets|secrets)\/[^/]+\/apply\/[^/]+$/.test(path)
|
|
45
|
+
|| /^\/api\/settings\/connections\/[^/]+\/(secret|binding)\/apply\/[^/]+$/.test(path);
|
|
46
|
+
if (method === "POST")
|
|
47
|
+
return /^\/api\/settings\/(status-emojis\/preview|apply|restart-fleet|reload|fleet\/instances|quickstart\/(probe|plan|commit))$/.test(path)
|
|
48
|
+
|| /^\/api\/settings\/(provider-secrets|secrets)\/[^/]+\/(verify|apply)$/.test(path)
|
|
49
|
+
|| /^\/api\/settings\/connections\/[^/]+\/(secret|binding)\/(verify|apply)$/.test(path)
|
|
50
|
+
|| /^\/api\/settings\/instances\/[^/]+\/(pause|wake)$/.test(path);
|
|
51
|
+
if (method === "PUT")
|
|
52
|
+
return /^\/api\/settings\/(fleet\/(web|defaults|channels)|classic\/defaults)$/.test(path);
|
|
53
|
+
if (method === "PATCH" && /^\/api\/settings\/classic\/channels\/[^/]+$/.test(path))
|
|
54
|
+
return true;
|
|
55
|
+
return ["POST", "PATCH", "DELETE"].includes(method) && /^\/api\/settings\/fleet\/instances\/[^/]+$/.test(path);
|
|
56
|
+
}
|
|
57
|
+
export function createPublicWebGateway(opts) {
|
|
58
|
+
let host = null;
|
|
59
|
+
const marker = `agend-public-${opts.exposureId}`;
|
|
60
|
+
const sockets = new Set();
|
|
61
|
+
const reject = (res, status) => { res.writeHead(status); res.end(JSON.stringify({ error: "public web request refused" })); };
|
|
62
|
+
const server = (opts.create ?? createServer)((req, res) => {
|
|
63
|
+
applyWebSecurityHeaders(res);
|
|
64
|
+
res.setHeader("Cache-Control", "no-store");
|
|
65
|
+
res.setHeader("Referrer-Policy", "no-referrer");
|
|
66
|
+
res.setHeader("Vary", "Cookie");
|
|
67
|
+
const raw = req.url ?? "/";
|
|
68
|
+
try {
|
|
69
|
+
if (!opts.isCurrent() || !host || req.headers.host !== host)
|
|
70
|
+
return reject(res, 403);
|
|
71
|
+
if (!raw.startsWith("/") || raw.startsWith("//") || /[\\\u0000-\u0020\u007f]/.test(raw))
|
|
72
|
+
return reject(res, 400);
|
|
73
|
+
const url = new URL(raw, `https://${host}`);
|
|
74
|
+
// Decode once now; reject malformed encodings before any shared route can throw.
|
|
75
|
+
const decoded = decodeURIComponent(url.pathname);
|
|
76
|
+
if (decoded.includes("\\") || decoded.includes("\0") || /%2f|%5c/i.test(url.pathname))
|
|
77
|
+
return reject(res, 400);
|
|
78
|
+
if (!isPublicWebRoute(req.method ?? "GET", url.pathname))
|
|
79
|
+
return reject(res, 404);
|
|
80
|
+
if (!opts.isOpen()) {
|
|
81
|
+
if ((req.method === "GET" || req.method === "HEAD") && url.pathname === "/signin") {
|
|
82
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
83
|
+
res.end(`<html><body>${marker}</body></html>`);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
return reject(res, 503);
|
|
87
|
+
}
|
|
88
|
+
bindGatewayRequest(req, { surface: "gateway", exposureId: opts.exposureId, expectedOrigin: `https://${host}`, isCurrent: () => opts.isCurrent() && opts.isOpen() });
|
|
89
|
+
opts.dispatch(req, res);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
if (!res.headersSent)
|
|
93
|
+
reject(res, 400);
|
|
94
|
+
else
|
|
95
|
+
res.destroy();
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
server.maxHeadersCount = 64;
|
|
99
|
+
server.headersTimeout = 5_000;
|
|
100
|
+
server.requestTimeout = 10_000;
|
|
101
|
+
server.keepAliveTimeout = 5_000;
|
|
102
|
+
server.setTimeout(15_000, socket => socket.destroy());
|
|
103
|
+
server.on("connection", socket => { if (sockets.size >= 32) {
|
|
104
|
+
socket.destroy();
|
|
105
|
+
return;
|
|
106
|
+
} sockets.add(socket); socket.on("close", () => sockets.delete(socket)); });
|
|
107
|
+
// Upgrades are never accepted, but they still leave HTTP's ordinary connection accounting.
|
|
108
|
+
server.on("upgrade", (_req, socket) => socket.destroy());
|
|
109
|
+
server.on("connect", (_req, socket) => socket.destroy());
|
|
110
|
+
server.on("clientError", (_err, socket) => socket.destroy());
|
|
111
|
+
server.on("error", () => { host = null; opts.onError?.(); });
|
|
112
|
+
return {
|
|
113
|
+
server, readinessMarker: marker,
|
|
114
|
+
listen: () => new Promise((resolve, rejectListen) => {
|
|
115
|
+
server.once("error", rejectListen);
|
|
116
|
+
server.listen(0, "127.0.0.1", () => {
|
|
117
|
+
server.off("error", rejectListen);
|
|
118
|
+
const address = server.address();
|
|
119
|
+
if (!address || typeof address === "string") {
|
|
120
|
+
rejectListen(new Error("gateway not bound"));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
resolve(new URL(`http://127.0.0.1:${address.port}`));
|
|
124
|
+
});
|
|
125
|
+
}),
|
|
126
|
+
setHost: value => {
|
|
127
|
+
// Provider-validated only, pinned again at this boundary.
|
|
128
|
+
if (value !== null && !/^[a-z0-9-]+\.trycloudflare\.com$/.test(value))
|
|
129
|
+
throw new Error("invalid tunnel host");
|
|
130
|
+
host = value;
|
|
131
|
+
},
|
|
132
|
+
close: () => {
|
|
133
|
+
host = null;
|
|
134
|
+
server.close();
|
|
135
|
+
server.closeAllConnections();
|
|
136
|
+
for (const socket of sockets)
|
|
137
|
+
socket.destroy();
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=public-web-gateway.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"public-web-gateway.js","sourceRoot":"","sources":["../src/public-web-gateway.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { CloudflaredProvider } from "./tunnel/cloudflared.js";
|
|
2
|
+
import { ensureCloudflared } from "./tunnel/cloudflared-install.js";
|
|
3
|
+
import type { TunnelReservation } from "./tunnel/purpose-lane.js";
|
|
4
|
+
import type { TunnelStopResult } from "./tunnel/types.js";
|
|
5
|
+
import type { PublicGateway } from "./public-web-gateway.js";
|
|
6
|
+
import type { LoginCodeOwner } from "./web-login.js";
|
|
7
|
+
import type { WebConfig } from "./types.js";
|
|
8
|
+
export declare const PUBLIC_LINK_DEFAULT_MINUTES = 120;
|
|
9
|
+
export declare const PUBLIC_LINK_MAX_MINUTES = 480;
|
|
10
|
+
export declare function publicLinkSettings(web: WebConfig | undefined): {
|
|
11
|
+
allowed: boolean;
|
|
12
|
+
ttlMs: number;
|
|
13
|
+
protocol: "http2" | "quic" | "auto";
|
|
14
|
+
};
|
|
15
|
+
export declare function validPublicLinkPatch(value: unknown): value is NonNullable<WebConfig["public_link"]>;
|
|
16
|
+
export interface PublicLinkDelivery {
|
|
17
|
+
readonly exposureId: string;
|
|
18
|
+
readonly url: string;
|
|
19
|
+
readonly expiresAt: number;
|
|
20
|
+
readonly isCurrent: () => boolean;
|
|
21
|
+
}
|
|
22
|
+
/** Owns access before child cleanup: losing a tunnel cannot leave a public web session alive. */
|
|
23
|
+
export declare class PublicWebLink {
|
|
24
|
+
private readonly deps;
|
|
25
|
+
private entry;
|
|
26
|
+
private readonly now;
|
|
27
|
+
constructor(deps: {
|
|
28
|
+
dataDir: string;
|
|
29
|
+
web(): WebConfig | undefined;
|
|
30
|
+
permitted(owner: LoginCodeOwner): boolean;
|
|
31
|
+
reserve(id: string): TunnelReservation | null;
|
|
32
|
+
createGateway(id: string, current: () => boolean, open: () => boolean, failed: () => void): PublicGateway;
|
|
33
|
+
revoke(id: string): void;
|
|
34
|
+
log(event: string, id: string): void;
|
|
35
|
+
onCleanupUnconfirmed?(result: TunnelStopResult, owner: LoginCodeOwner): void;
|
|
36
|
+
now?: () => number;
|
|
37
|
+
wallNow?: () => number;
|
|
38
|
+
ensure?: typeof ensureCloudflared;
|
|
39
|
+
provider?: (binary: string, protocol: "http2" | "quic" | "auto") => CloudflaredProvider;
|
|
40
|
+
});
|
|
41
|
+
get exposureId(): string | undefined;
|
|
42
|
+
status(): {
|
|
43
|
+
state: string;
|
|
44
|
+
expiresAt?: number;
|
|
45
|
+
remainingSeconds?: number;
|
|
46
|
+
};
|
|
47
|
+
private current;
|
|
48
|
+
refresh(): void;
|
|
49
|
+
/** Multiple same-start requests share delivery proof: failed A may never close confirmed B. */
|
|
50
|
+
deliver(owner: LoginCodeOwner, send: (link: PublicLinkDelivery) => Promise<boolean>): Promise<boolean>;
|
|
51
|
+
private start;
|
|
52
|
+
close(reason: string, expectedId?: string): Promise<TunnelStopResult>;
|
|
53
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { performance } from "node:perf_hooks";
|
|
2
|
+
import { newTunnelSid } from "./tunnel/manager.js";
|
|
3
|
+
import { CloudflaredProvider } from "./tunnel/cloudflared.js";
|
|
4
|
+
import { ensureCloudflared } from "./tunnel/cloudflared-install.js";
|
|
5
|
+
export const PUBLIC_LINK_DEFAULT_MINUTES = 120;
|
|
6
|
+
export const PUBLIC_LINK_MAX_MINUTES = 480;
|
|
7
|
+
export function publicLinkSettings(web) {
|
|
8
|
+
// Raw YAML startup does not go through the Settings validator. Enforce the
|
|
9
|
+
// fixed cap here too, at the boundary every runtime consumer shares.
|
|
10
|
+
const settings = web?.public_link;
|
|
11
|
+
const minutes = typeof settings?.ttl_minutes === "number" && Number.isInteger(settings.ttl_minutes) && settings.ttl_minutes > 0
|
|
12
|
+
? Math.min(settings.ttl_minutes, PUBLIC_LINK_MAX_MINUTES) : PUBLIC_LINK_DEFAULT_MINUTES;
|
|
13
|
+
const protocol = settings?.protocol;
|
|
14
|
+
return { allowed: settings?.allow_public === undefined || settings.allow_public === true, ttlMs: minutes * 60_000,
|
|
15
|
+
protocol: typeof protocol === "string" && ["http2", "quic", "auto"].includes(protocol) ? protocol : "http2" };
|
|
16
|
+
}
|
|
17
|
+
export function validPublicLinkPatch(value) {
|
|
18
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
19
|
+
return false;
|
|
20
|
+
const v = value;
|
|
21
|
+
return Object.keys(v).every(k => ["allow_public", "ttl_minutes", "protocol"].includes(k))
|
|
22
|
+
&& (v.allow_public === undefined || typeof v.allow_public === "boolean")
|
|
23
|
+
&& (v.ttl_minutes === undefined || (Number.isInteger(v.ttl_minutes) && Number(v.ttl_minutes) >= 1 && Number(v.ttl_minutes) <= PUBLIC_LINK_MAX_MINUTES))
|
|
24
|
+
&& (v.protocol === undefined || (typeof v.protocol === "string" && ["http2", "quic", "auto"].includes(v.protocol)));
|
|
25
|
+
}
|
|
26
|
+
/** Owns access before child cleanup: losing a tunnel cannot leave a public web session alive. */
|
|
27
|
+
export class PublicWebLink {
|
|
28
|
+
deps;
|
|
29
|
+
entry = null;
|
|
30
|
+
now;
|
|
31
|
+
constructor(deps) {
|
|
32
|
+
this.deps = deps;
|
|
33
|
+
this.now = deps.now ?? (() => performance.now());
|
|
34
|
+
}
|
|
35
|
+
get exposureId() { return this.entry?.id; }
|
|
36
|
+
status() {
|
|
37
|
+
const e = this.entry;
|
|
38
|
+
if (e && this.now() >= e.deadline && !e.closing)
|
|
39
|
+
void this.close("expired");
|
|
40
|
+
return e ? { state: e.phase, expiresAt: e.expiresAt, remainingSeconds: Math.max(0, Math.ceil((e.deadline - this.now()) / 1000)) } : { state: "closed" };
|
|
41
|
+
}
|
|
42
|
+
current(e) {
|
|
43
|
+
return this.entry === e && !e.closing && !e.abort.signal.aborted && this.now() < e.deadline
|
|
44
|
+
&& publicLinkSettings(this.deps.web()).allowed && this.deps.permitted(e.owner);
|
|
45
|
+
}
|
|
46
|
+
refresh() {
|
|
47
|
+
const e = this.entry;
|
|
48
|
+
if (e && !this.current(e))
|
|
49
|
+
void this.close("policy or binding changed");
|
|
50
|
+
}
|
|
51
|
+
/** Multiple same-start requests share delivery proof: failed A may never close confirmed B. */
|
|
52
|
+
async deliver(owner, send) {
|
|
53
|
+
if (!this.deps.permitted(owner) || !publicLinkSettings(this.deps.web()).allowed)
|
|
54
|
+
return false;
|
|
55
|
+
let e = this.entry;
|
|
56
|
+
if (e && (e.owner.adapterId !== owner.adapterId || e.owner.chatId !== owner.chatId || e.owner.threadId !== owner.threadId || !this.current(e)))
|
|
57
|
+
return false;
|
|
58
|
+
if (!e) {
|
|
59
|
+
const settings = publicLinkSettings(this.deps.web());
|
|
60
|
+
const id = newTunnelSid();
|
|
61
|
+
const reservation = this.deps.reserve(id); // Before installer, listener, or any await.
|
|
62
|
+
if (!reservation)
|
|
63
|
+
return false;
|
|
64
|
+
e = { id, owner: { ...owner }, reservation, abort: new AbortController(), deadline: this.now() + settings.ttlMs,
|
|
65
|
+
expiresAt: (this.deps.wallNow ?? Date.now)() + settings.ttlMs, phase: "starting", gateway: null, starting: Promise.resolve(), requests: 0, delivered: false };
|
|
66
|
+
this.entry = e;
|
|
67
|
+
const captured = e;
|
|
68
|
+
const expire = () => {
|
|
69
|
+
if (this.entry !== captured || captured.closing)
|
|
70
|
+
return;
|
|
71
|
+
const remaining = captured.deadline - this.now();
|
|
72
|
+
if (remaining <= 0)
|
|
73
|
+
void this.close("expired");
|
|
74
|
+
else {
|
|
75
|
+
captured.timer = setTimeout(expire, Math.ceil(remaining));
|
|
76
|
+
captured.timer.unref?.();
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
expire();
|
|
80
|
+
e.starting = this.start(e, settings.protocol);
|
|
81
|
+
this.deps.log("requested", id);
|
|
82
|
+
}
|
|
83
|
+
e.requests++;
|
|
84
|
+
try {
|
|
85
|
+
await e.starting;
|
|
86
|
+
if (!this.current(e) || !this.deps.permitted(owner) || !e.url)
|
|
87
|
+
return false;
|
|
88
|
+
const delivered = await send({ exposureId: e.id, url: e.url, expiresAt: e.expiresAt, isCurrent: () => this.current(e) && this.deps.permitted(owner) });
|
|
89
|
+
if (!delivered || !this.current(e) || !this.deps.permitted(owner))
|
|
90
|
+
return false;
|
|
91
|
+
e.delivered = true;
|
|
92
|
+
this.deps.log("privately delivered", e.id);
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
finally {
|
|
99
|
+
e.requests--;
|
|
100
|
+
if (!e.delivered && e.requests === 0 && this.entry === e)
|
|
101
|
+
await this.close("no confirmed private recipient");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async start(e, protocol) {
|
|
105
|
+
try {
|
|
106
|
+
const binary = await (this.deps.ensure ?? ensureCloudflared)({ dataDir: this.deps.dataDir, pinnedOnly: true, signal: e.abort.signal });
|
|
107
|
+
if (!this.current(e))
|
|
108
|
+
throw new Error("closed");
|
|
109
|
+
const gateway = this.deps.createGateway(e.id, () => this.current(e), () => e.phase === "open", () => { void this.close("gateway failed", e.id); });
|
|
110
|
+
e.gateway = gateway;
|
|
111
|
+
const origin = await gateway.listen();
|
|
112
|
+
if (!this.current(e))
|
|
113
|
+
throw new Error("closed");
|
|
114
|
+
const provider = (this.deps.provider ?? ((path, p) => new CloudflaredProvider({ binaryName: path, protocol: p })))(binary.path, protocol);
|
|
115
|
+
const result = await e.reservation.start(provider, {
|
|
116
|
+
sid: e.id, origin, pagePath: "/signin", readinessMarker: gateway.readinessMarker, expiresAt: e.expiresAt,
|
|
117
|
+
signal: e.abort.signal, onCandidateHost: host => { if (this.current(e))
|
|
118
|
+
gateway.setHost(host); },
|
|
119
|
+
});
|
|
120
|
+
if (!result.ok || !this.current(e))
|
|
121
|
+
throw new Error("tunnel unavailable");
|
|
122
|
+
const published = new URL(result.handle.pageUrl);
|
|
123
|
+
if (published.protocol !== "https:" || published.pathname !== "/signin" || published.username || published.password || published.search || published.hash)
|
|
124
|
+
throw new Error("invalid public page");
|
|
125
|
+
gateway.setHost(published.host);
|
|
126
|
+
e.url = published.href;
|
|
127
|
+
e.unsubscribe = result.handle.onUnexpectedExit(() => { void this.close("tunnel exited", e.id); });
|
|
128
|
+
if (!this.current(e))
|
|
129
|
+
throw new Error("closed");
|
|
130
|
+
e.phase = "open";
|
|
131
|
+
this.deps.log("opened", e.id);
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// deliver's finally joins close after this promise settles; no self-await.
|
|
135
|
+
e.abort.abort();
|
|
136
|
+
e.gateway?.setHost(null);
|
|
137
|
+
e.gateway?.close();
|
|
138
|
+
this.deps.log("startup failed", e.id);
|
|
139
|
+
throw new Error("public link unavailable");
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
close(reason, expectedId) {
|
|
143
|
+
const e = this.entry;
|
|
144
|
+
if (!e || (expectedId && expectedId !== e.id))
|
|
145
|
+
return Promise.resolve({ confirmed: true });
|
|
146
|
+
if (e.closing)
|
|
147
|
+
return e.closing;
|
|
148
|
+
// Every access fence is synchronous, before the first await and before child cleanup.
|
|
149
|
+
e.phase = "closing";
|
|
150
|
+
e.abort.abort();
|
|
151
|
+
if (e.timer)
|
|
152
|
+
clearTimeout(e.timer);
|
|
153
|
+
e.unsubscribe?.();
|
|
154
|
+
this.deps.revoke(e.id);
|
|
155
|
+
e.gateway?.setHost(null);
|
|
156
|
+
e.gateway?.close();
|
|
157
|
+
e.closing = (async () => {
|
|
158
|
+
await e.starting.catch(() => { });
|
|
159
|
+
e.gateway?.close(); // listen may have finished after the first close.
|
|
160
|
+
const result = await e.reservation.stop(reason);
|
|
161
|
+
if (this.entry === e) {
|
|
162
|
+
if (result.confirmed)
|
|
163
|
+
this.entry = null;
|
|
164
|
+
else {
|
|
165
|
+
e.phase = "cleanup_unconfirmed";
|
|
166
|
+
this.deps.onCleanupUnconfirmed?.(result, e.owner);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
this.deps.log(result.confirmed ? "closed" : "cleanup unconfirmed", e.id);
|
|
170
|
+
return result;
|
|
171
|
+
})();
|
|
172
|
+
return e.closing;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
//# sourceMappingURL=public-web-link.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"public-web-link.js","sourceRoot":"","sources":["../src/public-web-link.ts"],"names":[],"mappings":"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"}
|
package/dist/quickstart-api.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type SettingsExecution } from "./settings-transaction.js";
|
|
1
2
|
/**
|
|
2
3
|
* The Settings setup wizard's server side.
|
|
3
4
|
*
|
|
@@ -145,14 +146,20 @@ export declare function detectWizardBackends(): string[];
|
|
|
145
146
|
* asserts this module's import graph reaches neither fleet-manager, daemon nor
|
|
146
147
|
* instance-lifecycle, so the host cannot grow a path to them by accident.
|
|
147
148
|
*/
|
|
149
|
+
/** One pure draft shared by the writer and the authoritative confirmation diff. */
|
|
150
|
+
export declare function draftQuickstart(cfg: FleetConfig, body: WizardPlanInput, plan: WizardPlan): FleetConfig;
|
|
148
151
|
export interface QuickstartApiContext {
|
|
152
|
+
readonly webToken?: string | null;
|
|
153
|
+
readonly webSessions?: import("./web-session.js").WebSessionStore | null;
|
|
149
154
|
fleetConfig: FleetConfig | null;
|
|
150
155
|
dataDir: string;
|
|
151
156
|
logger: {
|
|
152
157
|
info(obj: unknown, msg?: string): void;
|
|
153
158
|
warn(obj: unknown, msg?: string): void;
|
|
154
159
|
};
|
|
155
|
-
|
|
160
|
+
/** SetupHost returns an undo for its exact persisted write; ordinary fleet writers keep their existing contract. */
|
|
161
|
+
saveFleetConfig(): void | (() => boolean);
|
|
162
|
+
settingsCommitted?(execution?: SettingsExecution): void;
|
|
156
163
|
/** Only a running fleet can answer this; without one, nothing is polling. */
|
|
157
164
|
isBotTokenInUse?(token: string): boolean;
|
|
158
165
|
}
|
package/dist/quickstart-api.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { settingsRequestExecution, settingsWrite } from "./settings-request-capability.js";
|
|
2
|
+
import { SettingsExecutionError, settingsFileResource, trySettingsLease } from "./settings-transaction.js";
|
|
3
|
+
import { readBoundedWebBody } from "./web-body.js";
|
|
4
|
+
import { permitWebContinuation } from "./web-continuation.js";
|
|
1
5
|
import { join } from "node:path";
|
|
2
6
|
import { SecretStore } from "./secret-store.js";
|
|
3
7
|
import { KNOWN_BACKENDS, validateFleetConfig } from "./config-validator.js";
|
|
@@ -145,27 +149,41 @@ export async function runProviderProbe(request, opts = {}) {
|
|
|
145
149
|
export function detectWizardBackends() {
|
|
146
150
|
return detectInstalledBackends(WIZARD_BACKENDS);
|
|
147
151
|
}
|
|
152
|
+
// ── HTTP routes ─────────────────────────────────────────────────────────────
|
|
153
|
+
/**
|
|
154
|
+
* What the wizard's routes need from their host.
|
|
155
|
+
*
|
|
156
|
+
* Five fields, and none of them is a fleet: this is the whole "ConfigVerbs +
|
|
157
|
+
* ProviderProbeVerbs" surface. A running fleet supplies it from FleetManager; a
|
|
158
|
+
* pre-fleet setup host supplies it from a file. `tests/prefleet-host.test.ts`
|
|
159
|
+
* asserts this module's import graph reaches neither fleet-manager, daemon nor
|
|
160
|
+
* instance-lifecycle, so the host cannot grow a path to them by accident.
|
|
161
|
+
*/
|
|
162
|
+
/** One pure draft shared by the writer and the authoritative confirmation diff. */
|
|
163
|
+
export function draftQuickstart(cfg, body, plan) {
|
|
164
|
+
const draft = structuredClone(cfg), summary = wizardChannels(cfg);
|
|
165
|
+
const channels = draft.channels ?? (draft.channel ? [draft.channel] : []);
|
|
166
|
+
const existingIndex = channels.findIndex(channel => channel.bot_token_env === body.token_env);
|
|
167
|
+
const entry = { id: nextChannelId(body.platform, body.token_env, summary), ...plan.channel };
|
|
168
|
+
if (existingIndex >= 0)
|
|
169
|
+
channels[existingIndex] = { ...channels[existingIndex], ...entry };
|
|
170
|
+
else
|
|
171
|
+
channels.push(entry);
|
|
172
|
+
draft.channels = channels;
|
|
173
|
+
delete draft.channel;
|
|
174
|
+
draft.instances = { ...draft.instances, [body.instance_name]: {
|
|
175
|
+
...(Object.hasOwn(draft.instances, body.instance_name) ? draft.instances[body.instance_name] : {}),
|
|
176
|
+
working_directory: body.working_directory, backend: body.backend,
|
|
177
|
+
} };
|
|
178
|
+
return draft;
|
|
179
|
+
}
|
|
148
180
|
function json(res, status, data) {
|
|
149
181
|
res.setHeader("Content-Type", "application/json");
|
|
150
182
|
res.writeHead(status);
|
|
151
183
|
res.end(JSON.stringify(data));
|
|
152
184
|
}
|
|
153
185
|
function readBody(req, maxBytes) {
|
|
154
|
-
return
|
|
155
|
-
const chunks = [];
|
|
156
|
-
let size = 0;
|
|
157
|
-
req.on("data", (chunk) => {
|
|
158
|
-
size += chunk.length;
|
|
159
|
-
if (size > maxBytes) {
|
|
160
|
-
reject(new Error("body too large"));
|
|
161
|
-
req.destroy();
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
chunks.push(chunk);
|
|
165
|
-
});
|
|
166
|
-
req.on("end", () => resolve(Buffer.concat(chunks)));
|
|
167
|
-
req.on("error", reject);
|
|
168
|
-
});
|
|
186
|
+
return readBoundedWebBody(req, maxBytes);
|
|
169
187
|
}
|
|
170
188
|
/** The channels as the wizard sees them, from either config shape. */
|
|
171
189
|
export function wizardChannels(cfg) {
|
|
@@ -216,6 +234,8 @@ export function handleQuickstartRequest(req, res, url, ctx) {
|
|
|
216
234
|
}
|
|
217
235
|
if (method === "POST" && path === "/api/settings/quickstart/probe") {
|
|
218
236
|
readBody(req, 16 * 1024).then(async (buf) => {
|
|
237
|
+
if (!permitWebContinuation(req, res, ctx))
|
|
238
|
+
return;
|
|
219
239
|
let body;
|
|
220
240
|
try {
|
|
221
241
|
body = JSON.parse(buf.toString("utf-8") || "{}");
|
|
@@ -245,6 +265,8 @@ export function handleQuickstartRequest(req, res, url, ctx) {
|
|
|
245
265
|
return true;
|
|
246
266
|
}
|
|
247
267
|
readBody(req, 16 * 1024).then(buf => {
|
|
268
|
+
if (!permitWebContinuation(req, res, ctx))
|
|
269
|
+
return;
|
|
248
270
|
let body;
|
|
249
271
|
try {
|
|
250
272
|
body = JSON.parse(buf.toString("utf-8") || "{}");
|
|
@@ -269,6 +291,8 @@ export function handleQuickstartRequest(req, res, url, ctx) {
|
|
|
269
291
|
return true;
|
|
270
292
|
}
|
|
271
293
|
readBody(req, 16 * 1024).then(buf => {
|
|
294
|
+
if (!permitWebContinuation(req, res, ctx))
|
|
295
|
+
return;
|
|
272
296
|
let body;
|
|
273
297
|
try {
|
|
274
298
|
body = JSON.parse(buf.toString("utf-8") || "{}");
|
|
@@ -291,58 +315,72 @@ export function handleQuickstartRequest(req, res, url, ctx) {
|
|
|
291
315
|
// nothing in memory moves until the whole result is known to be valid —
|
|
292
316
|
// the previous order wrote the token first and left the running config
|
|
293
317
|
// rewritten when validation then failed.
|
|
294
|
-
const draft =
|
|
295
|
-
const channels = (draft.channels ?? (draft.channel ? [draft.channel] : []));
|
|
296
|
-
const existingIndex = channels.findIndex(channel => channel.bot_token_env === body.token_env);
|
|
297
|
-
const entry = { id: nextChannelId(body.platform, body.token_env, summary), ...plan.channel };
|
|
298
|
-
if (existingIndex >= 0)
|
|
299
|
-
channels[existingIndex] = { ...channels[existingIndex], ...entry };
|
|
300
|
-
else
|
|
301
|
-
channels.push(entry);
|
|
302
|
-
draft.channels = channels;
|
|
303
|
-
delete draft.channel;
|
|
304
|
-
draft.instances[body.instance_name] = {
|
|
305
|
-
...(draft.instances[body.instance_name] ?? {}),
|
|
306
|
-
working_directory: body.working_directory,
|
|
307
|
-
backend: body.backend,
|
|
308
|
-
};
|
|
318
|
+
const draft = draftQuickstart(cfg, body, plan);
|
|
309
319
|
const validation = validateFleetConfig(draft);
|
|
310
320
|
if (!validation.valid) {
|
|
311
321
|
return json(res, 400, { error: validation.errors.map(e => `${e.path}: ${e.message}`).join("; ") });
|
|
312
322
|
}
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
};
|
|
323
|
-
cfg.channels = draft.channels;
|
|
324
|
-
delete cfg.channel;
|
|
325
|
-
cfg.instances[body.instance_name] = draft.instances[body.instance_name];
|
|
323
|
+
const execution = settingsRequestExecution(req), envPath = join(ctx.dataDir, ".env");
|
|
324
|
+
const lease = trySettingsLease([settingsFileResource(envPath)], execution?.owner);
|
|
325
|
+
if (!lease)
|
|
326
|
+
return json(res, 409, { error: "another secret operation is still running" });
|
|
327
|
+
let stored = false, secretBefore;
|
|
328
|
+
const before = { channels: cfg.channels, channel: cfg.channel, instance: cfg.instances[body.instance_name],
|
|
329
|
+
hadInstance: Object.hasOwn(cfg.instances, body.instance_name) };
|
|
330
|
+
let store;
|
|
331
|
+
const rollback = {};
|
|
326
332
|
try {
|
|
327
|
-
|
|
333
|
+
execution?.assert();
|
|
334
|
+
store = new SecretStore(envPath, new Set([body.token_env]), { owner: lease.owner });
|
|
335
|
+
settingsWrite(req, () => {
|
|
336
|
+
secretBefore = store.write(body.token_env, body.token);
|
|
337
|
+
stored = true;
|
|
338
|
+
cfg.channels = draft.channels;
|
|
339
|
+
delete cfg.channel;
|
|
340
|
+
Object.defineProperty(cfg.instances, body.instance_name, { value: draft.instances[body.instance_name], enumerable: true, writable: true, configurable: true });
|
|
341
|
+
const undo = ctx.saveFleetConfig();
|
|
342
|
+
if (typeof undo === "function")
|
|
343
|
+
rollback.config = undo;
|
|
344
|
+
ctx.settingsCommitted?.(execution);
|
|
345
|
+
});
|
|
328
346
|
}
|
|
329
347
|
catch (err) {
|
|
330
|
-
// The file is the authority. If it did not take the change, the running
|
|
331
|
-
// config must not keep it either.
|
|
332
348
|
cfg.channels = before.channels;
|
|
333
349
|
if (before.channel !== undefined)
|
|
334
350
|
cfg.channel = before.channel;
|
|
351
|
+
else
|
|
352
|
+
delete cfg.channel;
|
|
335
353
|
if (before.hadInstance)
|
|
336
354
|
cfg.instances[body.instance_name] = before.instance;
|
|
337
355
|
else
|
|
338
356
|
delete cfg.instances[body.instance_name];
|
|
339
|
-
|
|
357
|
+
let cleanupFailed = false;
|
|
358
|
+
try {
|
|
359
|
+
if (rollback.config && !rollback.config())
|
|
360
|
+
cleanupFailed = true;
|
|
361
|
+
}
|
|
362
|
+
catch {
|
|
363
|
+
cleanupFailed = true;
|
|
364
|
+
}
|
|
365
|
+
try {
|
|
366
|
+
if (stored && secretBefore)
|
|
367
|
+
store.restoreIfCurrent(secretBefore);
|
|
368
|
+
}
|
|
369
|
+
catch {
|
|
370
|
+
cleanupFailed = true;
|
|
371
|
+
}
|
|
372
|
+
if (cleanupFailed)
|
|
373
|
+
return json(res, 500, { error: "setup cleanup failed; inspect configuration on the host" });
|
|
374
|
+
return json(res, err instanceof SettingsExecutionError ? 409 : 500, { error: "setup was not committed" });
|
|
375
|
+
}
|
|
376
|
+
finally {
|
|
377
|
+
lease.release();
|
|
340
378
|
}
|
|
341
379
|
ctx.logger.info({ instance: body.instance_name, platform: body.platform }, "settings: quickstart committed");
|
|
342
380
|
// No apply here: the page starts the ordinary job so the wizard's last
|
|
343
381
|
// step has the same progress, deadline and restart recovery as everything
|
|
344
382
|
// else the panel applies.
|
|
345
|
-
json(res, 200, { ok: true, plan, secret_mode_ok:
|
|
383
|
+
json(res, 200, { ok: true, plan, secret_mode_ok: true, warnings: plan.warnings });
|
|
346
384
|
}).catch(() => json(res, 400, { error: "bad request" }));
|
|
347
385
|
return true;
|
|
348
386
|
}
|