@songsid/agend 2.3.5-alpha.2 → 2.3.5-alpha.4
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/dist/agend-home.d.ts +13 -0
- package/dist/agend-home.js +42 -0
- package/dist/agend-home.js.map +1 -1
- package/dist/agent-runtime-api.d.ts +4 -0
- package/dist/agent-runtime-api.js +59 -0
- package/dist/agent-runtime-api.js.map +1 -0
- package/dist/agent-runtime-change.d.ts +74 -0
- package/dist/agent-runtime-change.js +102 -0
- package/dist/agent-runtime-change.js.map +1 -0
- package/dist/auth-api.d.ts +21 -0
- package/dist/auth-api.js +95 -4
- package/dist/auth-api.js.map +1 -1
- package/dist/authority/web-authority.d.ts +12 -2
- package/dist/authority/web-authority.js +25 -4
- package/dist/authority/web-authority.js.map +1 -1
- package/dist/backend/claude-code.js +16 -1
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/codex-footer-hints.d.ts +4 -0
- package/dist/backend/codex-footer-hints.js +27 -0
- package/dist/backend/codex-footer-hints.js.map +1 -0
- package/dist/backend/codex.js +19 -11
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/kiro-identity.d.ts +2 -0
- package/dist/backend/kiro-identity.js +13 -6
- package/dist/backend/kiro-identity.js.map +1 -1
- package/dist/backend/kiro.js +18 -3
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend-availability.d.ts +6 -0
- package/dist/backend-availability.js +13 -5
- package/dist/backend-availability.js.map +1 -1
- package/dist/channel/mcp-tools.js +1 -1
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/cli.js +6 -9
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.js +12 -1
- package/dist/config-validator.js.map +1 -1
- package/dist/daemon.d.ts +5 -0
- package/dist/daemon.js +107 -30
- package/dist/daemon.js.map +1 -1
- package/dist/fleet-manager.d.ts +146 -8
- package/dist/fleet-manager.js +1030 -166
- package/dist/fleet-manager.js.map +1 -1
- package/dist/fleet-windows-runtime.d.ts +6 -1
- package/dist/fleet-windows-runtime.js +77 -1
- package/dist/fleet-windows-runtime.js.map +1 -1
- package/dist/fleet-yaml-patch.d.ts +3 -0
- package/dist/fleet-yaml-patch.js +103 -0
- package/dist/fleet-yaml-patch.js.map +1 -0
- package/dist/general-binding.d.ts +85 -0
- package/dist/general-binding.js +298 -0
- package/dist/general-binding.js.map +1 -0
- package/dist/general-host-owner.d.ts +8 -0
- package/dist/general-host-owner.js +26 -0
- package/dist/general-host-owner.js.map +1 -0
- package/dist/general-instance.d.ts +7 -0
- package/dist/general-instance.js +20 -3
- package/dist/general-instance.js.map +1 -1
- package/dist/general-knowledge/skills/model-discovery/SKILL.md +2 -0
- package/dist/general-role.d.ts +5 -0
- package/dist/general-role.js +26 -0
- package/dist/general-role.js.map +1 -0
- package/dist/instance-lifecycle.d.ts +1 -0
- package/dist/instance-lifecycle.js +26 -4
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/instructions.d.ts +2 -0
- package/dist/instructions.js +6 -0
- package/dist/instructions.js.map +1 -1
- package/dist/kiro-resume-summary.d.ts +37 -0
- package/dist/kiro-resume-summary.js +85 -0
- package/dist/kiro-resume-summary.js.map +1 -0
- package/dist/locale.js +59 -0
- package/dist/locale.js.map +1 -1
- package/dist/login-controller.js +9 -1
- package/dist/login-controller.js.map +1 -1
- package/dist/native-pane-lifecycle.js +4 -1
- package/dist/native-pane-lifecycle.js.map +1 -1
- package/dist/native-pane-manager.d.ts +1 -0
- package/dist/native-pane-manager.js +23 -2
- package/dist/native-pane-manager.js.map +1 -1
- package/dist/needs-you-hub.js +1 -1
- package/dist/needs-you-hub.js.map +1 -1
- package/dist/needs-you.d.ts +8 -1
- package/dist/needs-you.js +5 -2
- package/dist/needs-you.js.map +1 -1
- package/dist/office-share-page.js +1 -1
- package/dist/office-share-page.js.map +1 -1
- package/dist/package-preimage.js +27 -2
- package/dist/package-preimage.js.map +1 -1
- package/dist/pane-port.d.ts +11 -0
- package/dist/public-web-gateway.js +9 -0
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/public-web-link.d.ts +11 -0
- package/dist/public-web-link.js +13 -0
- package/dist/public-web-link.js.map +1 -1
- package/dist/quickstart-api.js +9 -5
- package/dist/quickstart-api.js.map +1 -1
- package/dist/quickstart.d.ts +1 -0
- package/dist/quickstart.js +4 -1
- package/dist/quickstart.js.map +1 -1
- package/dist/reply-buttons-controller.js +10 -0
- package/dist/reply-buttons-controller.js.map +1 -1
- package/dist/runtime-api.js +84 -9
- package/dist/runtime-api.js.map +1 -1
- package/dist/runtime-build-info.d.ts +9 -0
- package/dist/runtime-build-info.js +57 -0
- package/dist/runtime-build-info.js.map +1 -0
- package/dist/runtime-bundle.d.ts +3 -1
- package/dist/runtime-bundle.js +17 -8
- package/dist/runtime-bundle.js.map +1 -1
- package/dist/runtime-cli.d.ts +6 -1
- package/dist/runtime-cli.js +46 -11
- package/dist/runtime-cli.js.map +1 -1
- package/dist/runtime-client-store.d.ts +20 -1
- package/dist/runtime-client-store.js +56 -4
- package/dist/runtime-client-store.js.map +1 -1
- package/dist/runtime-client.d.ts +12 -0
- package/dist/runtime-client.js +43 -7
- package/dist/runtime-client.js.map +1 -1
- package/dist/runtime-connection.d.ts +22 -0
- package/dist/runtime-connection.js +93 -0
- package/dist/runtime-connection.js.map +1 -0
- package/dist/runtime-coordinator.d.ts +9 -0
- package/dist/runtime-coordinator.js +81 -34
- package/dist/runtime-coordinator.js.map +1 -1
- package/dist/runtime-general-protocol.d.ts +22 -0
- package/dist/runtime-general-protocol.js +57 -0
- package/dist/runtime-general-protocol.js.map +1 -0
- package/dist/runtime-general-workspace.d.ts +17 -0
- package/dist/runtime-general-workspace.js +213 -0
- package/dist/runtime-general-workspace.js.map +1 -0
- package/dist/runtime-pane-assets.d.ts +1 -1
- package/dist/runtime-pane-assets.js +1 -1
- package/dist/runtime-pane-assets.js.map +1 -1
- package/dist/runtime-pane-boot.d.ts +7 -0
- package/dist/runtime-pane-boot.js +23 -0
- package/dist/runtime-pane-boot.js.map +1 -1
- package/dist/runtime-pane-host.d.ts +1 -0
- package/dist/runtime-pane-host.js +22 -1
- package/dist/runtime-pane-host.js.map +1 -1
- package/dist/runtime-pane-mcp.d.ts +8 -0
- package/dist/runtime-pane-mcp.js +15 -2
- package/dist/runtime-pane-mcp.js.map +1 -1
- package/dist/runtime-pane-port.d.ts +6 -1
- package/dist/runtime-pane-port.js +21 -0
- package/dist/runtime-pane-port.js.map +1 -1
- package/dist/runtime-pane-protocol.d.ts +4 -1
- package/dist/runtime-pane-protocol.js +7 -1
- package/dist/runtime-pane-protocol.js.map +1 -1
- package/dist/runtime-protocol.d.ts +13 -0
- package/dist/runtime-protocol.js +18 -0
- package/dist/runtime-protocol.js.map +1 -1
- package/dist/settings-api.d.ts +5 -0
- package/dist/settings-api.js +37 -1
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-baseline.d.ts +1 -0
- package/dist/settings-baseline.js +1 -1
- package/dist/settings-baseline.js.map +1 -1
- package/dist/settings-confirmation.d.ts +1 -0
- package/dist/settings-confirmation.js +1 -0
- package/dist/settings-confirmation.js.map +1 -1
- package/dist/settings-effect.d.ts +4 -0
- package/dist/settings-effect.js +36 -1
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-http-confirmation.d.ts +4 -0
- package/dist/settings-http-confirmation.js +20 -1
- package/dist/settings-http-confirmation.js.map +1 -1
- package/dist/settings-request-capability.d.ts +2 -0
- package/dist/settings-request-capability.js +4 -0
- package/dist/settings-request-capability.js.map +1 -1
- package/dist/settings-route-lifetime.d.ts +14 -0
- package/dist/settings-route-lifetime.js +48 -0
- package/dist/settings-route-lifetime.js.map +1 -0
- package/dist/settings-transaction.d.ts +4 -0
- package/dist/settings-transaction.js +9 -0
- package/dist/settings-transaction.js.map +1 -1
- package/dist/setup-form.js +6 -3
- package/dist/setup-form.js.map +1 -1
- package/dist/setup-i18n.js +3 -0
- package/dist/setup-i18n.js.map +1 -1
- package/dist/stall-reporter.d.ts +21 -0
- package/dist/stall-reporter.js +82 -0
- package/dist/stall-reporter.js.map +1 -0
- package/dist/tmux-control.d.ts +2 -2
- package/dist/tmux-control.js +4 -2
- package/dist/tmux-control.js.map +1 -1
- package/dist/tmux-exec-helper.d.ts +6 -3
- package/dist/tmux-exec-helper.js +115 -53
- package/dist/tmux-exec-helper.js.map +1 -1
- package/dist/tool-permissions.js +2 -1
- package/dist/tool-permissions.js.map +1 -1
- package/dist/tunnel/cloudflared-install.d.ts +4 -0
- package/dist/tunnel/cloudflared-install.js +20 -1
- package/dist/tunnel/cloudflared-install.js.map +1 -1
- package/dist/tunnel/cloudflared.d.ts +8 -1
- package/dist/tunnel/cloudflared.js +34 -6
- package/dist/tunnel/cloudflared.js.map +1 -1
- package/dist/tunnel/lease.d.ts +3 -3
- package/dist/tunnel/lease.js +23 -19
- package/dist/tunnel/lease.js.map +1 -1
- package/dist/tunnel/manager.js +6 -7
- package/dist/tunnel/manager.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/ui/agent-proposals.js +2 -2
- package/dist/ui/agent-runtime-settings.js +67 -0
- package/dist/ui/agent-runtime.js +20 -2
- package/dist/ui/backend-signin.js +36 -21
- package/dist/ui/general-location.js +71 -0
- package/dist/ui/native-instance-settings.js +5 -8
- package/dist/ui/native-pane-recovery.js +1 -1
- package/dist/ui/panel-chat.js +5 -2
- package/dist/ui/panel-fleet.js +4 -2
- package/dist/ui/panel-needs.js +2 -0
- package/dist/ui/panel-runtimes.js +136 -33
- package/dist/ui/panel-settings.js +227 -138
- package/dist/ui/panel-windows-cli.js +77 -41
- package/dist/ui/settings-agent-filters.js +94 -0
- package/dist/ui/settings-appearance.js +122 -23
- package/dist/ui/settings-dialogs.js +13 -7
- package/dist/ui/settings-guests.js +25 -11
- package/dist/ui/settings-layout.js +245 -0
- package/dist/ui/settings-model.js +10 -1
- package/dist/ui/settings-outside.js +176 -0
- package/dist/ui/settings-registry.js +6 -3
- package/dist/ui/settings-shortcuts.js +65 -0
- package/dist/ui/settings-strings.js +95 -0
- package/dist/ui/settings-web-authority.js +14 -11
- package/dist/ui/shared/app-i18n.js +2 -2
- package/dist/ui/shared/app-shell.js +17 -7
- package/dist/ui/shared/app.css +1038 -13
- package/dist/ui/shared/instance-nav.js +16 -5
- package/dist/ui/shared/machine-name.js +13 -0
- package/dist/ui/shared/panel-view.js +266 -17
- package/dist/ui/shared/qrcode.module.js +2237 -0
- package/dist/ui/shared/settings-width.js +31 -0
- package/dist/ui/shared/signin.js +39 -5
- package/dist/ui/shared/tokens.css +1 -0
- package/dist/ui/shared/ui-dialog.js +2 -2
- package/dist/ui/shared/ui-menu.js +3 -3
- package/dist/ui/shared/vendor/qrcode-generator.LICENSE +27 -0
- package/dist/ui/shared/vendor/vendor.json +19 -2
- package/dist/ui/shared/view-strings.js +28 -0
- package/dist/ui/windows-cli-catalog.js +41 -12
- package/dist/unattended-install.d.ts +1 -1
- package/dist/unattended-install.js +19 -5
- package/dist/unattended-install.js.map +1 -1
- package/dist/update-check.d.ts +2 -8
- package/dist/update-check.js +2 -5
- package/dist/update-check.js.map +1 -1
- package/dist/view-api.d.ts +15 -0
- package/dist/view-api.js +94 -33
- package/dist/view-api.js.map +1 -1
- package/dist/web-api.d.ts +13 -0
- package/dist/web-api.js +80 -0
- package/dist/web-api.js.map +1 -1
- package/dist/web-login.d.ts +4 -3
- package/dist/web-login.js +23 -14
- package/dist/web-login.js.map +1 -1
- package/dist/web-only-fleet.d.ts +20 -0
- package/dist/web-only-fleet.js +21 -0
- package/dist/web-only-fleet.js.map +1 -1
- package/dist/web-remote-access.d.ts +152 -0
- package/dist/web-remote-access.js +368 -0
- package/dist/web-remote-access.js.map +1 -0
- package/dist/web-session.d.ts +12 -0
- package/dist/web-session.js +22 -8
- package/dist/web-session.js.map +1 -1
- package/dist/windows-agents.d.ts +15 -1
- package/dist/windows-agents.js +37 -10
- package/dist/windows-agents.js.map +1 -1
- package/dist/windows-cli-api.js +2 -1
- package/dist/windows-cli-api.js.map +1 -1
- package/dist/windows-cli-worker.d.ts +1 -0
- package/dist/windows-cli-worker.js +2 -2
- package/dist/windows-cli-worker.js.map +1 -1
- package/dist/windows-cli.d.ts +1 -0
- package/dist/windows-cli.js +7 -2
- package/dist/windows-cli.js.map +1 -1
- package/launcher/agend-install.cjs +16 -2
- package/package.json +1 -1
package/dist/agend-home.d.ts
CHANGED
|
@@ -3,3 +3,16 @@ export declare function ensureAgendHome(dir: string, options?: {
|
|
|
3
3
|
label?: string;
|
|
4
4
|
log?: (line: string) => void;
|
|
5
5
|
}): void;
|
|
6
|
+
/** #3009: how long the off-thread home check may take before the caller is told it could not be done. */
|
|
7
|
+
export declare const AGEND_HOME_CHECK_MS = 10000;
|
|
8
|
+
/**
|
|
9
|
+
* #3009 (#3017 review, Prism P1): ensureAgendHome — the very same checks (ownership, symlink and descriptor fences) —
|
|
10
|
+
* run on a short-lived worker thread, so a home filesystem that stops answering stalls that worker, never the fleet's
|
|
11
|
+
* event loop and its systemd watchdog. Bounded: past `timeoutMs` it rejects (the worker, possibly stuck in the kernel,
|
|
12
|
+
* is let go — unref'd, it never holds the process open). Only for paths that may write on a user's request (a login's
|
|
13
|
+
* tunnel lease); startup keeps the synchronous call.
|
|
14
|
+
*/
|
|
15
|
+
export declare function ensureAgendHomeOffThread(dir: string, options?: {
|
|
16
|
+
create?: boolean;
|
|
17
|
+
label?: string;
|
|
18
|
+
}, timeoutMs?: number): Promise<void>;
|
package/dist/agend-home.js
CHANGED
|
@@ -1,7 +1,49 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
+
import { Worker } from "node:worker_threads";
|
|
2
3
|
// The installer and runtime enforce the same root policy before writing credentials or operator sockets.
|
|
3
4
|
const { ensurePrivateHome } = createRequire(import.meta.url)("../launcher/private-home.cjs");
|
|
4
5
|
export function ensureAgendHome(dir, options) {
|
|
5
6
|
ensurePrivateHome(dir, options);
|
|
6
7
|
}
|
|
8
|
+
/** #3009: how long the off-thread home check may take before the caller is told it could not be done. */
|
|
9
|
+
export const AGEND_HOME_CHECK_MS = 10_000;
|
|
10
|
+
const privateHomeModule = createRequire(import.meta.url).resolve("../launcher/private-home.cjs");
|
|
11
|
+
/**
|
|
12
|
+
* #3009 (#3017 review, Prism P1): ensureAgendHome — the very same checks (ownership, symlink and descriptor fences) —
|
|
13
|
+
* run on a short-lived worker thread, so a home filesystem that stops answering stalls that worker, never the fleet's
|
|
14
|
+
* event loop and its systemd watchdog. Bounded: past `timeoutMs` it rejects (the worker, possibly stuck in the kernel,
|
|
15
|
+
* is let go — unref'd, it never holds the process open). Only for paths that may write on a user's request (a login's
|
|
16
|
+
* tunnel lease); startup keeps the synchronous call.
|
|
17
|
+
*/
|
|
18
|
+
export function ensureAgendHomeOffThread(dir, options = {}, timeoutMs = AGEND_HOME_CHECK_MS) {
|
|
19
|
+
return new Promise((resolve, reject) => {
|
|
20
|
+
let settled = false;
|
|
21
|
+
const done = (err) => { if (settled)
|
|
22
|
+
return; settled = true; clearTimeout(timer); if (err)
|
|
23
|
+
reject(err);
|
|
24
|
+
else
|
|
25
|
+
resolve(); };
|
|
26
|
+
let worker;
|
|
27
|
+
try {
|
|
28
|
+
worker = new Worker(`const { parentPort, workerData } = require("node:worker_threads");
|
|
29
|
+
try { require(workerData.module).ensurePrivateHome(workerData.dir, workerData.options); parentPort.postMessage({ ok: true }); }
|
|
30
|
+
catch (e) { parentPort.postMessage({ ok: false, message: e && e.message ? String(e.message) : String(e), code: e && e.code }); }`, { eval: true, workerData: { module: privateHomeModule, dir, options } });
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
reject(err);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
worker.unref();
|
|
37
|
+
const timer = setTimeout(() => {
|
|
38
|
+
done(Object.assign(new Error(`the AgEnD home could not be checked in time (${dir}): its filesystem did not answer`), { code: "ETIMEDOUT" }));
|
|
39
|
+
void worker.terminate().catch(() => { });
|
|
40
|
+
}, timeoutMs);
|
|
41
|
+
timer.unref?.();
|
|
42
|
+
worker.once("message", (m) => {
|
|
43
|
+
done(m.ok ? undefined : Object.assign(new Error(m.message ?? "AgEnD home check failed"), m.code ? { code: m.code } : {}));
|
|
44
|
+
void worker.terminate().catch(() => { });
|
|
45
|
+
});
|
|
46
|
+
worker.once("error", (err) => done(err instanceof Error ? err : new Error(String(err))));
|
|
47
|
+
});
|
|
48
|
+
}
|
|
7
49
|
//# sourceMappingURL=agend-home.js.map
|
package/dist/agend-home.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agend-home.js","sourceRoot":"","sources":["../src/agend-home.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"agend-home.js","sourceRoot":"","sources":["../src/agend-home.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import type { WebApiContext } from "./web-api.js";
|
|
3
|
+
/** Explicit dashboard-only runtime moves. Agent tool schemas do not offer this endpoint. */
|
|
4
|
+
export declare function handleAgentRuntimeChange(req: IncomingMessage, res: ServerResponse, url: URL, ctx: WebApiContext): boolean;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { readBoundedWebBody } from "./web-body.js";
|
|
2
|
+
import { nativeCliAuthorization } from "./windows-cli-api.js";
|
|
3
|
+
function json(res, status, value) { if (!res.destroyed && !res.writableEnded) {
|
|
4
|
+
res.writeHead(status, { "Content-Type": "application/json", "Cache-Control": "no-store" });
|
|
5
|
+
res.end(JSON.stringify(value));
|
|
6
|
+
} }
|
|
7
|
+
/** Explicit dashboard-only runtime moves. Agent tool schemas do not offer this endpoint. */
|
|
8
|
+
export function handleAgentRuntimeChange(req, res, url, ctx) {
|
|
9
|
+
const match = /^\/ui\/instances\/([^/]+)\/runtime$/.exec(url.pathname);
|
|
10
|
+
if (!match)
|
|
11
|
+
return false;
|
|
12
|
+
const allowed = () => nativeCliAuthorization(req, url, ctx);
|
|
13
|
+
if (!allowed()) {
|
|
14
|
+
json(res, 403, { error: "Runtime changes require a dashboard administrator" });
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
let name;
|
|
18
|
+
try {
|
|
19
|
+
name = decodeURIComponent(match[1]);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
json(res, 400, { error: "Invalid Agent name" });
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
if (!Object.hasOwn(ctx.fleetConfig?.instances ?? {}, name)) {
|
|
26
|
+
json(res, 404, { error: "Agent not found" });
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
if (!ctx.agentRuntimeChanges) {
|
|
30
|
+
json(res, 503, { error: "Runtime changes are unavailable on this host" });
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
if (req.method === "GET") {
|
|
34
|
+
json(res, 200, ctx.agentRuntimeChanges().describe(name));
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
if (req.method !== "POST") {
|
|
38
|
+
json(res, 405, { error: "Method not allowed" });
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
void (async () => {
|
|
42
|
+
try {
|
|
43
|
+
const raw = await readBoundedWebBody(req, 4096);
|
|
44
|
+
if (!allowed()) {
|
|
45
|
+
json(res, 403, { error: "Authorization expired" });
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (req.aborted || res.destroyed)
|
|
49
|
+
return;
|
|
50
|
+
const input = JSON.parse(raw.toString("utf8")), result = await ctx.agentRuntimeChanges().change(name, input, allowed);
|
|
51
|
+
json(res, 200, result);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
json(res, allowed() ? 409 : 403, { error: allowed() ? (error instanceof Error ? error.message : "Could not change Runtime") : "Authorization expired" });
|
|
55
|
+
}
|
|
56
|
+
})();
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=agent-runtime-api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-runtime-api.js","sourceRoot":"","sources":["../src/agent-runtime-api.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/** Administrator-only moves of one Fleet identity. Provider sessions never cross machines or providers. */
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import type { InstanceConfig } from "./types.js";
|
|
4
|
+
export declare const AgentRuntimeChangeInput: z.ZodObject<{
|
|
5
|
+
requestId: z.ZodString;
|
|
6
|
+
expectedRevision: z.ZodString;
|
|
7
|
+
runtime: z.ZodEnum<{
|
|
8
|
+
windows: "windows";
|
|
9
|
+
wsl: "wsl";
|
|
10
|
+
}>;
|
|
11
|
+
machineId: z.ZodOptional<z.ZodString>;
|
|
12
|
+
backend: z.ZodEnum<{
|
|
13
|
+
antigravity: "antigravity";
|
|
14
|
+
"claude-code": "claude-code";
|
|
15
|
+
codex: "codex";
|
|
16
|
+
grok: "grok";
|
|
17
|
+
"kiro-cli": "kiro-cli";
|
|
18
|
+
muse: "muse";
|
|
19
|
+
opencode: "opencode";
|
|
20
|
+
}>;
|
|
21
|
+
model: z.ZodDefault<z.ZodString>;
|
|
22
|
+
directory: z.ZodDefault<z.ZodString>;
|
|
23
|
+
sandbox: z.ZodDefault<z.ZodEnum<{
|
|
24
|
+
"read-only": "read-only";
|
|
25
|
+
"workspace-write": "workspace-write";
|
|
26
|
+
}>>;
|
|
27
|
+
adapter: z.ZodDefault<z.ZodEnum<{
|
|
28
|
+
exec: "exec";
|
|
29
|
+
pane: "pane";
|
|
30
|
+
}>>;
|
|
31
|
+
startNewConversation: z.ZodLiteral<true>;
|
|
32
|
+
}, z.core.$strict>;
|
|
33
|
+
export declare const GENERAL_USES_LOCATION_SWITCH = "General is moved from Settings \u2192 Agent defaults \u2192 General location, not from Runtime settings";
|
|
34
|
+
export type AgentRuntimeTarget = z.infer<typeof AgentRuntimeChangeInput>;
|
|
35
|
+
export interface PreparedAgentRuntime {
|
|
36
|
+
config: InstanceConfig;
|
|
37
|
+
discard(): void;
|
|
38
|
+
current?(): boolean;
|
|
39
|
+
}
|
|
40
|
+
export interface AgentRuntimeChangeHost {
|
|
41
|
+
config(name: string): InstanceConfig | undefined;
|
|
42
|
+
defaults(): unknown;
|
|
43
|
+
generation(name: string): unknown;
|
|
44
|
+
epoch(name: string): number;
|
|
45
|
+
backendState?(name: string, backend: string): string | undefined;
|
|
46
|
+
transition<T>(name: string, run: (stop: () => Promise<void>) => Promise<T>): Promise<T>;
|
|
47
|
+
/** Must reject busy, queued and unknown source work, including retained native owners. */
|
|
48
|
+
idle(name: string): boolean;
|
|
49
|
+
prepare(name: string, before: InstanceConfig, target: AgentRuntimeTarget, current: () => boolean): Promise<PreparedAgentRuntime>;
|
|
50
|
+
closed(name: string, before: InstanceConfig): Promise<boolean>;
|
|
51
|
+
/** Prepare fresh-session pointers without erasing provider history; returns a rollback for an unsaved change. */
|
|
52
|
+
fresh(name: string, requestId: string): () => void;
|
|
53
|
+
save(name: string, before: InstanceConfig, next: InstanceConfig): void;
|
|
54
|
+
changed(name: string): void;
|
|
55
|
+
}
|
|
56
|
+
export declare class AgentRuntimeChanges {
|
|
57
|
+
private readonly host;
|
|
58
|
+
private attempts;
|
|
59
|
+
constructor(host: AgentRuntimeChangeHost);
|
|
60
|
+
private isGeneral;
|
|
61
|
+
describe(name: string): {
|
|
62
|
+
revision: string;
|
|
63
|
+
runtime: string;
|
|
64
|
+
machineId: string | undefined;
|
|
65
|
+
backend: string;
|
|
66
|
+
backendState: string | undefined;
|
|
67
|
+
directory: string;
|
|
68
|
+
model: string;
|
|
69
|
+
general: boolean;
|
|
70
|
+
adapter: "exec" | "pane";
|
|
71
|
+
sandbox: "read-only" | "workspace-write";
|
|
72
|
+
};
|
|
73
|
+
change(name: string, raw: unknown, authorized: () => boolean): Promise<unknown>;
|
|
74
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/** Administrator-only moves of one Fleet identity. Provider sessions never cross machines or providers. */
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { settingsFingerprint } from "./settings-transaction.js";
|
|
4
|
+
export const AgentRuntimeChangeInput = z.object({
|
|
5
|
+
requestId: z.string().uuid(), expectedRevision: z.string().regex(/^[a-f0-9]{64}$/),
|
|
6
|
+
runtime: z.enum(["wsl", "windows"]), machineId: z.string().uuid().optional(),
|
|
7
|
+
backend: z.enum(["claude-code", "codex", "opencode", "kiro-cli", "antigravity", "grok", "muse"]),
|
|
8
|
+
model: z.string().max(160).default(""), directory: z.string().max(1024).default(""),
|
|
9
|
+
sandbox: z.enum(["read-only", "workspace-write"]).default("read-only"),
|
|
10
|
+
adapter: z.enum(["exec", "pane"]).default("exec"), startNewConversation: z.literal(true),
|
|
11
|
+
}).strict().superRefine((value, ctx) => {
|
|
12
|
+
if (value.runtime === "wsl" && (value.machineId || value.adapter !== "exec"))
|
|
13
|
+
ctx.addIssue({ code: "custom", message: "Choose the Fleet host for a WSL/Linux agent" });
|
|
14
|
+
if (value.runtime === "windows" && value.adapter === "pane" && !['codex', 'claude-code'].includes(value.backend))
|
|
15
|
+
ctx.addIssue({ code: "custom", message: "Persistent Windows terminals support Codex and Claude Code" });
|
|
16
|
+
});
|
|
17
|
+
export const GENERAL_USES_LOCATION_SWITCH = "General is moved from Settings → Agent defaults → General location, not from Runtime settings";
|
|
18
|
+
export class AgentRuntimeChanges {
|
|
19
|
+
host;
|
|
20
|
+
attempts = new Map();
|
|
21
|
+
constructor(host) {
|
|
22
|
+
this.host = host;
|
|
23
|
+
}
|
|
24
|
+
isGeneral(name, config) { return config?.general_topic === true || name === "general"; }
|
|
25
|
+
describe(name) {
|
|
26
|
+
const config = this.host.config(name);
|
|
27
|
+
if (!config)
|
|
28
|
+
throw Error("Agent not found");
|
|
29
|
+
const backend = config.backend || this.host.defaults()?.backend || "claude-code";
|
|
30
|
+
return { revision: settingsFingerprint([config, this.host.defaults(), this.host.generation(name), this.host.epoch(name)]),
|
|
31
|
+
runtime: config.runtime === "windows" ? "windows" : "wsl", machineId: config.machine_id, backend, backendState: this.host.backendState?.(name, backend),
|
|
32
|
+
directory: config.working_directory, model: config.model ?? "", general: this.isGeneral(name, config), adapter: config.native_adapter ?? "exec", sandbox: config.native_sandbox ?? "read-only" };
|
|
33
|
+
}
|
|
34
|
+
change(name, raw, authorized) {
|
|
35
|
+
const target = AgentRuntimeChangeInput.parse(raw), digest = settingsFingerprint(target), old = this.attempts.get(name);
|
|
36
|
+
if (!authorized())
|
|
37
|
+
throw Error("Runtime change requires a current dashboard administrator");
|
|
38
|
+
// General's location has its own journalled switch (Agent defaults → General location, #3036): it holds deliveries,
|
|
39
|
+
// confirms the old host exited and clears the Linux-only fields. This path does none of that, so it is not offered.
|
|
40
|
+
if (this.isGeneral(name, this.host.config(name)))
|
|
41
|
+
throw Error(GENERAL_USES_LOCATION_SWITCH);
|
|
42
|
+
if (old?.id === target.requestId) {
|
|
43
|
+
if (old.digest !== digest)
|
|
44
|
+
throw Error("Runtime request ID belongs to another change");
|
|
45
|
+
return old.result;
|
|
46
|
+
}
|
|
47
|
+
if (old?.pending)
|
|
48
|
+
throw Error("A Runtime change is already in progress for this Agent");
|
|
49
|
+
if (this.describe(name).revision !== target.expectedRevision)
|
|
50
|
+
throw Error("Agent settings changed; reopen Runtime settings before switching");
|
|
51
|
+
// One synchronous claim per Agent before preparation, transition queueing or workspace allocation.
|
|
52
|
+
const attempt = { id: target.requestId, digest, pending: true, result: null };
|
|
53
|
+
this.attempts.set(name, attempt);
|
|
54
|
+
attempt.result = Promise.resolve().then(() => this.host.transition(name, async (stop) => {
|
|
55
|
+
if (this.describe(name).revision !== target.expectedRevision)
|
|
56
|
+
throw Error("Agent settings changed while waiting to switch Runtime");
|
|
57
|
+
const before = this.host.config(name), baseline = settingsFingerprint([this.host.defaults(), this.host.generation(name)]);
|
|
58
|
+
let epoch = this.host.epoch(name), revoked = false, prepared;
|
|
59
|
+
const current = () => {
|
|
60
|
+
if (revoked)
|
|
61
|
+
return false;
|
|
62
|
+
if (!authorized() || this.host.config(name) !== before || this.host.epoch(name) !== epoch || settingsFingerprint([this.host.defaults(), this.host.generation(name)]) !== baseline || !this.host.idle(name) || prepared?.current?.() === false)
|
|
63
|
+
revoked = true;
|
|
64
|
+
return !revoked;
|
|
65
|
+
};
|
|
66
|
+
if (!current())
|
|
67
|
+
throw Error("Wait for this Agent and its queued work to finish before switching Runtime");
|
|
68
|
+
let undoSession, saved = false;
|
|
69
|
+
try {
|
|
70
|
+
prepared = await this.host.prepare(name, before, target, current);
|
|
71
|
+
if (!current())
|
|
72
|
+
throw Error("Runtime change was revoked or the Agent became busy during preparation");
|
|
73
|
+
// stop() owns exactly one epoch advance, even for the exec adapter. Any other advance permanently revokes us.
|
|
74
|
+
epoch++;
|
|
75
|
+
await stop();
|
|
76
|
+
if (!current())
|
|
77
|
+
throw Error("Runtime change lost its source owner while stopping the old CLI");
|
|
78
|
+
if (!await this.host.closed(name, before))
|
|
79
|
+
throw Error("The old CLI exit is unconfirmed; Runtime settings were not changed");
|
|
80
|
+
if (!current())
|
|
81
|
+
throw Error("Runtime change was revoked while checking the old CLI exit");
|
|
82
|
+
if (target.runtime === "wsl")
|
|
83
|
+
undoSession = this.host.fresh(name, target.requestId);
|
|
84
|
+
if (!current())
|
|
85
|
+
throw Error("Runtime change authority expired before saving");
|
|
86
|
+
this.host.save(name, before, prepared.config);
|
|
87
|
+
saved = true;
|
|
88
|
+
this.host.changed(name);
|
|
89
|
+
return { ok: true, name, runtime: target.runtime, machineId: target.machineId, backend: target.backend, started: false, historyPreserved: true };
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
if (!saved) {
|
|
93
|
+
undoSession?.();
|
|
94
|
+
prepared?.discard();
|
|
95
|
+
}
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
})).finally(() => { attempt.pending = false; });
|
|
99
|
+
return attempt.result;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=agent-runtime-change.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-runtime-change.js","sourceRoot":"","sources":["../src/agent-runtime-change.ts"],"names":[],"mappings":"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"}
|
package/dist/auth-api.d.ts
CHANGED
|
@@ -51,6 +51,26 @@ export interface AuthApiContext {
|
|
|
51
51
|
authorizeHostSession?(record: SessionRecord, peer: WebPeer): void;
|
|
52
52
|
/** #2910 D5: no Discord or Telegram connection at all — the sign-in page leads with `agend web --code`. */
|
|
53
53
|
isWebOnlyFleet?(): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* #3008: a code from the authenticator app, typed at the outside link's sign-in (FleetManager.outsideSignIn): "ok"
|
|
56
|
+
* only for the link connections from outside opened (`exposureId`), with a code not used before.
|
|
57
|
+
*/
|
|
58
|
+
outsideSignIn?(exposureId: string | undefined, code: string): {
|
|
59
|
+
kind: "ok";
|
|
60
|
+
} | {
|
|
61
|
+
kind: "wrong";
|
|
62
|
+
} | {
|
|
63
|
+
kind: "paused";
|
|
64
|
+
retryAfterMs: number;
|
|
65
|
+
} | {
|
|
66
|
+
kind: "closed";
|
|
67
|
+
} | {
|
|
68
|
+
kind: "unavailable";
|
|
69
|
+
};
|
|
70
|
+
/** #3008: this exposure is the link "Allow connections from outside" opened, open now (its sign-in asks for the code). */
|
|
71
|
+
isOutsideLink?(exposureId: string): boolean;
|
|
72
|
+
/** #3008: the session outsideSignIn's "ok" made is this computer's admin (and a Notice says who signed in). */
|
|
73
|
+
authorizeOutsideSession?(record: SessionRecord, peer: WebPeer, label: string): boolean;
|
|
54
74
|
/** #1570: record this browser as a returning device of the code's owner; the cookie value, or null when not kept. */
|
|
55
75
|
rememberReturningDevice?(owner: LoginCodeOwner, input: {
|
|
56
76
|
surface: "local" | "gateway";
|
|
@@ -113,6 +133,7 @@ export declare function bypassesWebGate(req: {
|
|
|
113
133
|
*/
|
|
114
134
|
export declare function serveSigninPage(res: ServerResponse, status?: number, opts?: {
|
|
115
135
|
webOnly?: boolean;
|
|
136
|
+
outside?: boolean;
|
|
116
137
|
}): void;
|
|
117
138
|
/**
|
|
118
139
|
* What a guest's browser gets for a page it cannot open (#2399 smoke: the gate's bare JSON before): "members" for a page
|
package/dist/auth-api.js
CHANGED
|
@@ -88,7 +88,14 @@ const ASSETS = {
|
|
|
88
88
|
"fx-motion.js",
|
|
89
89
|
// #1958: identity colours on the page and the Color dialog (static code; the colours come in the status frame).
|
|
90
90
|
"identity-color.js",
|
|
91
|
+
// #2936 / #2978 merge: Settings' content width (this device's), shared by the shell and the lazy Settings panel.
|
|
92
|
+
"settings-width.js",
|
|
93
|
+
// The computer's display name, in a module of its own so a tab opened before an upgrade loads it fresh (Chat imports it).
|
|
94
|
+
"machine-name.js",
|
|
91
95
|
"preact.module.js", "preact-hooks.module.js", "htm.module.js",
|
|
96
|
+
// #3008: the QR code of an authenticator's setup (qrcode-generator 2.0.4, MIT; shared/vendor/vendor.json, byte-checked).
|
|
97
|
+
// Static code, no data — the secret is drawn by Settings behind the gate; loaded only when an app is being set up.
|
|
98
|
+
"qrcode.module.js",
|
|
92
99
|
].map(name => [name, { file: join("shared", name), type: "text/javascript; charset=utf-8" }])),
|
|
93
100
|
"app.css": { file: join("shared", "app.css"), type: "text/css; charset=utf-8" },
|
|
94
101
|
};
|
|
@@ -133,7 +140,9 @@ function json(res, code, body, headers = {}) {
|
|
|
133
140
|
export function serveSigninPage(res, status = 200, opts = {}) {
|
|
134
141
|
try {
|
|
135
142
|
const page = readFileSync(join(__dirname, "ui", "signin.html"), "utf-8");
|
|
136
|
-
|
|
143
|
+
// #3008: through the link "Allow connections from outside" opened, the page asks for the authenticator's code.
|
|
144
|
+
const marks = `${opts.webOnly ? ' data-web-only="1"' : ""}${opts.webOnly && opts.outside ? ' data-outside="1"' : ""}`;
|
|
145
|
+
const html = marks ? page.replace("<body>", `<body${marks}>`) : page;
|
|
137
146
|
sendPanelHtml(res, withAppIconLinks(html), status, { "Cache-Control": "no-store" });
|
|
138
147
|
}
|
|
139
148
|
catch {
|
|
@@ -194,7 +203,8 @@ export function handleAuthRequest(req, res, url, ctx) {
|
|
|
194
203
|
json(res, 405, { error: "method not allowed" });
|
|
195
204
|
return true;
|
|
196
205
|
}
|
|
197
|
-
|
|
206
|
+
const link = gatewayRequestContext(req);
|
|
207
|
+
serveSigninPage(res, 200, { webOnly: ctx.isWebOnlyFleet?.() === true, outside: !!link && ctx.isOutsideLink?.(link.exposureId) === true });
|
|
198
208
|
return true;
|
|
199
209
|
}
|
|
200
210
|
if (path.startsWith("/assets/")) {
|
|
@@ -213,6 +223,87 @@ export function handleAuthRequest(req, res, url, ctx) {
|
|
|
213
223
|
}
|
|
214
224
|
return true;
|
|
215
225
|
}
|
|
226
|
+
// ── #3008: POST /auth/totp — signing in from outside, with the authenticator app's code ──
|
|
227
|
+
// Only through the link "Allow connections from outside" opened (the gateway's own provenance names it, never a
|
|
228
|
+
// header); anything else is refused as a wrong code would be, without counting. An admin session with no account,
|
|
229
|
+
// marked `remote`, ended by the switch's Off, a new app, or the link closing.
|
|
230
|
+
if (path === "/auth/totp") {
|
|
231
|
+
if (method !== "POST") {
|
|
232
|
+
json(res, 405, { error: "method not allowed" });
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
if (!req.headers.origin || !isSameOriginRequest(gateReq)) {
|
|
236
|
+
json(res, 403, { error: "Cross-site request rejected" });
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
if (!isJsonRequest(req)) {
|
|
240
|
+
json(res, 415, { error: "expected application/json" });
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
const token = ctx.webToken, gateway = gatewayRequestContext(req);
|
|
244
|
+
if (!token || !ctx.webSessions || !gateway || !ctx.outsideSignIn) {
|
|
245
|
+
json(res, 404, { error: "not_found" });
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
const sessions = ctx.webSessions, signIn = ctx.outsideSignIn.bind(ctx);
|
|
249
|
+
void readJsonBody(req).then(body => {
|
|
250
|
+
if (!body || typeof body.code !== "string" || body.code.length > 32) {
|
|
251
|
+
json(res, 400, { error: "expected {\"code\": \"123456\"}" });
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (!isWebRequestCurrent(req) || ctx.webToken !== token) {
|
|
255
|
+
json(res, 401, { error: "wrong_code" });
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
const result = signIn(gateway.exposureId, body.code);
|
|
259
|
+
if (result.kind === "paused") {
|
|
260
|
+
json(res, 429, { error: "paused", retryAfterMs: result.retryAfterMs }, { "Retry-After": String(Math.ceil(result.retryAfterMs / 1000)) });
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (result.kind === "closed") {
|
|
264
|
+
json(res, 404, { error: "not_found" });
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (result.kind === "unavailable") {
|
|
268
|
+
json(res, 503, { error: "unavailable" });
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (result.kind === "wrong") {
|
|
272
|
+
ctx.logger.debug("outside sign-in refused");
|
|
273
|
+
json(res, 401, { error: "wrong_code" });
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
for (const previous of readSessionCookies(gateReq, token)) {
|
|
277
|
+
if (sessions.authenticate(previous, tokenEpoch(token), { touch: false, surface: gateway.surface, exposureId: gateway.exposureId }))
|
|
278
|
+
sessions.revokeById(previous);
|
|
279
|
+
}
|
|
280
|
+
const label = labelFromUserAgent(typeof req.headers["user-agent"] === "string" ? req.headers["user-agent"] : undefined);
|
|
281
|
+
const { sessionId, record } = sessions.create({ tier: "admin", surface: gateway.surface, label, tokenEpoch: tokenEpoch(token), exposureId: gateway.exposureId, remote: "totp" });
|
|
282
|
+
if (record.remote !== "totp") {
|
|
283
|
+
sessions.revokeById(sessionId);
|
|
284
|
+
json(res, 404, { error: "not_found" });
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
ctx.logger.info({ handle: record.handle, label, surface: record.surface }, "Web sign-in from outside");
|
|
288
|
+
// #3015 review r3: admitted (its handle saved) and authorized, or no session at all.
|
|
289
|
+
let admitted = false;
|
|
290
|
+
try {
|
|
291
|
+
admitted = ctx.authorizeOutsideSession?.(record, gateway.named === true ? "named_link" : "quick_link", label) === true;
|
|
292
|
+
}
|
|
293
|
+
catch (err) {
|
|
294
|
+
ctx.logger.debug({ err }, "outside session not authorized");
|
|
295
|
+
}
|
|
296
|
+
if (!admitted) {
|
|
297
|
+
sessions.revokeById(sessionId);
|
|
298
|
+
json(res, 503, { error: "unavailable" });
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const session = buildSessionCookie(sessionId, isSecureRequest(gateReq), (record.absoluteExpiry - record.created) / 1000, token);
|
|
302
|
+
json(res, 200, { ok: true, csrf: csrfTokenFor(sessionId), tier: record.tier, expiresAt: record.absoluteExpiry }, { "Set-Cookie": session });
|
|
303
|
+
}).catch(() => { if (!res.destroyed && !res.headersSent)
|
|
304
|
+
json(res, 500, { error: "sign-in failed" }); });
|
|
305
|
+
return true;
|
|
306
|
+
}
|
|
216
307
|
// ── POST /auth/login ──
|
|
217
308
|
if (path === "/auth/login") {
|
|
218
309
|
if (method !== "POST") {
|
|
@@ -267,8 +358,8 @@ export function handleAuthRequest(req, res, url, ctx) {
|
|
|
267
358
|
const guestCodes = ctx.webGuestLoginCodes ?? null;
|
|
268
359
|
const audience = gateway?.exposureId ?? "local";
|
|
269
360
|
const guestPausedMs = guestCodes?.pausedForMs() ?? 0;
|
|
270
|
-
const result = guestPausedMs > 0 ? { kind: "paused", retryAfterMs: Math.max(guestPausedMs, codes.pausedForMs()) }
|
|
271
|
-
: codes.pausedForMs() > 0 ? codes.redeem(body.code, tokenEpoch(token), audience)
|
|
361
|
+
const result = guestPausedMs > 0 ? { kind: "paused", retryAfterMs: Math.max(guestPausedMs, codes.pausedForMs(audience)) }
|
|
362
|
+
: codes.pausedForMs(audience) > 0 ? codes.redeem(body.code, tokenEpoch(token), audience)
|
|
272
363
|
: guestCodes?.match(body.code, tokenEpoch(token), audience) ?? codes.redeem(body.code, tokenEpoch(token), audience);
|
|
273
364
|
if (result.kind === "invalid")
|
|
274
365
|
guestCodes?.recordWrong(body.code);
|